Consolidate separate platform CI build workflows and improve !build comment commands

This commit is contained in:
Keavon Chambers 2026-03-16 14:43:59 -07:00
parent efd142f030
commit df8001fca8
17 changed files with 848 additions and 772 deletions

View File

@ -1,78 +0,0 @@
name: "Build Linux Bundle"
on:
workflow_dispatch:
inputs:
push_to_cache:
description: "Push to Nix Cache"
required: false
type: boolean
default: false
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: ❄ Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: 🗑 Free disk space
run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache
- name: 📦 Build Nix package
run: nix build --no-link --print-out-paths
- name: 📤 Push to Nix cache
if: github.ref == 'refs/heads/master' || inputs.push_to_cache == true
env:
NIX_CACHE_AUTH_TOKEN: ${{ secrets.NIX_CACHE_AUTH_TOKEN }}
run: |
nix run nixpkgs#cachix -- authtoken $NIX_CACHE_AUTH_TOKEN
nix build --no-link --print-out-paths | nix run nixpkgs#cachix -- push graphite
- name: 🏗 Build Linux bundle
run: nix build .#graphite-bundle.tar.xz && cp ./result ./graphite-linux-bundle.tar.xz
- name: 📦 Upload Linux bundle
uses: actions/upload-artifact@v4
with:
name: graphite-linux-bundle
path: graphite-linux-bundle.tar.xz
compression-level: 0
- name: 🔧 Install Flatpak tooling
run: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
- name: 🏗 Build Flatpak
run: |
nix build .#graphite-flatpak-manifest
rm -rf .flatpak
mkdir -p .flatpak
cp ./result .flatpak/manifest.json
cd .flatpak
mkdir -p repo
flatpak-builder --user --force-clean --install-deps-from=flathub --repo=repo build ./manifest.json
flatpak build-bundle repo Graphite.flatpak art.graphite.Graphite --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
- name: 📦 Upload Flatpak package
uses: actions/upload-artifact@v4
with:
name: graphite-flatpak
path: .flatpak/Graphite.flatpak
compression-level: 0

View File

@ -1,158 +0,0 @@
name: "Build Mac Bundle"
on:
workflow_dispatch: {}
push:
branches:
- master
jobs:
build:
runs-on: macos-latest
env:
WASM_BINDGEN_CLI_VERSION: "0.2.100"
steps:
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
rustflags: ""
target: wasm32-unknown-unknown
- name: 💾 Set up Cargo cache
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: 🟢 Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: |
package-lock.json
frontend/package-lock.json
- name: 🚧 Install native dependencies
env:
GITHUB_TOKEN: ${{ github.token }}
BINSTALL_DISABLE_TELEMETRY: "true"
run: |
brew update
brew install \
pkg-config \
openssl@3 \
binaryen \
llvm \
cargo-binstall
echo "OPENSSL_DIR=$(brew --prefix openssl@3)" >> $GITHUB_ENV
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
echo "$(brew --prefix llvm)/bin" >> $GITHUB_PATH
cargo binstall --no-confirm --force wasm-pack
cargo binstall --no-confirm --force cargo-about
cargo binstall --no-confirm --force "wasm-bindgen-cli@${WASM_BINDGEN_CLI_VERSION}"
- name: 🏗 Build Mac bundle
env:
CARGO_TERM_COLOR: always
run: cargo run build desktop
- name: 📁 Stage artifacts
shell: bash
run: |
rm -rf target/artifacts
mkdir -p target/artifacts
cp -R target/release/Graphite.app target/artifacts/Graphite.app
- name: 📦 Upload Mac bundle
if: github.ref != 'refs/heads/master'
uses: actions/upload-artifact@v4
with:
name: graphite-mac-bundle
path: target/artifacts
- name: 🔏 Sign and notarize (preparation)
if: github.ref == 'refs/heads/master'
env:
APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }}
APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}
run: |
mkdir -p .sign
echo "$APPLE_CERT_BASE64" | base64 --decode > .sign/certificate.p12
security create-keychain -p "" .sign/main.keychain
security default-keychain -s .sign/main.keychain
security unlock-keychain -p "" .sign/main.keychain
security set-keychain-settings -t 3600 -u .sign/main.keychain
security import .sign/certificate.p12 -k .sign/main.keychain -P "$APPLE_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign
security set-key-partition-list -S apple-tool:,apple: -s -k "" .sign/main.keychain
cat > .sign/entitlements.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
EOF
- name: 🔏 Sign and notarize
if: github.ref == 'refs/heads/master'
env:
APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_CERT_NAME: ${{ secrets.APPLE_CERT_NAME }}
run: |
CERTIFICATE="$APPLE_CERT_NAME"
ENTITLEMENTS=".sign/entitlements.plist"
APP_PATH="target/artifacts/Graphite.app"
ZIP_PATH=".sign/Graphite.zip"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Graphite Helper.app"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Graphite Helper (GPU).app"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Graphite Helper (Renderer).app"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libcef_sandbox.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libEGL.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libGLESv2.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libvk_swiftshader.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH" --deep
codesign --verify --deep --strict --verbose=4 "$APP_PATH"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
xcrun notarytool submit "$ZIP_PATH" --wait --apple-id "$APPLE_EMAIL" --team-id "$APPLE_TEAM_ID" --password "$APPLE_PASSWORD"
rm "$ZIP_PATH"
xcrun stapler staple -v "$APP_PATH"
spctl -a -vv "$APP_PATH"
- name: 📦 Upload signed Mac bundle
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v4
with:
name: graphite-mac-bundle-signed
path: target/artifacts

View File

@ -1,22 +0,0 @@
name: "Build Nix Package"
on:
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: ❄ Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: 💾 Set up Nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: 📦 Build Nix package dev
run: nix build .#graphite-dev --print-build-logs

View File

@ -1,155 +0,0 @@
name: "Build Web Bundle"
on:
workflow_dispatch: {}
push:
branches:
- master
env:
CARGO_TERM_COLOR: always
INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="dev.graphite.art" data-api="https://graphite.art/visit/event" src="https://graphite.art/visit/script.hash.js"></script>
jobs:
build:
runs-on: [self-hosted, target/wasm]
permissions:
contents: write
deployments: write
actions: write
env:
RUSTC_WRAPPER: /usr/bin/sccache
CARGO_INCREMENTAL: 0
SCCACHE_DIR: /var/lib/github-actions/.cache
steps:
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: 🗑 Clear wasm-bindgen cache
run: rm -r ~/.cache/.wasm-pack || true
- name: 🟢 Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: 🚧 Install build dependencies
run: |
cd frontend
npm run setup
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
rustflags: ""
target: wasm32-unknown-unknown
- name: ✂ Replace template in <head> of index.html
run: |
sed -i "s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
- name: 🌐 Build Graphite web code
env:
NODE_ENV: production
run: mold -run cargo run build web
- name: 📤 Publish to Cloudflare Pages
id: cloudflare
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
MAX_ATTEMPTS=5
DELAY=30
for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
echo "Attempt $ATTEMPT of $MAX_ATTEMPTS..."
if OUTPUT=$(npx wrangler@3 pages deploy "frontend/dist" --project-name="graphite-dev" --commit-dirty=true 2>&1); then
URL=$(echo "$OUTPUT" | grep -oP 'https://[^\s]+\.pages\.dev' | tail -1)
echo "url=$URL" >> "$GITHUB_OUTPUT"
echo "Published successfully: $URL"
exit 0
fi
echo "Attempt $ATTEMPT failed:"
echo "$OUTPUT"
if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
echo "Retrying in ${DELAY}s..."
sleep $DELAY
DELAY=$((DELAY * 3))
fi
done
echo "All $MAX_ATTEMPTS Cloudflare Pages publish attempts failed."
exit 1
- name: 💬 Comment build link URL to commit hash page on GitHub
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CF_URL: ${{ steps.cloudflare.outputs.url }}
run: |
if [ -z "$CF_URL" ]; then
echo "No Cloudflare URL available, skipping comment."
exit 1
fi
gh api \
-X POST \
-H "Accept: application/vnd.github+json" \
/repos/${{ github.repository }}/commits/$(git rev-parse HEAD)/comments \
-f body="| 📦 **Build Complete for** $(git rev-parse HEAD) |
|-|
| $CF_URL |"
- name: ✂ Strip analytics script from built output for clean artifact
run: |
sed -i "s|$INDEX_HTML_HEAD_REPLACEMENT||" frontend/dist/index.html
- name: 📦 Upload web bundle artifact
uses: actions/upload-artifact@v4
with:
name: graphite-web-bundle
path: frontend/dist
- name: 📃 Generate code documentation info for website
run: |
cd tools/editor-message-tree
cargo run
cd ../..
mkdir -p artifacts-generated
mv website/generated/hierarchical_message_system_tree.txt artifacts-generated/hierarchical_message_system_tree.txt
- name: 💿 Obtain cache of auto-generated code docs artifacts, to check if they've changed
id: cache-website-code-docs
uses: actions/cache/restore@v4
with:
path: artifacts
key: website-code-docs
- name: 🔍 Check if auto-generated code docs artifacts changed
id: website-code-docs-changed
run: |
if ! diff --brief --recursive artifacts-generated artifacts; then
echo "Auto-generated code docs artifacts have changed."
rm -rf artifacts
mv artifacts-generated artifacts
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "Auto-generated code docs artifacts have not changed."
rm -rf artifacts
rm -rf artifacts-generated
fi
- name: 💾 Save cache of auto-generated code docs artifacts
if: steps.website-code-docs-changed.outputs.changed == 'true'
uses: actions/cache/save@v4
with:
path: artifacts
key: ${{ steps.cache-website-code-docs.outputs.cache-primary-key }}
- name: ♻️ Trigger website rebuild if the auto-generated code docs artifacts have changed
if: steps.website-code-docs-changed.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
rm -rf artifacts
gh workflow run website.yml --ref master

View File

@ -1,173 +0,0 @@
name: "Build Windows Bundle"
on:
workflow_dispatch: {}
push:
branches:
- master
jobs:
build:
runs-on: windows-latest
permissions:
contents: read
id-token: write
env:
WASM_BINDGEN_CLI_VERSION: "0.2.100"
steps:
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
rustflags: ""
target: wasm32-unknown-unknown
- name: 💾 Set up Cargo cache
uses: actions/cache@v4
with:
path: |
${{ env.USERPROFILE }}\.cargo\registry
${{ env.USERPROFILE }}\.cargo\git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: 🟢 Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: |
package-lock.json
frontend/package-lock.json
- name: 📦 Install Cargo-binstall
uses: cargo-bins/cargo-binstall@main
- name: 🚧 Install native dependencies
shell: pwsh
env:
GITHUB_TOKEN: ${{ github.token }}
BINSTALL_DISABLE_TELEMETRY: "true"
run: |
winget install --id LLVM.LLVM -e --accept-package-agreements --accept-source-agreements
winget install --id Kitware.CMake -e --accept-package-agreements --accept-source-agreements
winget install --id OpenSSL.OpenSSL -e --accept-package-agreements --accept-source-agreements
winget install --id WebAssembly.Binaryen -e --accept-package-agreements --accept-source-agreements
winget install --id GnuWin32.PkgConfig -e --accept-package-agreements --accept-source-agreements
"OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" | Out-File -FilePath $env:GITHUB_ENV -Append
"PKG_CONFIG_PATH=C:\Program Files\OpenSSL-Win64\lib\pkgconfig" | Out-File -FilePath $env:GITHUB_ENV -Append
cargo binstall --no-confirm --force wasm-pack
cargo binstall --no-confirm --force cargo-about
cargo binstall --no-confirm --force "wasm-bindgen-cli@$env:WASM_BINDGEN_CLI_VERSION"
- name: 🏗 Build Windows bundle
shell: bash # `cargo-about` refuses to run in powershell
env:
CARGO_TERM_COLOR: always
run: cargo run build desktop
- name: 📁 Stage artifacts
shell: bash
run: |
rm -rf target/artifacts
mkdir -p target/artifacts
cp -R target/release/Graphite target/artifacts/Graphite
- name: 📦 Upload Windows bundle
if: github.ref != 'refs/heads/master'
uses: actions/upload-artifact@v4
with:
name: graphite-windows-bundle
path: target/artifacts
- name: 🔑 Azure login
if: github.ref == 'refs/heads/master'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
enable-AzPSSession: true
- name: 🔏 Sign
if: github.ref == 'refs/heads/master'
uses: azure/artifact-signing-action@v1
with:
endpoint: https://eus.codesigning.azure.net/
signing-account-name: Graphite
certificate-profile-name: Graphite
files: |
${{ github.workspace }}\target\artifacts\Graphite\Graphite.exe
${{ github.workspace }}\target\artifacts\Graphite\libcef.dll
${{ github.workspace }}\target\artifacts\Graphite\chrome_elf.dll
${{ github.workspace }}\target\artifacts\Graphite\vulkan-1.dll
${{ github.workspace }}\target\artifacts\Graphite\dxcompiler.dll
${{ github.workspace }}\target\artifacts\Graphite\libEGL.dll
${{ github.workspace }}\target\artifacts\Graphite\libGLESv2.dll
${{ github.workspace }}\target\artifacts\Graphite\vk_swiftshader.dll
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
correlation-id: ${{ github.sha }}
- name: ✅ Verify signatures
if: github.ref == 'refs/heads/master'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$TargetDir = "target\artifacts\Graphite"
if (-not (Test-Path $TargetDir)) {
throw "TargetDir not found: $TargetDir"
}
$UnsignedOrBad = @()
Get-ChildItem -Path $TargetDir -Recurse -File -Include *.exe,*.dll | ForEach-Object {
$sig = Get-AuthenticodeSignature -FilePath $_.FullName
if ($sig.Status -ne 'Valid') {
$UnsignedOrBad += "$($_.FullName) (Status=$($sig.Status))"
}
}
if ($UnsignedOrBad.Count -gt 0) {
Write-Host "Unsigned or invalid binaries detected:"
$UnsignedOrBad | ForEach-Object {
Write-Host "::error::$_"
}
if ($env:GITHUB_STEP_SUMMARY) {
"### ❌ Unsigned or invalid binaries detected" |
Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
"" | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
$UnsignedOrBad | ForEach-Object {
"* `$_" | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
}
exit 1
}
Write-Host "All binaries are signed and valid."
if ($env:GITHUB_STEP_SUMMARY) {
"### ✅ All binaries are signed and valid" |
Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
- name: 📦 Upload signed Windows bundle
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v4
with:
name: graphite-windows-bundle-signed
path: target/artifacts

675
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,675 @@
name: "Build"
on:
push:
branches:
- master
workflow_dispatch:
inputs:
web:
description: "Web"
type: boolean
windows:
description: "Windows"
type: boolean
mac:
description: "Mac"
type: boolean
linux:
description: "Linux"
type: boolean
push_to_nix_cache:
description: "Linux: push to Nix cache"
type: boolean
debug:
description: "Debug build"
type: boolean
workflow_call:
inputs:
web:
type: boolean
windows:
type: boolean
mac:
type: boolean
linux:
type: boolean
push_to_nix_cache:
type: boolean
debug:
type: boolean
checkout_repo:
type: string
checkout_ref:
type: string
pr_number:
type: string
jobs:
web:
if: github.event_name == 'push' || inputs.web
runs-on: [self-hosted, target/wasm]
permissions:
contents: write
deployments: write
pull-requests: write
actions: write
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: /usr/bin/sccache
CARGO_INCREMENTAL: 0
SCCACHE_DIR: /var/lib/github-actions/.cache
INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="dev.graphite.art" data-api="https://graphite.art/visit/event" src="https://graphite.art/visit/script.hash.js"></script>
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
with:
repository: ${{ inputs.checkout_repo || github.repository }}
ref: ${{ inputs.checkout_ref || '' }}
- name: 🗑 Clear wasm-bindgen cache
run: rm -r ~/.cache/.wasm-pack || true
- name: 🟢 Install Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: 🚧 Install build dependencies
run: |
cd frontend
npm run setup
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
target: wasm32-unknown-unknown
- name: ✂ Replace template in <head> of index.html
if: github.event_name == 'push'
run: |
sed -i "s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
- name: 🌐 Build Graphite web code
env:
NODE_ENV: production
run: mold -run cargo run build web${{ inputs.debug && ' debug' || '' }}
- name: 📤 Publish to Cloudflare Pages
id: cloudflare
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
MAX_ATTEMPTS=5
DELAY=30
for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
echo "Attempt $ATTEMPT of $MAX_ATTEMPTS..."
if OUTPUT=$(npx wrangler@3 pages deploy "frontend/dist" --project-name="graphite-dev" --commit-dirty=true 2>&1); then
URL=$(echo "$OUTPUT" | grep -oP 'https://[^\s]+\.pages\.dev' | head -1)
echo "url=$URL" >> "$GITHUB_OUTPUT"
echo "Published successfully: $URL"
exit 0
fi
echo "Attempt $ATTEMPT failed:"
echo "$OUTPUT"
if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
echo "Retrying in ${DELAY}s..."
sleep $DELAY
DELAY=$((DELAY * 3))
fi
done
echo "All $MAX_ATTEMPTS Cloudflare Pages publish attempts failed."
exit 1
- name: 💬 Comment with the build link
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CF_URL: ${{ steps.cloudflare.outputs.url }}
run: |
if [ -z "$CF_URL" ]; then
echo "No Cloudflare URL available, skipping comment."
exit 0
fi
COMMENT_BODY="| 📦 **Web Build Complete for** $(git rev-parse HEAD) |
|-|
| $CF_URL |"
if [ "${{ github.event_name }}" = "push" ]; then
# Comment on the commit hash page
gh api \
-X POST \
-H "Accept: application/vnd.github+json" \
/repos/${{ github.repository }}/commits/$(git rev-parse HEAD)/comments \
-f body="$COMMENT_BODY"
else
# Comment on the PR (use provided PR number from !build, or look it up by branch name)
PR_NUMBER="${{ inputs.pr_number }}"
if [ -z "$PR_NUMBER" ]; then
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
fi
if [ -n "$PR_NUMBER" ]; then
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "$COMMENT_BODY"
else
echo "No open PR found, skipping comment."
fi
fi
- name: ✂ Strip analytics script from built output for clean artifact
if: github.event_name == 'push'
run: |
sed -i "s|$INDEX_HTML_HEAD_REPLACEMENT||" frontend/dist/index.html
- name: 📦 Upload web bundle artifact
uses: actions/upload-artifact@v6
with:
name: graphite-web-bundle
path: frontend/dist
- name: 📃 Generate code documentation info for website
if: github.event_name == 'push'
run: |
cd tools/editor-message-tree
cargo run
cd ../..
mkdir -p artifacts-generated
mv website/generated/hierarchical_message_system_tree.txt artifacts-generated/hierarchical_message_system_tree.txt
- name: 💿 Obtain cache of auto-generated code docs artifacts, to check if they've changed
if: github.event_name == 'push'
id: cache-website-code-docs
uses: actions/cache/restore@v5
with:
path: artifacts
key: website-code-docs
- name: 🔍 Check if auto-generated code docs artifacts changed
if: github.event_name == 'push'
id: website-code-docs-changed
run: |
if ! diff --brief --recursive artifacts-generated artifacts; then
echo "Auto-generated code docs artifacts have changed."
rm -rf artifacts
mv artifacts-generated artifacts
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "Auto-generated code docs artifacts have not changed."
rm -rf artifacts
rm -rf artifacts-generated
fi
- name: 💾 Save cache of auto-generated code docs artifacts
if: github.event_name == 'push' && steps.website-code-docs-changed.outputs.changed == 'true'
uses: actions/cache/save@v5
with:
path: artifacts
key: ${{ steps.cache-website-code-docs.outputs.cache-primary-key }}
- name: ♻️ Trigger website rebuild if the auto-generated code docs artifacts have changed
if: github.event_name == 'push' && steps.website-code-docs-changed.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
rm -rf artifacts
gh workflow run website.yml --ref master
windows:
if: github.event_name == 'push' || inputs.windows
runs-on: windows-latest
permissions:
contents: read
id-token: write
pull-requests: write
env:
WASM_BINDGEN_CLI_VERSION: "0.2.100"
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
with:
repository: ${{ inputs.checkout_repo || github.repository }}
ref: ${{ inputs.checkout_ref || '' }}
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
target: wasm32-unknown-unknown
- name: 💾 Set up Cargo cache
uses: actions/cache@v5
with:
path: |
${{ env.USERPROFILE }}\.cargo\registry
${{ env.USERPROFILE }}\.cargo\git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: 🟢 Install Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: |
package-lock.json
frontend/package-lock.json
- name: 📦 Install Cargo-binstall
uses: cargo-bins/cargo-binstall@main
- name: 🚧 Install native dependencies
shell: pwsh
env:
GITHUB_TOKEN: ${{ github.token }}
BINSTALL_DISABLE_TELEMETRY: "true"
run: |
winget install --id LLVM.LLVM -e --accept-package-agreements --accept-source-agreements
winget install --id Kitware.CMake -e --accept-package-agreements --accept-source-agreements
winget install --id OpenSSL.OpenSSL -e --accept-package-agreements --accept-source-agreements
winget install --id WebAssembly.Binaryen -e --accept-package-agreements --accept-source-agreements
winget install --id GnuWin32.PkgConfig -e --accept-package-agreements --accept-source-agreements
"OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" | Out-File -FilePath $env:GITHUB_ENV -Append
"PKG_CONFIG_PATH=C:\Program Files\OpenSSL-Win64\lib\pkgconfig" | Out-File -FilePath $env:GITHUB_ENV -Append
cargo binstall --no-confirm --force wasm-pack
cargo binstall --no-confirm --force cargo-about
cargo binstall --no-confirm --force "wasm-bindgen-cli@$env:WASM_BINDGEN_CLI_VERSION"
- name: 🏗 Build Windows bundle
shell: bash # `cargo-about` refuses to run in powershell
env:
CARGO_TERM_COLOR: always
run: cargo run build desktop${{ inputs.debug && ' debug' || '' }}
- name: 📁 Stage artifacts
shell: bash
run: |
PROFILE=${{ inputs.debug && 'debug' || 'release' }}
rm -rf target/artifacts
mkdir -p target/artifacts
cp -R target/$PROFILE/Graphite target/artifacts/Graphite
- name: 📦 Upload Windows bundle
if: github.event_name != 'push'
uses: actions/upload-artifact@v6
with:
name: graphite-windows-bundle
path: target/artifacts
- name: 💬 Comment artifact link on PR
if: github.event_name != 'push'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ARTIFACT_URL=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-windows-bundle") | .archive_download_url')
PR_NUMBER="${{ inputs.pr_number }}"
if [ -z "$PR_NUMBER" ]; then
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
fi
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_URL" ]; then
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "| 📦 **Windows Build Complete for** $(git rev-parse HEAD) |
|-|
| [Download artifact]($ARTIFACT_URL) |"
fi
- name: 🔑 Azure login
if: github.event_name == 'push'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
enable-AzPSSession: true
- name: 🔏 Sign
if: github.event_name == 'push'
uses: azure/artifact-signing-action@v1
with:
endpoint: https://eus.codesigning.azure.net/
signing-account-name: Graphite
certificate-profile-name: Graphite
files: |
${{ github.workspace }}\target\artifacts\Graphite\Graphite.exe
${{ github.workspace }}\target\artifacts\Graphite\libcef.dll
${{ github.workspace }}\target\artifacts\Graphite\chrome_elf.dll
${{ github.workspace }}\target\artifacts\Graphite\vulkan-1.dll
${{ github.workspace }}\target\artifacts\Graphite\dxcompiler.dll
${{ github.workspace }}\target\artifacts\Graphite\libEGL.dll
${{ github.workspace }}\target\artifacts\Graphite\libGLESv2.dll
${{ github.workspace }}\target\artifacts\Graphite\vk_swiftshader.dll
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
correlation-id: ${{ github.sha }}
- name: ✅ Verify signatures
if: github.event_name == 'push'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$TargetDir = "target\artifacts\Graphite"
if (-not (Test-Path $TargetDir)) {
throw "TargetDir not found: $TargetDir"
}
$UnsignedOrBad = @()
Get-ChildItem -Path $TargetDir -Recurse -File -Include *.exe,*.dll | ForEach-Object {
$sig = Get-AuthenticodeSignature -FilePath $_.FullName
if ($sig.Status -ne 'Valid') {
$UnsignedOrBad += "$($_.FullName) (Status=$($sig.Status))"
}
}
if ($UnsignedOrBad.Count -gt 0) {
Write-Host "Unsigned or invalid binaries detected:"
$UnsignedOrBad | ForEach-Object {
Write-Host "::error::$_"
}
if ($env:GITHUB_STEP_SUMMARY) {
"### ❌ Unsigned or invalid binaries detected" |
Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
"" | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
$UnsignedOrBad | ForEach-Object {
"* `$_" | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
}
exit 1
}
Write-Host "All binaries are signed and valid."
if ($env:GITHUB_STEP_SUMMARY) {
"### ✅ All binaries are signed and valid" |
Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
- name: 📦 Upload signed Windows bundle
if: github.event_name == 'push'
uses: actions/upload-artifact@v6
with:
name: graphite-windows-bundle-signed
path: target/artifacts
mac:
if: github.event_name == 'push' || inputs.mac
runs-on: macos-latest
permissions:
contents: read
pull-requests: write
env:
WASM_BINDGEN_CLI_VERSION: "0.2.100"
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
with:
repository: ${{ inputs.checkout_repo || github.repository }}
ref: ${{ inputs.checkout_ref || '' }}
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
target: wasm32-unknown-unknown
- name: 💾 Set up Cargo cache
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: 🟢 Install Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: |
package-lock.json
frontend/package-lock.json
- name: 🚧 Install native dependencies
env:
GITHUB_TOKEN: ${{ github.token }}
BINSTALL_DISABLE_TELEMETRY: "true"
run: |
brew update
brew install \
pkg-config \
openssl@3 \
binaryen \
llvm \
cargo-binstall
echo "OPENSSL_DIR=$(brew --prefix openssl@3)" >> $GITHUB_ENV
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
echo "$(brew --prefix llvm)/bin" >> $GITHUB_PATH
cargo binstall --no-confirm --force wasm-pack
cargo binstall --no-confirm --force cargo-about
cargo binstall --no-confirm --force "wasm-bindgen-cli@${WASM_BINDGEN_CLI_VERSION}"
- name: 🏗 Build Mac bundle
env:
CARGO_TERM_COLOR: always
run: cargo run build desktop${{ inputs.debug && ' debug' || '' }}
- name: 📁 Stage artifacts
shell: bash
run: |
PROFILE=${{ inputs.debug && 'debug' || 'release' }}
rm -rf target/artifacts
mkdir -p target/artifacts
cp -R target/$PROFILE/Graphite.app target/artifacts/Graphite.app
- name: 📦 Upload Mac bundle
if: github.event_name != 'push'
uses: actions/upload-artifact@v6
with:
name: graphite-mac-bundle
path: target/artifacts
- name: 💬 Comment artifact link on PR
if: github.event_name != 'push'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ARTIFACT_URL=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-mac-bundle") | .archive_download_url')
PR_NUMBER="${{ inputs.pr_number }}"
if [ -z "$PR_NUMBER" ]; then
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
fi
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_URL" ]; then
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "| 📦 **Mac Build Complete for** $(git rev-parse HEAD) |
|-|
| [Download artifact]($ARTIFACT_URL) |"
fi
- name: 🔏 Sign and notarize (preparation)
if: github.event_name == 'push'
env:
APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }}
APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}
run: |
mkdir -p .sign
echo "$APPLE_CERT_BASE64" | base64 --decode > .sign/certificate.p12
security create-keychain -p "" .sign/main.keychain
security default-keychain -s .sign/main.keychain
security unlock-keychain -p "" .sign/main.keychain
security set-keychain-settings -t 3600 -u .sign/main.keychain
security import .sign/certificate.p12 -k .sign/main.keychain -P "$APPLE_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign
security set-key-partition-list -S apple-tool:,apple: -s -k "" .sign/main.keychain
cat > .sign/entitlements.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
EOF
- name: 🔏 Sign and notarize
if: github.event_name == 'push'
env:
APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_CERT_NAME: ${{ secrets.APPLE_CERT_NAME }}
run: |
CERTIFICATE="$APPLE_CERT_NAME"
ENTITLEMENTS=".sign/entitlements.plist"
APP_PATH="target/artifacts/Graphite.app"
ZIP_PATH=".sign/Graphite.zip"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Graphite Helper.app"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Graphite Helper (GPU).app"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Graphite Helper (Renderer).app"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libcef_sandbox.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libEGL.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libGLESv2.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libvk_swiftshader.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH" --deep
codesign --verify --deep --strict --verbose=4 "$APP_PATH"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
xcrun notarytool submit "$ZIP_PATH" --wait --apple-id "$APPLE_EMAIL" --team-id "$APPLE_TEAM_ID" --password "$APPLE_PASSWORD"
rm "$ZIP_PATH"
xcrun stapler staple -v "$APP_PATH"
spctl -a -vv "$APP_PATH"
- name: 📦 Upload signed Mac bundle
if: github.event_name == 'push'
uses: actions/upload-artifact@v6
with:
name: graphite-mac-bundle-signed
path: target/artifacts
linux:
if: github.event_name == 'push' || inputs.linux
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
with:
repository: ${{ inputs.checkout_repo || github.repository }}
ref: ${{ inputs.checkout_ref || '' }}
- name: ❄ Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: 🗑 Free disk space
run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache
- name: 📦 Build Nix package
run: nix build .#graphite${{ inputs.debug && '-dev' || '' }} --no-link --print-out-paths
- name: 📤 Push to Nix cache
if: (github.event_name == 'push' || inputs.push_to_nix_cache) && !inputs.debug
env:
NIX_CACHE_AUTH_TOKEN: ${{ secrets.NIX_CACHE_AUTH_TOKEN }}
run: |
nix run nixpkgs#cachix -- authtoken $NIX_CACHE_AUTH_TOKEN
nix build --no-link --print-out-paths | nix run nixpkgs#cachix -- push graphite
- name: 🏗 Build Linux bundle
run: nix build .#graphite${{ inputs.debug && '-dev' || '' }}-bundle.tar.xz && cp ./result ./graphite-linux-bundle.tar.xz
- name: 📦 Upload Linux bundle
uses: actions/upload-artifact@v6
with:
name: graphite-linux-bundle
path: graphite-linux-bundle.tar.xz
compression-level: 0
- name: 💬 Comment artifact link on PR
if: github.event_name != 'push'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ARTIFACT_URL=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-linux-bundle") | .archive_download_url')
PR_NUMBER="${{ inputs.pr_number }}"
if [ -z "$PR_NUMBER" ]; then
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
fi
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_URL" ]; then
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "| 📦 **Linux Build Complete for** $(git rev-parse HEAD) |
|-|
| [Download artifact]($ARTIFACT_URL) |"
fi
- name: 🔧 Install Flatpak tooling
run: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
- name: 🏗 Build Flatpak
run: |
nix build .#graphite-flatpak-manifest
rm -rf .flatpak
mkdir -p .flatpak
cp ./result .flatpak/manifest.json
cd .flatpak
mkdir -p repo
flatpak-builder --user --force-clean --install-deps-from=flathub --repo=repo build ./manifest.json
flatpak build-bundle repo Graphite.flatpak art.graphite.Graphite --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
- name: 📦 Upload Flatpak package
uses: actions/upload-artifact@v6
with:
name: graphite-flatpak
path: .flatpak/Graphite.flatpak
compression-level: 0

View File

@ -12,7 +12,7 @@ jobs:
steps: steps:
- name: 📥 Clone repository - name: 📥 Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: 🔒 Check crate security advisories for root workspace - name: 🔒 Check crate security advisories for root workspace
uses: EmbarkStudios/cargo-deny-action@v2 uses: EmbarkStudios/cargo-deny-action@v2

View File

@ -8,48 +8,6 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
jobs: jobs:
# Rust format check on GitHub runner
rust-fmt:
runs-on: ubuntu-latest
steps:
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: 🚦 Check if CI can be skipped
id: skip-check
uses: cariad-tech/merge-queue-ci-skipper@main
- name: 🦀 Install Rust
if: steps.skip-check.outputs.skip-check != 'true'
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
rustflags: ""
components: rustfmt
- name: 🔬 Check Rust formatting
if: steps.skip-check.outputs.skip-check != 'true'
run: cargo fmt --all -- --check
# License compatibility check on GitHub runner
cargo-deny:
runs-on: ubuntu-latest
steps:
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: 📜 Check crate license compatibility for root workspace
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check bans licenses sources
- name: 📜 Check crate license compatibility for /libraries/rawkit
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check bans licenses sources
manifest-path: libraries/rawkit/Cargo.toml
# Build the web app on the self-hosted wasm runner # Build the web app on the self-hosted wasm runner
build: build:
runs-on: [self-hosted, target/wasm] runs-on: [self-hosted, target/wasm]
@ -65,7 +23,7 @@ jobs:
steps: steps:
- name: 📥 Clone repository - name: 📥 Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: 🚦 Check if CI can be skipped - name: 🚦 Check if CI can be skipped
id: skip-check id: skip-check
@ -77,7 +35,7 @@ jobs:
- name: 🟢 Install Node.js - name: 🟢 Install Node.js
if: steps.skip-check.outputs.skip-check != 'true' if: steps.skip-check.outputs.skip-check != 'true'
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version-file: .nvmrc node-version-file: .nvmrc
@ -93,6 +51,7 @@ jobs:
with: with:
toolchain: stable toolchain: stable
override: true override: true
cache: false
rustflags: "" rustflags: ""
target: wasm32-unknown-unknown target: wasm32-unknown-unknown
@ -134,7 +93,7 @@ jobs:
steps: steps:
- name: 📥 Clone repository - name: 📥 Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: 🚦 Check if CI can be skipped - name: 🚦 Check if CI can be skipped
id: skip-check id: skip-check
@ -146,6 +105,7 @@ jobs:
with: with:
toolchain: stable toolchain: stable
override: true override: true
cache: false
rustflags: "" rustflags: ""
- name: 🦀 Fetch Rust dependencies - name: 🦀 Fetch Rust dependencies
@ -159,3 +119,46 @@ jobs:
RUSTFLAGS: -Dwarnings RUSTFLAGS: -Dwarnings
run: | run: |
mold -run cargo test --all-features mold -run cargo test --all-features
# Rust format check on GitHub runner
rust-fmt:
runs-on: ubuntu-latest
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: 🚦 Check if CI can be skipped
id: skip-check
uses: cariad-tech/merge-queue-ci-skipper@main
- name: 🦀 Install Rust
if: steps.skip-check.outputs.skip-check != 'true'
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
components: rustfmt
- name: 🔬 Check Rust formatting
if: steps.skip-check.outputs.skip-check != 'true'
run: cargo fmt --all -- --check
# License compatibility check on GitHub runner
check-licenses:
runs-on: ubuntu-latest
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: 📜 Check crate license compatibility for root workspace
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check bans licenses sources
- name: 📜 Check crate license compatibility for /libraries/rawkit
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check bans licenses sources
manifest-path: libraries/rawkit/Cargo.toml

View File

@ -1,158 +1,137 @@
# USAGE: # USAGE:
# After reviewing the code, core team members may comment on a PR with the exact text: # After reviewing the code, core team members may comment on a PR with `!build` followed by optional `<target>` and `<profile>` arguments.
# - `!build-debug` to build with debug symbols and optimizations disabled # This matches the syntax of the `cargo run build` CLI command, but allows platforms to be specified.
# - `!build` to build without debug symbols and optimizations enabled #
# The comment may not contain any other text, not even whitespace or newlines. # `<target>`: `web` (default), `desktop` (all platforms), or `desktop:<platforms>` (subset of `windows+mac+linux`)
# `<profile>`: `release` (default) or `debug`
#
# Examples:
# - !build
# - !build debug
# - !build desktop
# - !build desktop:windows+mac
# - !build desktop:linux debug
name: "!build PR Command" name: "!build PR Command"
on: on:
issue_comment: issue_comment:
types: types:
- created - created
env:
CARGO_TERM_COLOR: always
jobs: jobs:
build: setup:
# Command should be limited to core team members (those in the organization) for security. # Command should be limited to core team members (those in the organization) for security.
# From the GitHub Actions docs: # From the GitHub Actions docs:
# author_association = 'MEMBER': Author is a member of the organization that owns the repository. # author_association = 'MEMBER': Author is a member of the organization that owns the repository.
if: > if: >
github.event.issue.pull_request && github.event.issue.pull_request &&
github.event.comment.author_association == 'MEMBER' && github.event.comment.author_association == 'MEMBER' &&
(github.event.comment.body == '!build-debug' || github.event.comment.body == '!build') startsWith(github.event.comment.body, '!build')
runs-on: self-hosted runs-on: ubuntu-latest
permissions: permissions:
contents: read
deployments: write
pull-requests: write pull-requests: write
env: outputs:
RUSTC_WRAPPER: /usr/bin/sccache repo: ${{ steps.pr_info.outputs.repo }}
CARGO_INCREMENTAL: 0 ref: ${{ steps.pr_info.outputs.ref }}
SCCACHE_DIR: /var/lib/github-actions/.cache web: ${{ steps.pr_info.outputs.web }}
windows: ${{ steps.pr_info.outputs.windows }}
mac: ${{ steps.pr_info.outputs.mac }}
linux: ${{ steps.pr_info.outputs.linux }}
debug: ${{ steps.pr_info.outputs.debug }}
steps: steps:
- name: 🔎 Find branch for this PR - name: 🔎 Parse command, find branch, and set build flags
id: commit_info id: pr_info
run: | run: |
RESPONSE=$(curl -L -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}) COMMENT="${{ github.event.comment.body }}"
REPO=$(echo $RESPONSE | jq -r '.head.repo.full_name')
REF=$(echo $RESPONSE | jq -r '.head.ref')
SHA=$(echo $RESPONSE | jq -r '.head.sha')
echo "repo=$REPO" >> $GITHUB_OUTPUT
echo "ref=$REF" >> $GITHUB_OUTPUT
echo "sha=$SHA" >> $GITHUB_OUTPUT
- name: 📥 Clone repository # Split into space-separated words
uses: actions/checkout@v4 read -ra WORDS <<< "$COMMENT"
with:
repository: ${{ steps.commit_info.outputs.repo }}
ref: ${{ steps.commit_info.outputs.ref }}
- name: 🗑 Clear wasm-bindgen cache # First word must be "!build"
continue-on-error: true if [[ "${WORDS[0]}" != "!build" ]]; then
run: rm -r ~/.cache/.wasm-pack echo "::error::Expected comment to start with !build"
exit 1
- name: 🟢 Install Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: 🚧 Install build dependencies
run: |
cd frontend
npm run setup
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
rustflags: ""
target: wasm32-unknown-unknown
- name: ✂ Replace template in <head> of index.html
env:
INDEX_HTML_HEAD_REPLACEMENT: ""
run: |
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
sed -i "s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
- name: ⌨ Set build command based on comment
id: build_command
run: |
if [[ "${{ github.event.comment.body }}" == "!build-debug" ]]; then
echo "command=build web debug" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.comment.body }}" == "!build" ]]; then
echo "command=build web" >> $GITHUB_OUTPUT
else
echo "Failed to detect if the build command written in the comment should have been '!build-debug', or '!build'" >> $GITHUB_OUTPUT
fi fi
- name: 💬 Comment Actions run link # Initialize build flags (web defaults to true, matching the CLI default target)
id: comment_actions_run_link WEB="true"
uses: actions/github-script@v6 WINDOWS="false"
MAC="false"
LINUX="false"
DEBUG="false"
# Parse target (optional, defaults to `web` if omitted)
IDX=1
case "${WORDS[$IDX]:-}" in
# Target: `web` enables just the web build (already the default, but accepted explicitly)
"web")
((IDX++)) ;;
# Target: `desktop` enables all three desktop platforms
"desktop")
WEB="false"; WINDOWS="true"; MAC="true"; LINUX="true"; ((IDX++)) ;;
# Target: `desktop:<platforms>` enables a subset of desktop platforms, split by `+`
desktop:*)
WEB="false"
PLATFORMS="${WORDS[$IDX]#desktop:}"
IFS='+' read -ra PARTS <<< "$PLATFORMS"
for PART in "${PARTS[@]}"; do
case "$PART" in
"windows") WINDOWS="true" ;;
"mac") MAC="true" ;;
"linux") LINUX="true" ;;
*) echo "::error::Unrecognized platform: $PART"; exit 1 ;;
esac
done
((IDX++))
;;
esac
# Parse profile (optional, defaults to `release` if omitted)
case "${WORDS[$IDX]:-}" in
"debug") DEBUG="true"; ((IDX++)) ;;
"release") ((IDX++)) ;;
esac
# Reject any unexpected trailing words
if [[ $IDX -lt ${#WORDS[@]} ]]; then
echo "::error::Unexpected argument: ${WORDS[$IDX]}"
exit 1
fi
# Write parsed build flags to job outputs
echo "web=$WEB" >> $GITHUB_OUTPUT
echo "windows=$WINDOWS" >> $GITHUB_OUTPUT
echo "mac=$MAC" >> $GITHUB_OUTPUT
echo "linux=$LINUX" >> $GITHUB_OUTPUT
echo "debug=$DEBUG" >> $GITHUB_OUTPUT
# Fetch the PR's head branch and repo (needed for forked PRs where the code lives in another repo)
RESPONSE=$(curl -L -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.issue.number }})
echo "repo=$(echo $RESPONSE | jq -r '.head.repo.full_name')" >> $GITHUB_OUTPUT
echo "ref=$(echo $RESPONSE | jq -r '.head.ref')" >> $GITHUB_OUTPUT
- name: 💬 Edit comment with workflow run link
uses: actions/github-script@v8
with: with:
script: | script: |
github.rest.issues.updateComment({ github.rest.issues.updateComment({
comment_id: ${{ github.event.comment.id }}, comment_id: ${{ github.event.comment.id }},
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
body: '!build ([Run ID ' + context.runId + '](https://github.com/GraphiteEditor/Graphite/actions/runs/' + context.runId + '))' body: '${{ github.event.comment.body }} ([Run ID ' + context.runId + '](https://github.com/GraphiteEditor/Graphite/actions/runs/' + context.runId + '))'
}); });
- name: 🌐 Build Graphite web code invoke-build:
env: needs: setup
NODE_ENV: production uses: ./.github/workflows/build.yml
if: ${{ success() || failure()}} secrets: inherit
run: mold -run cargo run ${{ steps.build_command.outputs.command }}
- name: ❗ Warn on build failure
if: ${{ failure() }}
uses: actions/github-script@v6
with: with:
script: | web: ${{ needs.setup.outputs.web == 'true' }}
github.rest.issues.createComment({ windows: ${{ needs.setup.outputs.windows == 'true' }}
issue_number: context.issue.number, mac: ${{ needs.setup.outputs.mac == 'true' }}
owner: context.repo.owner, linux: ${{ needs.setup.outputs.linux == 'true' }}
repo: context.repo.repo, debug: ${{ needs.setup.outputs.debug == 'true' }}
body: 'The build process has failed. Please check the [build logs](https://github.com/GraphiteEditor/Graphite/actions/runs/' + context.runId + ') for details.' checkout_repo: ${{ needs.setup.outputs.repo }}
}); checkout_ref: ${{ needs.setup.outputs.ref }}
pr_number: ${{ github.event.issue.number }}
- name: 📤 Publish to Cloudflare Pages
id: cloudflare
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
if OUTPUT=$(npx wrangler@3 pages deploy "frontend/dist" --project-name="graphite-dev" --commit-dirty=true 2>&1); then
URL=$(echo "$OUTPUT" | grep -oP 'https://[^\s]+\.pages\.dev' | tail -1)
echo "url=$URL" >> "$GITHUB_OUTPUT"
echo "Published successfully: $URL"
else
echo "$OUTPUT"
exit 1
fi
- name: ❗ Warn on publish failure
if: ${{ failure() }}
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'The deployment to Cloudflare Pages has failed. Please check the [build logs](https://github.com/GraphiteEditor/Graphite/actions/runs/' + context.runId + ') for details.'
});
- name: 💬 Comment build link
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '| 📦 **Build Complete for** ${{ steps.commit_info.outputs.sha }} |\n|-|\n| ${{ steps.cloudflare.outputs.url }} |'
})

View File

@ -15,7 +15,7 @@ jobs:
contents: read contents: read
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
@ -35,7 +35,7 @@ jobs:
- name: Cache iai-callgrind binary - name: Cache iai-callgrind binary
id: cache-iai id: cache-iai
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: ~/.cargo/bin/iai-callgrind-runner path: ~/.cargo/bin/iai-callgrind-runner
key: ${{ runner.os }}-iai-callgrind-runner-0.16.1 key: ${{ runner.os }}-iai-callgrind-runner-0.16.1
@ -65,7 +65,7 @@ jobs:
- name: Cache benchmark baselines - name: Cache benchmark baselines
id: cache-benchmark-baselines id: cache-benchmark-baselines
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: target/iai path: target/iai
key: ${{ runner.os }}-${{ runner.arch }}-${{ steps.cpu-info.outputs.cpu-hash }}-benchmark-baselines-master-${{ steps.master-sha.outputs.sha }} key: ${{ runner.os }}-${{ runner.arch }}-${{ steps.cpu-info.outputs.cpu-hash }}-benchmark-baselines-master-${{ steps.master-sha.outputs.sha }}

View File

@ -26,13 +26,14 @@ jobs:
steps: steps:
- name: 📥 Clone repository - name: 📥 Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: 🦀 Install Rust - name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: stable toolchain: stable
override: true override: true
cache: false
rustflags: "" rustflags: ""
- name: 📦 Run sccache-cache - name: 📦 Run sccache-cache

View File

@ -13,7 +13,7 @@ jobs:
contents: read contents: read
steps: steps:
- name: 📥 Clone repository - name: 📥 Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: ❄ Install Nix - name: ❄ Install Nix
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@main

View File

@ -1,4 +1,4 @@
name: "Editor: Production (Latest Stable)" name: "Release"
on: on:
push: push:
@ -22,13 +22,13 @@ jobs:
steps: steps:
- name: 📥 Clone repository - name: 📥 Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: 🗑 Clear wasm-bindgen cache - name: 🗑 Clear wasm-bindgen cache
run: rm -r ~/.cache/.wasm-pack run: rm -r ~/.cache/.wasm-pack
- name: 🟢 Install Node.js - name: 🟢 Install Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version-file: .nvmrc node-version-file: .nvmrc
@ -42,6 +42,7 @@ jobs:
with: with:
toolchain: stable toolchain: stable
override: true override: true
cache: false
rustflags: "" rustflags: ""
target: wasm32-unknown-unknown target: wasm32-unknown-unknown

View File

@ -24,10 +24,10 @@ jobs:
steps: steps:
- name: 📥 Clone repository - name: 📥 Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: 🟢 Install Node.js - name: 🟢 Install Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version-file: .nvmrc node-version-file: .nvmrc
@ -51,7 +51,7 @@ jobs:
- name: 💿 Obtain cache of auto-generated code docs artifacts - name: 💿 Obtain cache of auto-generated code docs artifacts
id: cache-website-code-docs id: cache-website-code-docs
uses: actions/cache/restore@v4 uses: actions/cache/restore@v5
with: with:
path: artifacts path: artifacts
key: website-code-docs key: website-code-docs

View File

@ -59,7 +59,8 @@ in
graphite-dev = (lib.call ./pkgs/graphite.nix) { dev = true; }; graphite-dev = (lib.call ./pkgs/graphite.nix) { dev = true; };
graphite-raster-nodes-shaders = lib.call ./pkgs/graphite-raster-nodes-shaders.nix; graphite-raster-nodes-shaders = lib.call ./pkgs/graphite-raster-nodes-shaders.nix;
graphite-branding = lib.call ./pkgs/graphite-branding.nix; graphite-branding = lib.call ./pkgs/graphite-branding.nix;
graphite-bundle = lib.call ./pkgs/graphite-bundle.nix; graphite-bundle = (lib.call ./pkgs/graphite-bundle.nix) { };
graphite-dev-bundle = (lib.call ./pkgs/graphite-bundle.nix) { graphite = graphite-dev; };
graphite-flatpak-manifest = lib.call ./pkgs/graphite-flatpak-manifest.nix; graphite-flatpak-manifest = lib.call ./pkgs/graphite-flatpak-manifest.nix;
# TODO: graphene-cli = lib.call ./pkgs/graphene-cli.nix; # TODO: graphene-cli = lib.call ./pkgs/graphene-cli.nix;

View File

@ -4,6 +4,9 @@
system, system,
... ...
}: }:
{
graphite ? self.packages.${system}.graphite,
}:
let let
bundle = bundle =
{ {
@ -13,7 +16,6 @@ let
}: }:
( (
let let
graphite = self.packages.${system}.graphite;
tar = if compression == null then archive else true; tar = if compression == null then archive else true;
nameArchiveSuffix = if tar then ".tar" else ""; nameArchiveSuffix = if tar then ".tar" else "";
nameCompressionSuffix = if compression == null then "" else "." + compression; nameCompressionSuffix = if compression == null then "" else "." + compression;