diff --git a/.github/workflows/build-linux-bundle.yml b/.github/workflows/build-linux-bundle.yml deleted file mode 100644 index e61300e3..00000000 --- a/.github/workflows/build-linux-bundle.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/build-mac-bundle.yml b/.github/workflows/build-mac-bundle.yml deleted file mode 100644 index 681542e0..00000000 --- a/.github/workflows/build-mac-bundle.yml +++ /dev/null @@ -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' - - - - - com.apple.security.cs.allow-jit - - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.disable-executable-page-protection - - com.apple.security.cs.disable-library-validation - - - - 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 diff --git a/.github/workflows/build-nix-package.yml b/.github/workflows/build-nix-package.yml deleted file mode 100644 index 2f2295a9..00000000 --- a/.github/workflows/build-nix-package.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/build-web-bundle.yml b/.github/workflows/build-web-bundle.yml deleted file mode 100644 index bde9beff..00000000 --- a/.github/workflows/build-web-bundle.yml +++ /dev/null @@ -1,155 +0,0 @@ -name: "Build Web Bundle" - -on: - workflow_dispatch: {} - push: - branches: - - master - -env: - CARGO_TERM_COLOR: always - INDEX_HTML_HEAD_REPLACEMENT: - -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 of index.html - run: | - sed -i "s||$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 diff --git a/.github/workflows/build-windows-bundle.yml b/.github/workflows/build-windows-bundle.yml deleted file mode 100644 index 8402b094..00000000 --- a/.github/workflows/build-windows-bundle.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..11af2efd --- /dev/null +++ b/.github/workflows/build.yml @@ -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: + + 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 of index.html + if: github.event_name == 'push' + run: | + sed -i "s||$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' + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.disable-library-validation + + + + 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 diff --git a/.github/workflows/cargo-deny.yml b/.github/workflows/cargo-deny.yml index e49d3ffb..7d696fba 100644 --- a/.github/workflows/cargo-deny.yml +++ b/.github/workflows/cargo-deny.yml @@ -12,7 +12,7 @@ jobs: steps: - name: 📥 Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🔒 Check crate security advisories for root workspace uses: EmbarkStudios/cargo-deny-action@v2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8410b6b2..f825072e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,48 +8,6 @@ env: CARGO_TERM_COLOR: always 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: runs-on: [self-hosted, target/wasm] @@ -65,7 +23,7 @@ jobs: steps: - name: 📥 Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🚦 Check if CI can be skipped id: skip-check @@ -77,7 +35,7 @@ jobs: - name: 🟢 Install Node.js if: steps.skip-check.outputs.skip-check != 'true' - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc @@ -93,6 +51,7 @@ jobs: with: toolchain: stable override: true + cache: false rustflags: "" target: wasm32-unknown-unknown @@ -134,7 +93,7 @@ jobs: steps: - name: 📥 Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🚦 Check if CI can be skipped id: skip-check @@ -146,6 +105,7 @@ jobs: with: toolchain: stable override: true + cache: false rustflags: "" - name: 🦀 Fetch Rust dependencies @@ -159,3 +119,46 @@ jobs: RUSTFLAGS: -Dwarnings run: | 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 diff --git a/.github/workflows/comment-!build-commands.yml b/.github/workflows/comment-!build-commands.yml index 9b7a5a18..a1799e55 100644 --- a/.github/workflows/comment-!build-commands.yml +++ b/.github/workflows/comment-!build-commands.yml @@ -1,158 +1,137 @@ # USAGE: -# After reviewing the code, core team members may comment on a PR with the exact text: -# - `!build-debug` to build with debug symbols and optimizations disabled -# - `!build` to build without debug symbols and optimizations enabled -# The comment may not contain any other text, not even whitespace or newlines. +# After reviewing the code, core team members may comment on a PR with `!build` followed by optional `` and `` arguments. +# This matches the syntax of the `cargo run build` CLI command, but allows platforms to be specified. +# +# ``: `web` (default), `desktop` (all platforms), or `desktop:` (subset of `windows+mac+linux`) +# ``: `release` (default) or `debug` +# +# Examples: +# - !build +# - !build debug +# - !build desktop +# - !build desktop:windows+mac +# - !build desktop:linux debug name: "!build PR Command" on: issue_comment: types: - created -env: - CARGO_TERM_COLOR: always jobs: - build: + setup: # Command should be limited to core team members (those in the organization) for security. # From the GitHub Actions docs: # author_association = 'MEMBER': Author is a member of the organization that owns the repository. if: > github.event.issue.pull_request && github.event.comment.author_association == 'MEMBER' && - (github.event.comment.body == '!build-debug' || github.event.comment.body == '!build') - runs-on: self-hosted + startsWith(github.event.comment.body, '!build') + runs-on: ubuntu-latest permissions: - contents: read - deployments: write pull-requests: write - env: - RUSTC_WRAPPER: /usr/bin/sccache - CARGO_INCREMENTAL: 0 - SCCACHE_DIR: /var/lib/github-actions/.cache + outputs: + repo: ${{ steps.pr_info.outputs.repo }} + ref: ${{ steps.pr_info.outputs.ref }} + 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: - - name: 🔎 Find branch for this PR - id: commit_info + - name: 🔎 Parse command, find branch, and set build flags + id: pr_info 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 }}) - 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 + COMMENT="${{ github.event.comment.body }}" - - name: 📥 Clone repository - uses: actions/checkout@v4 - with: - repository: ${{ steps.commit_info.outputs.repo }} - ref: ${{ steps.commit_info.outputs.ref }} + # Split into space-separated words + read -ra WORDS <<< "$COMMENT" - - name: 🗑 Clear wasm-bindgen cache - continue-on-error: true - run: rm -r ~/.cache/.wasm-pack - - - 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 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|" 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 + # First word must be "!build" + if [[ "${WORDS[0]}" != "!build" ]]; then + echo "::error::Expected comment to start with !build" + exit 1 fi - - name: 💬 Comment Actions run link - id: comment_actions_run_link - uses: actions/github-script@v6 + # Initialize build flags (web defaults to true, matching the CLI default target) + WEB="true" + 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:` 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: script: | github.rest.issues.updateComment({ comment_id: ${{ github.event.comment.id }}, owner: context.repo.owner, 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 - env: - NODE_ENV: production - if: ${{ success() || failure()}} - run: mold -run cargo run ${{ steps.build_command.outputs.command }} - - - name: ❗ Warn on build 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 build process has failed. Please check the [build logs](https://github.com/GraphiteEditor/Graphite/actions/runs/' + context.runId + ') for details.' - }); - - - 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 }} |' - }) + invoke-build: + needs: setup + uses: ./.github/workflows/build.yml + secrets: inherit + with: + web: ${{ needs.setup.outputs.web == 'true' }} + windows: ${{ needs.setup.outputs.windows == 'true' }} + mac: ${{ needs.setup.outputs.mac == 'true' }} + linux: ${{ needs.setup.outputs.linux == 'true' }} + debug: ${{ needs.setup.outputs.debug == 'true' }} + checkout_repo: ${{ needs.setup.outputs.repo }} + checkout_ref: ${{ needs.setup.outputs.ref }} + pr_number: ${{ github.event.issue.number }} diff --git a/.github/workflows/comment-clippy-warnings.yaml b/.github/workflows/comment-clippy-warnings.yaml index d9e2ff68..9e352bf6 100644 --- a/.github/workflows/comment-clippy-warnings.yaml +++ b/.github/workflows/comment-clippy-warnings.yaml @@ -15,7 +15,7 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Install Rust uses: actions-rs/toolchain@v1 diff --git a/.github/workflows/comment-profiling-changes.yaml b/.github/workflows/comment-profiling-changes.yaml index 3369f4e6..275f6589 100644 --- a/.github/workflows/comment-profiling-changes.yaml +++ b/.github/workflows/comment-profiling-changes.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -35,7 +35,7 @@ jobs: - name: Cache iai-callgrind binary id: cache-iai - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.cargo/bin/iai-callgrind-runner key: ${{ runner.os }}-iai-callgrind-runner-0.16.1 @@ -65,7 +65,7 @@ jobs: - name: Cache benchmark baselines id: cache-benchmark-baselines - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: target/iai key: ${{ runner.os }}-${{ runner.arch }}-${{ steps.cpu-info.outputs.cpu-hash }}-benchmark-baselines-master-${{ steps.master-sha.outputs.sha }} diff --git a/.github/workflows/library-rawkit.yml b/.github/workflows/library-rawkit.yml index 5d5b8bf6..910daac0 100644 --- a/.github/workflows/library-rawkit.yml +++ b/.github/workflows/library-rawkit.yml @@ -26,13 +26,14 @@ jobs: steps: - name: 📥 Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🦀 Install Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable override: true + cache: false rustflags: "" - name: 📦 Run sccache-cache diff --git a/.github/workflows/provide-shaders.yml b/.github/workflows/provide-shaders.yml index 84f75311..647878ce 100644 --- a/.github/workflows/provide-shaders.yml +++ b/.github/workflows/provide-shaders.yml @@ -13,7 +13,7 @@ jobs: contents: read steps: - name: 📥 Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: ❄ Install Nix uses: DeterminateSystems/nix-installer-action@main diff --git a/.github/workflows/build-production.yml b/.github/workflows/release.yml similarity index 95% rename from .github/workflows/build-production.yml rename to .github/workflows/release.yml index d9a186e2..602f4016 100644 --- a/.github/workflows/build-production.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: "Editor: Production (Latest Stable)" +name: "Release" on: push: @@ -22,13 +22,13 @@ jobs: steps: - name: 📥 Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🗑 Clear wasm-bindgen cache run: rm -r ~/.cache/.wasm-pack - name: 🟢 Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc @@ -42,6 +42,7 @@ jobs: with: toolchain: stable override: true + cache: false rustflags: "" target: wasm32-unknown-unknown diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index fae935b4..99964261 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -24,10 +24,10 @@ jobs: steps: - name: 📥 Clone repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🟢 Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc @@ -51,7 +51,7 @@ jobs: - name: 💿 Obtain cache of auto-generated code docs artifacts id: cache-website-code-docs - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: artifacts key: website-code-docs diff --git a/.nix/default.nix b/.nix/default.nix index e77cc4a6..20e26a96 100644 --- a/.nix/default.nix +++ b/.nix/default.nix @@ -59,7 +59,8 @@ in graphite-dev = (lib.call ./pkgs/graphite.nix) { dev = true; }; graphite-raster-nodes-shaders = lib.call ./pkgs/graphite-raster-nodes-shaders.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; # TODO: graphene-cli = lib.call ./pkgs/graphene-cli.nix; diff --git a/.nix/pkgs/graphite-bundle.nix b/.nix/pkgs/graphite-bundle.nix index d50b00fc..cf2d54de 100644 --- a/.nix/pkgs/graphite-bundle.nix +++ b/.nix/pkgs/graphite-bundle.nix @@ -4,6 +4,9 @@ system, ... }: +{ + graphite ? self.packages.${system}.graphite, +}: let bundle = { @@ -13,7 +16,6 @@ let }: ( let - graphite = self.packages.${system}.graphite; tar = if compression == null then archive else true; nameArchiveSuffix = if tar then ".tar" else ""; nameCompressionSuffix = if compression == null then "" else "." + compression;