From 076a9dd5a63e4cef0cb3104248e9172fa14d5ab5 Mon Sep 17 00:00:00 2001 From: David Stern Date: Wed, 24 Jun 2026 11:52:24 -0400 Subject: [PATCH 01/16] Optimize settings schema generation during release process. --- .../actions/bundle_arch_package/action.yml | 9 +- .github/workflows/create_release.yml | 225 +++++++++++--- app/Cargo.toml | 4 - app/src/bin/generate_settings_schema.rs | 284 ------------------ app/src/bin/generate_settings_schema_tests.rs | 32 -- app/src/lib.rs | 4 + app/src/settings/mod.rs | 2 + app/src/settings/schema_generation.rs | 221 ++++++++++++++ app/src/settings/schema_generation_tests.rs | 107 +++++++ crates/warp_cli/src/lib.rs | 8 + flake.nix | 11 +- script/deploy_remote_server | 5 +- script/deploy_remote_server_to_test_vm | 3 +- script/linux/bundle | 26 ++ script/macos/bundle | 23 ++ script/macos/run | 2 + script/prepare_bundled_resources | 59 ++-- script/test_prepare_bundled_resources | 44 +++ script/windows/bundle.ps1 | 10 + script/windows/prepare_bundled_resources.ps1 | 43 ++- 20 files changed, 712 insertions(+), 410 deletions(-) delete mode 100644 app/src/bin/generate_settings_schema.rs delete mode 100644 app/src/bin/generate_settings_schema_tests.rs create mode 100644 app/src/settings/schema_generation.rs create mode 100644 app/src/settings/schema_generation_tests.rs create mode 100755 script/test_prepare_bundled_resources diff --git a/.github/actions/bundle_arch_package/action.yml b/.github/actions/bundle_arch_package/action.yml index 2b3db2ee19d..87e079846e0 100644 --- a/.github/actions/bundle_arch_package/action.yml +++ b/.github/actions/bundle_arch_package/action.yml @@ -17,6 +17,9 @@ inputs: description: The artifact type to build (app or cli) required: false default: app + settings-schema-source: + description: The path to the settings schema to package. + required: true runs: using: composite @@ -27,16 +30,20 @@ runs: - name: Run in container shell: bash + env: + SETTINGS_SCHEMA_SOURCE: ${{ inputs.settings-schema-source }} run: | + settings_schema_source="$(realpath "$SETTINGS_SCHEMA_SOURCE")" docker run --rm \ -v "${{ github.workspace }}:/github/workspace" \ -v "${{ github.workspace }}/target:/github/workspace/target" \ + -v "$settings_schema_source:/tmp/settings_schema.json:ro" \ -w /github/workspace \ -v "${CARGO_HOME:-$HOME/.cargo}/git:/home/build/.cargo/git" \ -v "${CARGO_HOME:-$HOME/.cargo}/registry:/home/build/.cargo/registry" \ -e GIT_RELEASE_TAG="$GIT_RELEASE_TAG" \ -e GITHUB_ACTIONS="$GITHUB_ACTIONS" \ -e GITHUB_OUTPUT="/dev/null" \ - ${SETTINGS_SCHEMA_CACHE:+-e SETTINGS_SCHEMA_CACHE=/github/workspace/.settings_schema_cache.json} \ + -e SETTINGS_SCHEMA_SOURCE="/tmp/settings_schema.json" \ arch-bundle-builder \ ${{ inputs.channel }} ${{ inputs.release-tag }} ${{ inputs.arch }} ${{ inputs.artifact }} diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 97893d8d611..88ca9378000 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -81,7 +81,6 @@ jobs: with: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} - - name: Set publish flag based on the workflow trigger id: set_publish run: | @@ -140,7 +139,7 @@ jobs: release_macos_single_arch: name: Build Release (macOS ${{ matrix.dmg_name_suffix }}) - runs-on: macos-26-xlarge + runs-on: ${{ matrix.runner }} needs: prepare_release if: ${{ inputs.build_macos != false }} timeout-minutes: 60 @@ -150,8 +149,10 @@ jobs: include: - arch: aarch64 dmg_name_suffix: arm64 + runner: macos-26-xlarge - arch: x86_64 dmg_name_suffix: x86_64 + runner: macos-26-large steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -200,6 +201,13 @@ jobs: WARP_NOTARIZATION_APPLE_ID: ${{ secrets.WARP_NOTARIZATION_APPLE_ID }} WARP_NOTARIZATION_PASSWORD: ${{ secrets.WARP_NOTARIZATION_PASSWORD }} + - name: Archive ${{ matrix.arch }} macOS settings schema + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: settings-schema-macos-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + retention-days: 1 + path: ${{ steps.bundle_app.outputs.settings_schema_path }} + - name: Add DMG to GitHub release assets if: ${{ needs.prepare_release.outputs.should_publish == 'true' }} uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 @@ -314,6 +322,22 @@ jobs: run: tar xvfz build-artifacts.tar.gz shell: bash + - name: Download canonical aarch64 macOS settings schema + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: settings-schema-macos-aarch64-${{ steps.get-config.outputs.channel }} + path: .release-artifacts/settings-schema-macos + + - name: Download x86_64 macOS settings schema + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: settings-schema-macos-x86_64-${{ steps.get-config.outputs.channel }} + path: .release-artifacts/settings-schema-macos-x86_64 + + - name: Verify macOS settings schemas match + run: cmp .release-artifacts/settings-schema-macos/settings_schema.json .release-artifacts/settings-schema-macos-x86_64/settings_schema.json + shell: bash + - name: Install cargo-bundle run: script/install_cargo_bundle @@ -333,6 +357,7 @@ jobs: WARP_CODESIGN_KEYCHAIN_PASSWORD: ${{ secrets.WARP_CODESIGN_KEYCHAIN_PASSWORD }} WARP_NOTARIZATION_APPLE_ID: ${{ secrets.WARP_NOTARIZATION_APPLE_ID }} WARP_NOTARIZATION_PASSWORD: ${{ secrets.WARP_NOTARIZATION_PASSWORD }} + SETTINGS_SCHEMA_SOURCE: ${{ github.workspace }}/.release-artifacts/settings-schema-macos/settings_schema.json - name: Set up Sentry CLI if: ${{ needs.prepare_release.outputs.should_publish == 'true' }} @@ -410,7 +435,7 @@ jobs: release_macos_cli: name: Build Release (macOS CLI ${{ matrix.arch }}) runs-on: macos-26-xlarge - needs: prepare_release + needs: [prepare_release, release_macos_single_arch] if: ${{ inputs.build_macos != false }} timeout-minutes: 60 strategy: @@ -447,6 +472,12 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} + - name: Download canonical macOS settings schema + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: settings-schema-macos-aarch64-${{ steps.get-config.outputs.channel }} + path: .release-artifacts/settings-schema-macos + - name: Build ${{ matrix.arch }} binary id: bundle_cli run: | @@ -460,6 +491,7 @@ jobs: WARP_CODESIGN_KEYCHAIN_PASSWORD: ${{ secrets.WARP_CODESIGN_KEYCHAIN_PASSWORD }} WARP_NOTARIZATION_APPLE_ID: ${{ secrets.WARP_NOTARIZATION_APPLE_ID }} WARP_NOTARIZATION_PASSWORD: ${{ secrets.WARP_NOTARIZATION_PASSWORD }} + SETTINGS_SCHEMA_SOURCE: ${{ github.workspace }}/.release-artifacts/settings-schema-macos/settings_schema.json - name: Package CLI binary run: | @@ -514,7 +546,7 @@ jobs: release_macos_tui: name: Build Release (macOS TUI ${{ matrix.arch }}) runs-on: macos-26-xlarge - needs: prepare_release + needs: [prepare_release, release_macos_single_arch] if: ${{ inputs.build_macos != false }} timeout-minutes: 60 strategy: @@ -551,6 +583,12 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} + - name: Download canonical macOS settings schema + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: settings-schema-macos-aarch64-${{ steps.get-config.outputs.channel }} + path: .release-artifacts/settings-schema-macos + - name: Build ${{ matrix.arch }} binary id: bundle_tui run: | @@ -564,6 +602,7 @@ jobs: WARP_CODESIGN_KEYCHAIN_PASSWORD: ${{ secrets.WARP_CODESIGN_KEYCHAIN_PASSWORD }} WARP_NOTARIZATION_APPLE_ID: ${{ secrets.WARP_NOTARIZATION_APPLE_ID }} WARP_NOTARIZATION_PASSWORD: ${{ secrets.WARP_NOTARIZATION_PASSWORD }} + SETTINGS_SCHEMA_SOURCE: ${{ github.workspace }}/.release-artifacts/settings-schema-macos/settings_schema.json - name: Set up Sentry CLI if: ${{ needs.prepare_release.outputs.should_publish == 'true' }} @@ -629,7 +668,7 @@ jobs: release_linux_tui: name: Build Release (Linux TUI ${{ matrix.arch }}) runs-on: ${{ matrix.runner }} - needs: prepare_release + needs: [prepare_release, release_linux_x86] if: ${{ inputs.build_linux != false }} timeout-minutes: 180 strategy: @@ -668,6 +707,12 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} + - name: Download canonical Linux settings schema + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: settings-schema-linux-${{ steps.get-config.outputs.channel }} + path: .release-artifacts/settings-schema-linux + # Namespace's User Bundled Cache persists target/ between jobs on the same profile. - name: Clean stale bundle output run: | @@ -683,6 +728,7 @@ jobs: env: CHANNEL: ${{ steps.get-config.outputs.channel }} GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + SETTINGS_SCHEMA_SOURCE: ${{ github.workspace }}/.release-artifacts/settings-schema-linux/settings_schema.json - name: Package and validate TUI tarball run: | @@ -746,9 +792,6 @@ jobs: # them with FUSE, which isn't available on GitHub runners (and this is # easier and less error-prone than trying to install it). APPIMAGE_EXTRACT_AND_RUN: "1" - # Cache the generated settings schema so prepare_bundled_resources only - # compiles and runs the generator once per job instead of per-package. - SETTINGS_SCHEMA_CACHE: ${{ github.workspace }}/.settings_schema_cache.json steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -835,12 +878,20 @@ jobs: CHANNEL: ${{ steps.get-config.outputs.channel }} GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + - name: Archive canonical Linux settings schema + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: settings-schema-linux-${{ steps.get-config.outputs.channel }} + retention-days: 1 + path: ${{ steps.bundle_app.outputs.settings_schema_path }} + - name: Bundle Arch Linux package uses: ./.github/actions/bundle_arch_package with: channel: ${{ steps.get-config.outputs.channel }} release-tag: ${{ needs.prepare_release.outputs.release_tag }} arch: x86_64 + settings-schema-source: ${{ steps.bundle_app.outputs.settings_schema_path }} - name: Sign Arch Linux packages run: | @@ -899,13 +950,9 @@ jobs: release_linux_cli_x86: name: Build Release (Linux CLI x86_64) runs-on: namespace-profile-ubuntu-22-04 - needs: prepare_release + needs: [prepare_release, release_linux_x86] if: ${{ inputs.build_linux != false }} timeout-minutes: 90 - env: - # Cache the generated settings schema so prepare_bundled_resources only - # compiles and runs the generator once per job instead of per-package. - SETTINGS_SCHEMA_CACHE: ${{ github.workspace }}/.settings_schema_cache.json steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -978,6 +1025,12 @@ jobs: fi shell: bash + - name: Download canonical Linux settings schema + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: settings-schema-linux-${{ steps.get-config.outputs.channel }} + path: .release-artifacts/settings-schema-linux + - name: Bundle CLI id: bundle_cli run: | @@ -987,6 +1040,7 @@ jobs: env: CHANNEL: ${{ steps.get-config.outputs.channel }} GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + SETTINGS_SCHEMA_SOURCE: ${{ github.workspace }}/.release-artifacts/settings-schema-linux/settings_schema.json - name: Package CLI tar.gz run: | @@ -1000,6 +1054,7 @@ jobs: release-tag: ${{ needs.prepare_release.outputs.release_tag }} arch: x86_64 artifact: cli + settings-schema-source: .release-artifacts/settings-schema-linux/settings_schema.json - name: Sign Arch Linux packages run: | @@ -1085,9 +1140,6 @@ jobs: # them with FUSE, which isn't available on GitHub runners (and this is # easier and less error-prone than trying to install it). APPIMAGE_EXTRACT_AND_RUN: "1" - # Cache the generated settings schema so prepare_bundled_resources only - # compiles and runs the generator once per job instead of per-package. - SETTINGS_SCHEMA_CACHE: ${{ github.workspace }}/.settings_schema_cache.json steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -1157,12 +1209,14 @@ jobs: EXECUTABLE_PATH="$(realpath --relative-to=$(pwd) $EXECUTABLE_PATH)" DEBUG_EXECUTABLE_PATH="$(realpath --relative-to=$(pwd) $DEBUG_EXECUTABLE_PATH)" PACKAGES_DIR="$(realpath --relative-to=$(pwd) $PACKAGES_DIR)" - tar czvf binaries.tar.gz $EXECUTABLE_PATH $DEBUG_EXECUTABLE_PATH $PACKAGES_DIR + cp "$SETTINGS_SCHEMA_PATH" settings_schema.json + tar czvf binaries.tar.gz $EXECUTABLE_PATH $DEBUG_EXECUTABLE_PATH $PACKAGES_DIR settings_schema.json shell: bash env: EXECUTABLE_PATH: ${{ steps.build_app.outputs.executable_path }} DEBUG_EXECUTABLE_PATH: ${{ steps.build_app.outputs.debug_executable_path }} PACKAGES_DIR: ${{steps.build_app.outputs.packages_dir}} + SETTINGS_SCHEMA_PATH: ${{ steps.build_app.outputs.settings_schema_path }} - name: Archive build files uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -1251,13 +1305,9 @@ jobs: release_linux_arm: name: Bundle Release (Linux ARM) runs-on: namespace-profile-ubuntu-20-04 - needs: [ prepare_release, build_linux_arm_binaries, build_linux_cli_arm_binaries ] + needs: [ prepare_release, release_linux_x86, build_linux_arm_binaries, build_linux_cli_arm_binaries ] if: ${{ inputs.build_linux != false }} timeout-minutes: 60 - env: - # Cache the generated settings schema so prepare_bundled_resources only - # compiles and runs the generator once per job instead of per-package. - SETTINGS_SCHEMA_CACHE: ${{ github.workspace }}/.settings_schema_cache.json steps: - name: Checkout sources uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -1271,6 +1321,12 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} + - name: Download canonical Linux settings schema + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: settings-schema-linux-${{ steps.get-config.outputs.channel }} + path: .release-artifacts/settings-schema-linux + - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 with: credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} @@ -1293,12 +1349,8 @@ jobs: gcc --version g++ --version - # Install Linux build dependencies (protoc, libssl-dev, etc.). Although this job - # runs with --skip-build, script/prepare_bundled_resources still invokes - # `cargo run --bin generate_settings_schema` and `cargo about generate`, which - # compile workspace crates whose build scripts depend on protoc and other - # system libraries. The four sibling Linux jobs get these via prepare_environment; - # this job doesn't use prepare_environment, so install them explicitly here. + # Install Linux build dependencies required by cargo-about while preparing + # the bundled third-party license resources. - name: Install Linux build dependencies shell: bash run: ./script/linux/install_build_deps @@ -1359,6 +1411,10 @@ jobs: tar xzvf binaries.tar.gz shell: bash + - name: Verify Linux settings schemas match + run: cmp .release-artifacts/settings-schema-linux/settings_schema.json settings_schema.json + shell: bash + - name: Bundle deb/rpm app packages id: bundle_app run: | @@ -1369,6 +1425,7 @@ jobs: env: CHANNEL: ${{ steps.get-config.outputs.channel }} GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + SETTINGS_SCHEMA_SOURCE: ${{ github.workspace }}/.release-artifacts/settings-schema-linux/settings_schema.json - name: Bundle Arch Linux app package uses: ./.github/actions/bundle_arch_package @@ -1376,6 +1433,7 @@ jobs: channel: ${{ steps.get-config.outputs.channel }} release-tag: ${{ needs.prepare_release.outputs.release_tag }} arch: aarch64 + settings-schema-source: .release-artifacts/settings-schema-linux/settings_schema.json - name: Upload app debugging symbols to Sentry if: ${{ needs.prepare_release.outputs.should_publish == 'true' }} @@ -1407,6 +1465,7 @@ jobs: env: CHANNEL: ${{ steps.get-config.outputs.channel }} GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + SETTINGS_SCHEMA_SOURCE: ${{ github.workspace }}/.release-artifacts/settings-schema-linux/settings_schema.json - name: Package CLI tar.gz run: | @@ -1420,6 +1479,7 @@ jobs: release-tag: ${{ needs.prepare_release.outputs.release_tag }} arch: aarch64 artifact: cli + settings-schema-source: .release-artifacts/settings-schema-linux/settings_schema.json - name: Upload CLI debugging symbols to Sentry if: ${{ needs.prepare_release.outputs.should_publish == 'true' }} @@ -1626,7 +1686,7 @@ jobs: release_windows_tui: name: Build Release (Windows TUI ${{ matrix.arch }}) runs-on: windows-latest-large - needs: prepare_release + needs: [prepare_release, build_windows_binaries] if: ${{ inputs.build_windows != false }} timeout-minutes: 150 strategy: @@ -1664,6 +1724,12 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} + - name: Download canonical Windows settings schema + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: settings-schema-windows-${{ steps.get-config.outputs.channel }} + path: .release-artifacts/settings-schema-windows + - name: Test Windows TUI installer if: ${{ matrix.arch == 'x64' }} run: ./script/windows/test_tui_installer.ps1 @@ -1710,6 +1776,8 @@ jobs: AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} SIGN_TOOL_CMD: ${{ steps.setup_signing.outputs.sign_tool_cmd }} + SETTINGS_SCHEMA_SOURCE: ${{ github.workspace }}/.release-artifacts/settings-schema-windows/settings_schema.json + - name: Add TUI installer to GitHub release assets if: ${{ needs.prepare_release.outputs.should_publish == 'true' }} uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 @@ -1754,8 +1822,8 @@ jobs: ${{ steps.bundle_tui.outputs.pdb_file_path }} if-no-files-found: error - release_windows: - name: Build Release (Windows ${{ matrix.arch }}) + build_windows_binaries: + name: Build Release Binary (Windows ${{ matrix.arch }}) runs-on: ${{ matrix.runner }} needs: prepare_release if: ${{ inputs.build_windows != false }} @@ -1767,10 +1835,6 @@ jobs: runner: windows-latest-large - arch: arm64 runner: windows-latest-large - env: - TRUSTED_SIGNING_ENDPOINT: https://eus.codesigning.azure.net/ - TRUSTED_SIGNING_ACCOUNT: warpdotdev - TRUSTED_SIGNING_CERT_PROFILE: warpterminal steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -1803,6 +1867,92 @@ jobs: CHANNEL: ${{ steps.get-config.outputs.channel }} GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + - name: Generate canonical Windows settings schema + if: ${{ matrix.arch == 'x64' }} + shell: pwsh + run: | + $Process = Start-Process ` + -FilePath "${{ steps.build_binary.outputs.binary_path }}" ` + -ArgumentList @('dump-settings-schema', "${{ runner.temp }}\settings_schema.json") ` + -NoNewWindow ` + -PassThru ` + -Wait + if ($Process.ExitCode -ne 0) { throw "Failed to generate the Windows settings schema: exit code $($Process.ExitCode)." } + + - name: Archive canonical Windows settings schema + if: ${{ matrix.arch == 'x64' }} + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: settings-schema-windows-${{ steps.get-config.outputs.channel }} + retention-days: 1 + path: ${{ runner.temp }}\settings_schema.json + + - name: Archive Windows binary build + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: windows-binary-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + retention-days: 1 + path: | + ${{ steps.build_binary.outputs.binary_path }} + ${{ steps.build_binary.outputs.pdb_file_path }} + app/assets/windows/${{ matrix.arch }} + + release_windows: + name: Bundle Release (Windows ${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + needs: [prepare_release, build_windows_binaries] + if: ${{ inputs.build_windows != false }} + timeout-minutes: 150 + strategy: + matrix: + include: + - arch: x64 + runner: windows-latest-large + - arch: arm64 + runner: windows-latest-large + env: + TRUSTED_SIGNING_ENDPOINT: https://eus.codesigning.azure.net/ + TRUSTED_SIGNING_ACCOUNT: warpdotdev + TRUSTED_SIGNING_CERT_PROFILE: warpterminal + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - uses: ./.github/actions/prepare_environment + with: + target_os: windows + cache_key: ${{ matrix.arch }} + is_self_hosted: false + ref: ${{ needs.prepare_release.outputs.release_branch }} + install_release_deps: true + ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} + + - name: Get channel configuration + id: get-config + uses: ./.github/actions/get_channel_config/ + with: + config_file: ${{ env.CONFIG_FILE }} + channel: ${{ inputs.channel }} + + - name: Download Windows binary build + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: windows-binary-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + path: . + + - name: Download canonical Windows settings schema + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: settings-schema-windows-${{ steps.get-config.outputs.channel }} + path: .release-artifacts/settings-schema-windows + + - name: Locate binary build outputs + id: binary_paths + run: script/bundle -Channel $CHANNEL -skip_build_binary -skip_build_installer --arch ${{ matrix.arch }} + shell: bash + env: + CHANNEL: ${{ steps.get-config.outputs.channel }} + GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + - name: Sign the executables with Azure Trusted Signing uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1.2.0 with: @@ -1813,7 +1963,7 @@ jobs: trusted-signing-account-name: ${{ env.TRUSTED_SIGNING_ACCOUNT }} certificate-profile-name: ${{ env.TRUSTED_SIGNING_CERT_PROFILE }} files: | - ${{ steps.build_binary.outputs.binary_path }} + ${{ steps.binary_paths.outputs.binary_path }} ${{ github.workspace }}\app\assets\bundled\bootstrap\pwsh.ps1 files-folder: ${{ github.workspace }}\app\assets\windows\${{ matrix.arch }} files-folder-filter: dll,exe @@ -1844,6 +1994,7 @@ jobs: AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} SIGN_TOOL_CMD: ${{ steps.setup_signing.outputs.sign_tool_cmd }} + SETTINGS_SCHEMA_SOURCE: ${{ github.workspace }}/.release-artifacts/settings-schema-windows/settings_schema.json - name: Set up Sentry CLI if: ${{ needs.prepare_release.outputs.should_publish == 'true' }} @@ -1859,7 +2010,7 @@ jobs: script/sentry_upload_dif.sh shell: bash env: - DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.build_binary.outputs.binary_path }} + DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.binary_paths.outputs.binary_path }} - name: Upload debugging symbols to Sentry if: ${{ needs.prepare_release.outputs.should_publish == 'true' }} diff --git a/app/Cargo.toml b/app/Cargo.toml index 7dfd14d8806..f032f59589d 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -48,10 +48,6 @@ path = "src/bin/preview.rs" required-features = ["preview_channel"] test = false -[[bin]] -name = "generate_settings_schema" -path = "src/bin/generate_settings_schema.rs" -test = false [dependencies] addr = "0.15.6" diff --git a/app/src/bin/generate_settings_schema.rs b/app/src/bin/generate_settings_schema.rs deleted file mode 100644 index 969da71722e..00000000000 --- a/app/src/bin/generate_settings_schema.rs +++ /dev/null @@ -1,284 +0,0 @@ -//! Generates a JSON Schema file describing Warp's user-facing settings. -//! -//! Usage: -//! ``` -//! cargo run --bin generate_settings_schema -- [--channel dev|preview|stable] [output_path] -//! ``` - -use std::collections::HashSet; -use std::io::Write; - -use schemars::SchemaGenerator; -use serde_json::{Map, Value}; -use settings::schema::SettingSchemaEntry; -use settings::{SettingSurfaces, SettingsMode}; -use warp_core::features::{DEBUG_FLAGS, DOGFOOD_FLAGS, FeatureFlag, PREVIEW_FLAGS, RELEASE_FLAGS}; - -/// Ensures all `inventory::submit!` registrations from the app crate's -/// dependency tree are linked into the binary. -/// -/// Binary targets only link crate code that is transitively referenced. -/// Without an explicit reference to the `warp` library, the linker will -/// not include most of the app's object files and the `inventory` -/// submissions they contain. -fn ensure_settings_linked() { - let _ = std::hint::black_box(warp::settings::RESTORE_SESSION); -} - -/// Recursively strips `minimum`, `maximum`, and `format` from integer and -/// number schemas. schemars derives these from Rust type bounds (e.g. `u8` -/// → `minimum: 0, maximum: 255, format: "uint8"`), which are misleading -/// for settings whose valid domain is narrower than the type allows. -fn strip_numeric_metadata(value: &mut Value) { - match value { - Value::Object(map) => { - let is_numeric = map - .get("type") - .and_then(Value::as_str) - .is_some_and(|t| t == "integer" || t == "number"); - - if is_numeric { - map.remove("minimum"); - map.remove("maximum"); - map.remove("format"); - } - - for val in map.values_mut() { - strip_numeric_metadata(val); - } - } - Value::Array(arr) => { - for val in arr { - strip_numeric_metadata(val); - } - } - _ => {} - } -} - -/// Removes `{"enum": [], "type": "string"}` entries from `oneOf` arrays. -/// schemars emits an empty enum bucket for externally-tagged enums when all -/// unit variants have individual descriptions (and are therefore promoted to -/// separate `oneOf` branches with `const`). The empty bucket is unreachable -/// and confuses schema consumers. -fn strip_empty_enum_entries(value: &mut Value) { - match value { - Value::Object(map) => { - if let Some(Value::Array(one_of)) = map.get_mut("oneOf") { - one_of.retain(|entry| { - !matches!(entry, Value::Object(obj) - if obj.get("enum").is_some_and(|e| e.as_array().is_some_and(|a| a.is_empty())) - ) - }); - } - - for val in map.values_mut() { - strip_empty_enum_entries(val); - } - } - Value::Array(arr) => { - for val in arr { - strip_empty_enum_entries(val); - } - } - _ => {} - } -} - -fn active_flags_for_channel(channel: &str) -> HashSet { - let mut flags = HashSet::new(); - - let flag_lists: &[&[FeatureFlag]] = match channel { - "stable" => &[RELEASE_FLAGS], - "preview" => &[RELEASE_FLAGS, PREVIEW_FLAGS], - "dev" => &[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS], - other => { - eprintln!("Unknown channel '{other}', defaulting to dev"); - &[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS] - } - }; - - for list in flag_lists { - for flag in *list { - flags.insert(*flag); - } - } - - flags -} - -/// Creates intermediate hierarchy objects so that a setting at e.g. -/// `appearance.text` is nested under `properties.appearance.properties.text.properties`. -fn ensure_hierarchy<'a>( - root_properties: &'a mut Map, - hierarchy: &str, -) -> &'a mut Map { - let segments: Vec<&str> = hierarchy.split('.').collect(); - let mut current = root_properties; - - for segment in segments { - // Ensure the segment object exists - let entry = current.entry(segment.to_string()).or_insert_with(|| { - Value::Object({ - let mut m = Map::new(); - m.insert("type".to_string(), Value::String("object".to_string())); - m.insert("properties".to_string(), Value::Object(Map::new())); - m - }) - }); - - // Navigate into its properties - current = entry - .as_object_mut() - .expect("hierarchy node should be an object") - .entry("properties") - .or_insert_with(|| Value::Object(Map::new())) - .as_object_mut() - .expect("properties should be an object"); - } - - current -} - -fn setting_surface_names(surfaces: SettingSurfaces) -> Vec { - [(SettingsMode::Gui, "gui"), (SettingsMode::Tui, "tui")] - .into_iter() - .filter(|(mode, _)| surfaces.includes(*mode)) - .map(|(_, name)| Value::String(name.to_owned())) - .collect() -} -fn main() { - ensure_settings_linked(); - - let args: Vec = std::env::args().collect(); - - let mut channel = "dev"; - let mut output_path: Option<&str> = None; - let mut i = 1; - while i < args.len() { - match args[i].as_str() { - "--channel" => { - i += 1; - if i < args.len() { - channel = &args[i]; - } - } - arg if !arg.starts_with('-') => { - output_path = Some(arg); - } - other => { - eprintln!("Unknown argument: {other}"); - std::process::exit(1); - } - } - i += 1; - } - - let active_flags = active_flags_for_channel(channel); - let mut generator = SchemaGenerator::default(); - let mut root_properties = Map::new(); - let mut entry_count = 0; - - for entry in inventory::iter:: { - // Skip private settings - if entry.is_private { - continue; - } - - // Skip settings whose feature flag is not active - if let Some(flag) = entry.feature_flag - && !active_flags.contains(&flag) - { - continue; - } - - let type_schema = (entry.schema_fn)(&mut generator); - - let mut schema_value: Value = type_schema.to_value(); - schema_value - .as_object_mut() - .expect("setting schema should be an object") - .insert( - "x-warp-surfaces".to_string(), - Value::Array(setting_surface_names((entry.surfaces_fn)())), - ); - - // Compute default value — prefer file default over serde default - let default_json = (entry.file_default_value_fn)(); - - if let Ok(default_value) = serde_json::from_str::(&default_json) - && let Some(obj) = schema_value.as_object_mut() - { - obj.insert("default".to_string(), default_value); - } - - // Always overwrite description with the macro-provided one - if !entry.description.is_empty() - && let Some(obj) = schema_value.as_object_mut() - { - obj.insert( - "description".to_string(), - Value::String(entry.description.to_string()), - ); - } - - // Place the setting in the hierarchy - let target = if let Some(hierarchy) = entry.hierarchy { - ensure_hierarchy(&mut root_properties, hierarchy) - } else { - &mut root_properties - }; - - target.insert(entry.storage_key.to_string(), schema_value); - entry_count += 1; - } - - // Collect $defs from the generator - let defs_map = generator.take_definitions(true); - - // Assemble the root document - let mut root = Map::new(); - root.insert( - "$schema".to_string(), - Value::String("https://json-schema.org/draft/2020-12/schema".to_string()), - ); - root.insert( - "title".to_string(), - Value::String("Warp Settings".to_string()), - ); - root.insert( - "description".to_string(), - Value::String(format!( - "JSON Schema for Warp settings ({channel} channel, {entry_count} settings)" - )), - ); - root.insert("type".to_string(), Value::String("object".to_string())); - root.insert("properties".to_string(), Value::Object(root_properties)); - - if !defs_map.is_empty() { - root.insert("$defs".to_string(), Value::Object(defs_map)); - } - - // Strip type-derived numeric metadata (minimum, maximum, format) that - // schemars emits from Rust primitive bounds (e.g. u8 → max 255). - // These leak implementation details rather than semantic constraints. - let mut root_value = Value::Object(root); - strip_numeric_metadata(&mut root_value); - strip_empty_enum_entries(&mut root_value); - - let output = serde_json::to_string_pretty(&root_value).expect("schema should serialize"); - - if let Some(path) = output_path { - let mut file = std::fs::File::create(path) - .unwrap_or_else(|e| panic!("Failed to create output file '{path}': {e}")); - file.write_all(output.as_bytes()) - .unwrap_or_else(|e| panic!("Failed to write to '{path}': {e}")); - eprintln!("Wrote {entry_count} settings to {path}"); - } else { - println!("{output}"); - } -} - -#[cfg(test)] -#[path = "generate_settings_schema_tests.rs"] -mod tests; diff --git a/app/src/bin/generate_settings_schema_tests.rs b/app/src/bin/generate_settings_schema_tests.rs deleted file mode 100644 index 7c4b069cf32..00000000000 --- a/app/src/bin/generate_settings_schema_tests.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::*; - -#[test] -fn surface_annotation_matches_setting_schema_entry_metadata() { - ensure_settings_linked(); - - for entry in inventory::iter:: { - let surfaces = (entry.surfaces_fn)(); - let annotation = setting_surface_names(surfaces); - let annotation_names: HashSet<&str> = annotation.iter().filter_map(Value::as_str).collect(); - - assert_eq!( - annotation_names.contains("gui"), - surfaces.includes(SettingsMode::Gui), - "GUI surface mismatch for {}", - entry.storage_key - ); - assert_eq!( - annotation_names.contains("tui"), - surfaces.includes(SettingsMode::Tui), - "TUI surface mismatch for {}", - entry.storage_key - ); - assert_eq!( - annotation_names.len(), - usize::from(surfaces.includes(SettingsMode::Gui)) - + usize::from(surfaces.includes(SettingsMode::Tui)), - "unexpected surface annotation for {}", - entry.storage_key - ); - } -} diff --git a/app/src/lib.rs b/app/src/lib.rs index cabaaf71647..d049dcff98d 100644 --- a/app/src/lib.rs +++ b/app/src/lib.rs @@ -815,6 +815,10 @@ pub fn run() -> Result<()> { return debug_dump::run(); } #[cfg(not(target_family = "wasm"))] + warp_cli::Command::DumpSettingsSchema { output_path } => { + return settings::schema_generation::dump_settings_schema(output_path.as_deref()); + } + #[cfg(not(target_family = "wasm"))] warp_cli::Command::PrintTelemetryEvents => { return TelemetryEvent::print_telemetry_events_json(); } diff --git a/app/src/settings/mod.rs b/app/src/settings/mod.rs index 72609bbd507..2a79d1acf84 100644 --- a/app/src/settings/mod.rs +++ b/app/src/settings/mod.rs @@ -28,6 +28,8 @@ mod onboarding; mod pane; mod privacy; mod same_line_prompt_block; +#[cfg(not(target_family = "wasm"))] +pub(crate) mod schema_generation; mod scroll; mod select; mod shared_object_limit_banner; diff --git a/app/src/settings/schema_generation.rs b/app/src/settings/schema_generation.rs new file mode 100644 index 00000000000..b500562b5e1 --- /dev/null +++ b/app/src/settings/schema_generation.rs @@ -0,0 +1,221 @@ +use std::io::Write as _; +use std::path::Path; + +use anyhow::{Context as _, Result}; +use schemars::SchemaGenerator; +use serde_json::{Map, Value}; +use settings::schema::SettingSchemaEntry; +use settings::{SettingSurfaces, SettingsMode}; +use tempfile::NamedTempFile; +use warp_core::channel::ChannelState; +use warp_core::features::FeatureFlag; + +/// Writes the settings schema to a file or prints it to standard output. +pub(crate) fn dump_settings_schema(output_path: Option<&Path>) -> Result<()> { + let output = settings_schema_json(|flag| flag.is_enabled())?; + + if let Some(path) = output_path { + write_atomically(path, output.as_bytes())?; + eprintln!("Wrote settings schema to {}", path.display()); + } else { + println!("{output}"); + } + + Ok(()) +} + +fn settings_schema_json(is_flag_enabled: impl Fn(FeatureFlag) -> bool) -> Result { + let mut generator = SchemaGenerator::default(); + let mut root_properties = Map::new(); + let mut entry_count = 0; + + for entry in inventory::iter:: { + if entry.is_private { + continue; + } + + if entry + .feature_flag + .is_some_and(|flag| !is_flag_enabled(flag)) + { + continue; + } + + let type_schema = (entry.schema_fn)(&mut generator); + let mut schema_value = type_schema.to_value(); + schema_value + .as_object_mut() + .expect("setting schema should be an object") + .insert( + "x-warp-surfaces".to_string(), + Value::Array(setting_surface_names((entry.surfaces_fn)())), + ); + let default_json = (entry.file_default_value_fn)(); + + if let Ok(default_value) = serde_json::from_str::(&default_json) + && let Some(object) = schema_value.as_object_mut() + { + object.insert("default".to_string(), default_value); + } + + if !entry.description.is_empty() + && let Some(object) = schema_value.as_object_mut() + { + object.insert( + "description".to_string(), + Value::String(entry.description.to_string()), + ); + } + + let target = if let Some(hierarchy) = entry.hierarchy { + ensure_hierarchy(&mut root_properties, hierarchy) + } else { + &mut root_properties + }; + + target.insert(entry.storage_key.to_string(), schema_value); + entry_count += 1; + } + + let definitions = generator.take_definitions(true); + let mut root = Map::new(); + root.insert( + "$schema".to_string(), + Value::String("https://json-schema.org/draft/2020-12/schema".to_string()), + ); + root.insert( + "title".to_string(), + Value::String("Warp Settings".to_string()), + ); + root.insert( + "description".to_string(), + Value::String(format!( + "JSON Schema for Warp settings ({} channel, {entry_count} settings)", + ChannelState::channel() + )), + ); + root.insert("type".to_string(), Value::String("object".to_string())); + root.insert("properties".to_string(), Value::Object(root_properties)); + + if !definitions.is_empty() { + root.insert("$defs".to_string(), Value::Object(definitions)); + } + + let mut root_value = Value::Object(root); + strip_numeric_metadata(&mut root_value); + strip_empty_enum_entries(&mut root_value); + + serde_json::to_string_pretty(&root_value).context("settings schema should serialize") +} + +fn setting_surface_names(surfaces: SettingSurfaces) -> Vec { + [(SettingsMode::Gui, "gui"), (SettingsMode::Tui, "tui")] + .into_iter() + .filter(|(mode, _)| surfaces.includes(*mode)) + .map(|(_, name)| Value::String(name.to_owned())) + .collect() +} + +fn ensure_hierarchy<'a>( + root_properties: &'a mut Map, + hierarchy: &str, +) -> &'a mut Map { + let mut current = root_properties; + + for segment in hierarchy.split('.') { + let entry = current.entry(segment.to_string()).or_insert_with(|| { + Value::Object({ + let mut object = Map::new(); + object.insert("type".to_string(), Value::String("object".to_string())); + object.insert("properties".to_string(), Value::Object(Map::new())); + object + }) + }); + + current = entry + .as_object_mut() + .expect("hierarchy node should be an object") + .entry("properties") + .or_insert_with(|| Value::Object(Map::new())) + .as_object_mut() + .expect("properties should be an object"); + } + + current +} + +fn strip_numeric_metadata(value: &mut Value) { + match value { + Value::Object(map) => { + let is_numeric = map + .get("type") + .and_then(Value::as_str) + .is_some_and(|value_type| value_type == "integer" || value_type == "number"); + + if is_numeric { + map.remove("minimum"); + map.remove("maximum"); + map.remove("format"); + } + + for value in map.values_mut() { + strip_numeric_metadata(value); + } + } + Value::Array(array) => { + for value in array { + strip_numeric_metadata(value); + } + } + _ => {} + } +} + +fn strip_empty_enum_entries(value: &mut Value) { + match value { + Value::Object(map) => { + if let Some(Value::Array(one_of)) = map.get_mut("oneOf") { + one_of.retain(|entry| { + !matches!(entry, Value::Object(object) + if object.get("enum").is_some_and(|value| value.as_array().is_some_and(Vec::is_empty)) + ) + }); + } + + for value in map.values_mut() { + strip_empty_enum_entries(value); + } + } + Value::Array(array) => { + for value in array { + strip_empty_enum_entries(value); + } + } + _ => {} + } +} + +fn write_atomically(path: &Path, contents: &[u8]) -> Result<()> { + let parent = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .unwrap_or_else(|| Path::new(".")); + std::fs::create_dir_all(parent) + .with_context(|| format!("failed to create {}", parent.display()))?; + + let mut temporary_file = NamedTempFile::new_in(parent) + .with_context(|| format!("failed to create a temporary file in {}", parent.display()))?; + temporary_file + .write_all(contents) + .with_context(|| format!("failed to write temporary schema for {}", path.display()))?; + temporary_file + .persist(path) + .map_err(|error| error.error) + .with_context(|| format!("failed to persist settings schema to {}", path.display()))?; + + Ok(()) +} + +#[cfg(test)] +#[path = "schema_generation_tests.rs"] +mod tests; diff --git a/app/src/settings/schema_generation_tests.rs b/app/src/settings/schema_generation_tests.rs new file mode 100644 index 00000000000..c49d19a8437 --- /dev/null +++ b/app/src/settings/schema_generation_tests.rs @@ -0,0 +1,107 @@ +use serde_json::json; +use settings::SettingsMode; +use settings::schema::SettingSchemaEntry; +use std::collections::HashSet; + +use super::{ + setting_surface_names, settings_schema_json, strip_empty_enum_entries, strip_numeric_metadata, +}; + +#[test] +fn strips_numeric_metadata_recursively() { + let mut schema = json!({ + "type": "object", + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "maximum": 255, + "format": "uint8" + } + } + }); + + strip_numeric_metadata(&mut schema); + + assert_eq!( + schema, + json!({ + "type": "object", + "properties": { + "count": { + "type": "integer" + } + } + }) + ); +} + +#[test] +fn strips_empty_enum_entries() { + let mut schema = json!({ + "oneOf": [ + { + "enum": [], + "type": "string" + }, + { + "const": "kept" + } + ] + }); + + strip_empty_enum_entries(&mut schema); + + assert_eq!( + schema, + json!({ + "oneOf": [ + { + "const": "kept" + } + ] + }) + ); +} + +#[test] +fn generates_a_settings_schema() { + let schema = settings_schema_json(|_| false).unwrap(); + let schema: serde_json::Value = serde_json::from_str(&schema).unwrap(); + + assert_eq!(schema["title"], "Warp Settings"); + assert_eq!(schema["type"], "object"); + assert!(schema["properties"].is_object()); +} + +#[test] +fn surface_annotation_matches_setting_schema_entry_metadata() { + for entry in inventory::iter:: { + let surfaces = (entry.surfaces_fn)(); + let annotation = setting_surface_names(surfaces); + let annotation_names: HashSet<&str> = annotation + .iter() + .filter_map(|value| value.as_str()) + .collect(); + + assert_eq!( + annotation_names.contains("gui"), + surfaces.includes(SettingsMode::Gui), + "GUI surface mismatch for {}", + entry.storage_key + ); + assert_eq!( + annotation_names.contains("tui"), + surfaces.includes(SettingsMode::Tui), + "TUI surface mismatch for {}", + entry.storage_key + ); + assert_eq!( + annotation_names.len(), + usize::from(surfaces.includes(SettingsMode::Gui)) + + usize::from(surfaces.includes(SettingsMode::Tui)), + "unexpected surface annotation for {}", + entry.storage_key + ); + } +} diff --git a/crates/warp_cli/src/lib.rs b/crates/warp_cli/src/lib.rs index af2c05ce31e..dfb1f54ad66 100644 --- a/crates/warp_cli/src/lib.rs +++ b/crates/warp_cli/src/lib.rs @@ -683,6 +683,12 @@ pub enum Command { /// Print debugging information and exit. #[clap(long_flag = "dump-debug-info")] DumpDebugInfo, + /// Print the JSON schema for the current Warp channel's settings and exit. + #[cfg(not(target_family = "wasm"))] + DumpSettingsSchema { + /// Write the schema to this path instead of standard output. + output_path: Option, + }, /// Print telemetry events in production and exit. #[clap(long_flag = "print-telemetry-events", hide = true)] @@ -698,6 +704,8 @@ impl Command { Command::CommandLine(_) | Command::DumpDebugInfo => true, Command::Completions { .. } => true, #[cfg(not(target_family = "wasm"))] + Command::DumpSettingsSchema { output_path } => output_path.is_none(), + #[cfg(not(target_family = "wasm"))] Command::PrintTelemetryEvents => true, } } diff --git a/flake.nix b/flake.nix index 2dc81648b58..59a41548793 100644 --- a/flake.nix +++ b/flake.nix @@ -155,8 +155,6 @@ "warp" "--bin" "warp-oss" - "--bin" - "generate_settings_schema" ]; inherit buildFeatures; @@ -175,7 +173,7 @@ let installDir = "$out/opt/warpdotdev/warp-terminal-experimental"; resourcesDir = "${installDir}/resources"; - releaseChannel = "stable"; + releaseChannel = "oss"; libraryPath = lib.makeLibraryPath linuxRuntimeLibraries; executablePath = lib.makeBinPath (with pkgs; [ xdg-utils ]); in @@ -187,16 +185,11 @@ ./script/prepare_bundled_resources \ ./script/copy_conditional_skills - SKIP_SETTINGS_SCHEMA=1 ./script/prepare_bundled_resources \ + SETTINGS_SCHEMA_EXECUTABLE="${installDir}/warp-oss" ./script/prepare_bundled_resources \ "${resourcesDir}" \ "${releaseChannel}" \ release - "$out/bin/generate_settings_schema" \ - --channel "${releaseChannel}" \ - "${resourcesDir}/settings_schema.json" - rm -f "$out/bin/generate_settings_schema" - install -Dm644 \ "${resourcesDir}/THIRD_PARTY_LICENSES.txt" \ "$out/share/licenses/warp-terminal-experimental/THIRD_PARTY_LICENSES.txt" diff --git a/script/deploy_remote_server b/script/deploy_remote_server index 0e7fd415cee..2d0f6543ba6 100755 --- a/script/deploy_remote_server +++ b/script/deploy_remote_server @@ -147,7 +147,7 @@ echo "==> Build complete ($BINARY_SIZE)" # Prepare the bundled resources tree (skills, settings schema) deployed to # the global, version-independent location the daemon reads. rm -rf "$LOCAL_RESOURCES_DIR" -"$WORKSPACE_ROOT/script/prepare_bundled_resources" \ +SKIP_SETTINGS_SCHEMA=1 "$WORKSPACE_ROOT/script/prepare_bundled_resources" \ "$LOCAL_RESOURCES_DIR" \ local \ "$CARGO_PROFILE" @@ -194,6 +194,9 @@ echo "==> Uploading bundled resources to $HOST:$REMOTE_ABS_DIR/$BUNDLED_RESOURCE rsync -z -rlt --delete --partial --progress \ "$LOCAL_RESOURCES_DIR/" \ "$HOST:$REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME/" +echo "==> Generating Linux settings schema on $HOST" +ssh "$HOST" \ + "$REMOTE_ABS_DIR/$BINARY_NAME dump-settings-schema $REMOTE_ABS_DIR/$BUNDLED_RESOURCES_DIR_NAME/settings_schema.json" echo "==> Stopping stale remote-server daemons on $HOST" ssh "$HOST" bash <<'EOF' diff --git a/script/deploy_remote_server_to_test_vm b/script/deploy_remote_server_to_test_vm index c938ebdb092..b529eba73c3 100755 --- a/script/deploy_remote_server_to_test_vm +++ b/script/deploy_remote_server_to_test_vm @@ -98,7 +98,7 @@ echo "==> Build complete ($BINARY_SIZE)" rm -rf "$LOCAL_BUNDLE_DIR" mkdir -p "$LOCAL_BUNDLE_DIR" cp "$BUILT_BINARY" "$LOCAL_BUNDLE_DIR/$BUNDLE_BINARY_NAME" -"$WORKSPACE_ROOT/script/prepare_bundled_resources" \ +SKIP_SETTINGS_SCHEMA=1 "$WORKSPACE_ROOT/script/prepare_bundled_resources" \ "$LOCAL_BUNDLE_DIR/resources" \ integration \ "$CARGO_PROFILE" @@ -197,6 +197,7 @@ mv resources bundled_resources chmod 755 "$3" mv -f "$3" "$4" +"./$4" dump-settings-schema bundled_resources/settings_schema.json "./$4" --version REMOTE_SCRIPT done diff --git a/script/linux/bundle b/script/linux/bundle index d9274ea7a3a..16da564d156 100755 --- a/script/linux/bundle +++ b/script/linux/bundle @@ -344,6 +344,31 @@ elif [[ "$ARTIFACT" == "tui" ]]; then BINARY_PATH="$OUT_DIR/$WARP_BIN" fi +if [[ "${SKIP_SETTINGS_SCHEMA:-}" != "1" && -z "${SETTINGS_SCHEMA_EXECUTABLE:-}" && -z "${SETTINGS_SCHEMA_SOURCE:-}" ]]; then + if [[ "$ARTIFACT" == "tui" ]]; then + echo "Error: TUI bundles require SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 + exit 1 + elif [[ "$BUILD" == "true" ]]; then + export SETTINGS_SCHEMA_EXECUTABLE="$EXECUTABLE_PATH" + else + echo "Error: --skip-build requires SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 + exit 1 + fi +fi + +if [[ "${SKIP_SETTINGS_SCHEMA:-}" != "1" && -n "${SETTINGS_SCHEMA_EXECUTABLE:-}" && -n "${SETTINGS_SCHEMA_SOURCE:-}" ]]; then + echo "Error: SETTINGS_SCHEMA_EXECUTABLE and SETTINGS_SCHEMA_SOURCE are mutually exclusive." >&2 + exit 1 +fi + +if [[ "${SKIP_SETTINGS_SCHEMA:-}" != "1" && -n "${SETTINGS_SCHEMA_EXECUTABLE:-}" ]]; then + GENERATED_SETTINGS_SCHEMA="$CARGO_TARGET_OUTPUT_DIR/settings_schema.json" + echo "Generating reusable settings schema at $GENERATED_SETTINGS_SCHEMA" + "$SETTINGS_SCHEMA_EXECUTABLE" dump-settings-schema "$GENERATED_SETTINGS_SCHEMA" + unset SETTINGS_SCHEMA_EXECUTABLE + export SETTINGS_SCHEMA_SOURCE="$GENERATED_SETTINGS_SCHEMA" +fi + # Prepare bundled resources for standalone builds. if [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" || "$ARTIFACT" == "tui" ]]; then echo "Preparing standalone resources directory" @@ -361,6 +386,7 @@ if [ "${GITHUB_ACTIONS}" == "true" ]; then echo "debug_executable_path=$DEBUG_EXECUTABLE_PATH" >> "$GITHUB_OUTPUT" echo "packages_dir=$OUT_DIR" >> "$GITHUB_OUTPUT" echo "bundled_resources_dir=${BUNDLED_RESOURCES_DIR:-}" >> "$GITHUB_OUTPUT" + echo "settings_schema_path=${SETTINGS_SCHEMA_SOURCE:-}" >> "$GITHUB_OUTPUT" echo "::echo::off" fi diff --git a/script/macos/bundle b/script/macos/bundle index 98ab3262052..246baa6f8db 100755 --- a/script/macos/bundle +++ b/script/macos/bundle @@ -662,6 +662,14 @@ if [[ "$ARTIFACT" == "app" ]]; then BUNDLED_RESOURCES_DIR="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/Resources" echo "Preparing bundled resources..." + if [[ "${SKIP_SETTINGS_SCHEMA:-}" != "1" && -z "${SETTINGS_SCHEMA_EXECUTABLE:-}" && -z "${SETTINGS_SCHEMA_SOURCE:-}" ]]; then + if [[ $BUILD_BINARY == true ]]; then + export SETTINGS_SCHEMA_EXECUTABLE="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/MacOS/$WARP_BIN" + else + echo "Error: --skip-build requires SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 + exit 1 + fi + fi "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" "$WORKSPACE_ROOT_DIR/script/compile_icon" "$RELEASE_CHANNEL" "$BUNDLE_DIR/$WARP_APP_NAME.app" @@ -762,6 +770,14 @@ elif [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then echo "Preparing CLI resources directory" BUNDLED_RESOURCES_DIR="$OUT_DIR/resources" + if [[ "${SKIP_SETTINGS_SCHEMA:-}" != "1" && -z "${SETTINGS_SCHEMA_EXECUTABLE:-}" && -z "${SETTINGS_SCHEMA_SOURCE:-}" ]]; then + if [[ $BUILD_BINARY == true ]]; then + export SETTINGS_SCHEMA_EXECUTABLE="$OUT_DIR/$WARP_BIN" + else + echo "Error: --skip-build requires SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 + exit 1 + fi + fi "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" @@ -792,6 +808,10 @@ elif [[ "$ARTIFACT" == "tui" ]]; then # Fonts are compiled into the binary, so they are intentionally absent here. echo "Preparing TUI resources directory" BUNDLED_RESOURCES_DIR="$OUT_DIR/resources" + if [[ "${SKIP_SETTINGS_SCHEMA:-}" != "1" && -z "${SETTINGS_SCHEMA_EXECUTABLE:-}" && -z "${SETTINGS_SCHEMA_SOURCE:-}" ]]; then + echo "Error: TUI bundles require SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 + exit 1 + fi "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" BINARY_PATH="$OUT_DIR/$WARP_BIN" @@ -1030,6 +1050,9 @@ if [ "${GITHUB_ACTIONS}" == "true" ]; then if [[ -n "$BUNDLED_RESOURCES_DIR" ]]; then echo "bundled_resources_dir=$BUNDLED_RESOURCES_DIR" >> "$GITHUB_OUTPUT" fi + if [[ -f "$BUNDLED_RESOURCES_DIR/settings_schema.json" ]]; then + echo "settings_schema_path=$BUNDLED_RESOURCES_DIR/settings_schema.json" >> "$GITHUB_OUTPUT" + fi echo "::echo::off" fi diff --git a/script/macos/run b/script/macos/run index f2172a53afc..8fb85216a4b 100755 --- a/script/macos/run +++ b/script/macos/run @@ -123,6 +123,8 @@ export WARP_PLIST_PATH="$WARP_APP_PATH/Contents/Info.plist" echo "Preparing bundled resources..." if [ "${GENERATE_SCHEMA:-false}" != "true" ]; then export SKIP_SETTINGS_SCHEMA=1 +else + export SETTINGS_SCHEMA_EXECUTABLE="$WARP_APP_PATH/Contents/MacOS/$WARP_BIN_NAME" fi NO_LICENSES=1 "${REPO_ROOT}/script/prepare_bundled_resources" "$WARP_APP_PATH/Contents/Resources" "$WARP_CHANNEL" diff --git a/script/prepare_bundled_resources b/script/prepare_bundled_resources index deb9d6979ed..e1951e0e2c0 100755 --- a/script/prepare_bundled_resources +++ b/script/prepare_bundled_resources @@ -14,20 +14,19 @@ # this path (e.g., $DEST_DIR/skills). # channel: (Optional) Release channel (local, dev, preview, # stable). Used to include channel-gated skills. -# cargo_profile: (Optional) Cargo build profile to use when -# generating the settings schema. Reusing the same -# profile as the main build avoids recompiling deps. +# cargo_profile: (Optional) Retained for compatibility with existing +# callers. # # Environment variables: # SKIP_SETTINGS_SCHEMA: Set to "1" to skip generating the JSON settings # schema. By default the schema is always generated # so that production bundles never accidentally omit it. -# SETTINGS_SCHEMA_CACHE: Path to a cache file for the generated schema. -# When set, the first invocation generates the schema -# and saves a copy to this path; subsequent invocations -# copy from the cache instead of regenerating. This -# avoids redundant compilations when bundling multiple -# package formats for the same channel. +# SETTINGS_SCHEMA_EXECUTABLE: Path to a runnable compiled Warp executable. +# The script invokes its dump-settings-schema +# subcommand to generate the schema. +# SETTINGS_SCHEMA_SOURCE: Path to an existing settings schema to copy. +# This is required when the compiled executable cannot +# run on the current host. set -e @@ -130,29 +129,31 @@ fi # Generate settings JSON schema unless explicitly skipped. if [ "${SKIP_SETTINGS_SCHEMA:-}" != "1" ]; then SCHEMA_OUTPUT="$DEST_DIR/settings_schema.json" - - if [ -n "${SETTINGS_SCHEMA_CACHE:-}" ] && [ -f "$SETTINGS_SCHEMA_CACHE" ]; then - echo "Copying cached settings schema to $SCHEMA_OUTPUT" - cp "$SETTINGS_SCHEMA_CACHE" "$SCHEMA_OUTPUT" - else - echo "Generating settings schema at $SCHEMA_OUTPUT" - - SCHEMA_CMD=(cargo run) - if [ -n "$CARGO_PROFILE" ]; then - SCHEMA_CMD+=(--profile "$CARGO_PROFILE") + if [ -n "${SETTINGS_SCHEMA_EXECUTABLE:-}" ] && [ -n "${SETTINGS_SCHEMA_SOURCE:-}" ]; then + echo "Error: SETTINGS_SCHEMA_EXECUTABLE and SETTINGS_SCHEMA_SOURCE are mutually exclusive." >&2 + exit 1 + elif [ -n "${SETTINGS_SCHEMA_EXECUTABLE:-}" ]; then + if [ ! -x "$SETTINGS_SCHEMA_EXECUTABLE" ]; then + echo "Error: Settings schema executable is not runnable: $SETTINGS_SCHEMA_EXECUTABLE" >&2 + exit 1 fi - SCHEMA_CMD+=(--manifest-path "$REPO_ROOT/Cargo.toml" --bin generate_settings_schema --) - if [ -n "$CHANNEL" ]; then - SCHEMA_CMD+=(--channel "$CHANNEL") + echo "Generating settings schema at $SCHEMA_OUTPUT" + "$SETTINGS_SCHEMA_EXECUTABLE" dump-settings-schema "$SCHEMA_OUTPUT" + elif [ -n "${SETTINGS_SCHEMA_SOURCE:-}" ]; then + if [ ! -f "$SETTINGS_SCHEMA_SOURCE" ]; then + echo "Error: Settings schema source does not exist: $SETTINGS_SCHEMA_SOURCE" >&2 + exit 1 fi - SCHEMA_CMD+=("$SCHEMA_OUTPUT") - - "${SCHEMA_CMD[@]}" + echo "Copying settings schema to $SCHEMA_OUTPUT" + cp "$SETTINGS_SCHEMA_SOURCE" "$SCHEMA_OUTPUT" + else + echo "Error: Set SETTINGS_SCHEMA_EXECUTABLE or SETTINGS_SCHEMA_SOURCE when preparing a settings schema." >&2 + exit 1 + fi - if [ -n "${SETTINGS_SCHEMA_CACHE:-}" ]; then - echo "Caching settings schema at $SETTINGS_SCHEMA_CACHE" - cp "$SCHEMA_OUTPUT" "$SETTINGS_SCHEMA_CACHE" - fi + if [ ! -s "$SCHEMA_OUTPUT" ]; then + echo "Error: Settings schema was not written to $SCHEMA_OUTPUT" >&2 + exit 1 fi fi diff --git a/script/test_prepare_bundled_resources b/script/test_prepare_bundled_resources new file mode 100755 index 00000000000..a02a1ad4839 --- /dev/null +++ b/script/test_prepare_bundled_resources @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +set -e + +WORKSPACE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TEMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TEMP_DIR"' EXIT + +FAKE_EXECUTABLE="$TEMP_DIR/warp" +cat > "$FAKE_EXECUTABLE" <<'EOF' +#!/usr/bin/env bash +if [[ "$1" != "dump-settings-schema" || -z "$2" ]]; then + exit 1 +fi +printf '{"source":"executable"}' > "$2" +EOF +chmod +x "$FAKE_EXECUTABLE" + +EXECUTABLE_DESTINATION="$TEMP_DIR/from-executable" +NO_LICENSES=1 \ + SETTINGS_SCHEMA_EXECUTABLE="$FAKE_EXECUTABLE" \ + "$WORKSPACE_ROOT/script/prepare_bundled_resources" "$EXECUTABLE_DESTINATION" dev +grep -qx '{"source":"executable"}' "$EXECUTABLE_DESTINATION/settings_schema.json" + +SOURCE_SCHEMA="$TEMP_DIR/source-schema.json" +printf '{"source":"file"}' > "$SOURCE_SCHEMA" +SOURCE_DESTINATION="$TEMP_DIR/from-source" +NO_LICENSES=1 \ + SETTINGS_SCHEMA_SOURCE="$SOURCE_SCHEMA" \ + "$WORKSPACE_ROOT/script/prepare_bundled_resources" "$SOURCE_DESTINATION" dev +cmp "$SOURCE_SCHEMA" "$SOURCE_DESTINATION/settings_schema.json" + +if NO_LICENSES=1 "$WORKSPACE_ROOT/script/prepare_bundled_resources" "$TEMP_DIR/missing-input" dev; then + echo "Expected resource preparation without a schema input to fail." >&2 + exit 1 +fi + +if NO_LICENSES=1 \ + SETTINGS_SCHEMA_EXECUTABLE="$FAKE_EXECUTABLE" \ + SETTINGS_SCHEMA_SOURCE="$SOURCE_SCHEMA" \ + "$WORKSPACE_ROOT/script/prepare_bundled_resources" "$TEMP_DIR/conflicting-inputs" dev; then + echo "Expected conflicting schema inputs to fail." >&2 + exit 1 +fi diff --git a/script/windows/bundle.ps1 b/script/windows/bundle.ps1 index ea0efe6ac50..3fd84be52b3 100644 --- a/script/windows/bundle.ps1 +++ b/script/windows/bundle.ps1 @@ -249,6 +249,16 @@ if ($SKIP_BUILD_INSTALLER) { Write-Output "Built for $ARCH with executable at $BINARY_PATH" # Prepare bundled resources +if ($env:SKIP_SETTINGS_SCHEMA -ne '1' -and -not $env:SETTINGS_SCHEMA_EXECUTABLE -and -not $env:SETTINGS_SCHEMA_SOURCE) { + if ($IS_TUI) { + Write-Error 'TUI bundles require SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE.' + exit 1 + } elseif ($SKIP_BUILD_BINARY) { + Write-Error '-skip_build_binary requires SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE.' + exit 1 + } + $env:SETTINGS_SCHEMA_EXECUTABLE = $BINARY_PATH +} $BUNDLED_RESOURCES_DIR = "$CARGO_TARGET_OUTPUT_DIR\resources" Write-Output 'Preparing bundled resources...' & "$WINDOWS_INSTALLER_DIR\prepare_bundled_resources.ps1" -DestinationDir "$BUNDLED_RESOURCES_DIR" -Channel "$CHANNEL" -CargoProfile "$CARGO_PROFILE" diff --git a/script/windows/prepare_bundled_resources.ps1 b/script/windows/prepare_bundled_resources.ps1 index 1ac5b9897af..058729dace7 100644 --- a/script/windows/prepare_bundled_resources.ps1 +++ b/script/windows/prepare_bundled_resources.ps1 @@ -162,21 +162,40 @@ foreach ($entry in $AdditionalLicenses) { # Generate settings JSON schema unless explicitly skipped. if ($env:SKIP_SETTINGS_SCHEMA -ne '1') { $SchemaOutput = Join-Path $DestinationDir 'settings_schema.json' - Write-Output "Generating settings schema at $SchemaOutput" - $SchemaCmd = @('run') - if ($CargoProfile) { - $SchemaCmd += @('--profile', $CargoProfile) - } - $SchemaCmd += @('--manifest-path', (Join-Path $RepoRoot 'Cargo.toml'), '--bin', 'generate_settings_schema', '--') - if ($Channel) { - $SchemaCmd += @('--channel', $Channel) + if ($env:SETTINGS_SCHEMA_EXECUTABLE -and $env:SETTINGS_SCHEMA_SOURCE) { + Write-Error 'SETTINGS_SCHEMA_EXECUTABLE and SETTINGS_SCHEMA_SOURCE are mutually exclusive.' + exit 1 + } elseif ($env:SETTINGS_SCHEMA_EXECUTABLE) { + if (-Not (Test-Path $env:SETTINGS_SCHEMA_EXECUTABLE -PathType Leaf)) { + Write-Error "Settings schema executable does not exist: $env:SETTINGS_SCHEMA_EXECUTABLE" + exit 1 + } + Write-Output "Generating settings schema at $SchemaOutput" + $Process = Start-Process ` + -FilePath $env:SETTINGS_SCHEMA_EXECUTABLE ` + -ArgumentList @('dump-settings-schema', $SchemaOutput) ` + -NoNewWindow ` + -PassThru ` + -Wait + if ($Process.ExitCode -ne 0) { + Write-Error "Failed to generate settings schema: executable exited with code $($Process.ExitCode)" + exit 1 + } + } elseif ($env:SETTINGS_SCHEMA_SOURCE) { + if (-Not (Test-Path $env:SETTINGS_SCHEMA_SOURCE -PathType Leaf)) { + Write-Error "Settings schema source does not exist: $env:SETTINGS_SCHEMA_SOURCE" + exit 1 + } + Write-Output "Copying settings schema to $SchemaOutput" + Copy-Item -Path $env:SETTINGS_SCHEMA_SOURCE -Destination $SchemaOutput -Force + } else { + Write-Error 'Set SETTINGS_SCHEMA_EXECUTABLE or SETTINGS_SCHEMA_SOURCE when preparing a settings schema.' + exit 1 } - $SchemaCmd += $SchemaOutput - & cargo @SchemaCmd - if (-Not $?) { - Write-Error 'Failed to generate settings schema' + if (-Not (Test-Path $SchemaOutput -PathType Leaf) -or (Get-Item $SchemaOutput).Length -eq 0) { + Write-Error "Settings schema was not written to $SchemaOutput" exit 1 } } From e28967103384c627efcf3a87f9b636c4c9acd37a Mon Sep 17 00:00:00 2001 From: David Stern Date: Fri, 21 Aug 2026 15:23:52 -0400 Subject: [PATCH 02/16] formatting Co-Authored-By: Warp --- app/src/settings/schema_generation_tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/settings/schema_generation_tests.rs b/app/src/settings/schema_generation_tests.rs index c49d19a8437..059c4eb09e5 100644 --- a/app/src/settings/schema_generation_tests.rs +++ b/app/src/settings/schema_generation_tests.rs @@ -1,7 +1,8 @@ +use std::collections::HashSet; + use serde_json::json; use settings::SettingsMode; use settings::schema::SettingSchemaEntry; -use std::collections::HashSet; use super::{ setting_surface_names, settings_schema_json, strip_empty_enum_entries, strip_numeric_metadata, From 4b126de3e07c6d0e03994db86bb2a540a3afa11b Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Fri, 21 Aug 2026 22:35:34 +0000 Subject: [PATCH 03/16] Fix run-tui settings schema regression, document macOS schema caveat, and fix Cargo.toml spacing - script/run-tui: skip settings schema generation for local TUI runs, since the TUI binaries don't implement dump-settings-schema (only the full app binary does) and packaged TUI builds consume a schema generated from that binary during release instead. This fixes a regression where every local ./script/run-tui invocation failed with "Set SETTINGS_SCHEMA_EXECUTABLE or SETTINGS_SCHEMA_SOURCE" after prepare_bundled_resources started failing closed. - script/test_prepare_bundled_resources: add coverage for the SKIP_SETTINGS_SCHEMA=1 path that script/run-tui now relies on. - .github/workflows/create_release.yml: document why the macOS aarch64/x86_64 settings schema comparison expects byte-identical output. - app/Cargo.toml: collapse the double blank line left behind by removing the generate_settings_schema [[bin]] block. --- .github/workflows/create_release.yml | 3 +++ app/Cargo.toml | 1 - script/run-tui | 12 ++++++------ script/test_prepare_bundled_resources | 11 +++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 88ca9378000..dc6e8121856 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -334,6 +334,9 @@ jobs: name: settings-schema-macos-x86_64-${{ steps.get-config.outputs.channel }} path: .release-artifacts/settings-schema-macos-x86_64 + # These two schemas are expected to be byte-identical: no setting is currently scoped to + # a specific macOS architecture. A mismatch here means that assumption no longer holds, + # and this step needs to become architecture-aware rather than simply failing the release. - name: Verify macOS settings schemas match run: cmp .release-artifacts/settings-schema-macos/settings_schema.json .release-artifacts/settings-schema-macos-x86_64/settings_schema.json shell: bash diff --git a/app/Cargo.toml b/app/Cargo.toml index f032f59589d..dd270cc7eba 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -48,7 +48,6 @@ path = "src/bin/preview.rs" required-features = ["preview_channel"] test = false - [dependencies] addr = "0.15.6" ai.workspace = true diff --git a/script/run-tui b/script/run-tui index f6bedf28346..84f961a4f99 100755 --- a/script/run-tui +++ b/script/run-tui @@ -110,17 +110,17 @@ if [ -n "${TARGET_TRIPLE}" ]; then fi BUILD_DIR="${BUILD_DIR}/${PROFILE_DIR}" -# Stage bundled skills and the settings schema next to the binary so the -# standalone TUI can resolve them at runtime. License generation remains skipped -# for local runs. Reuse the selected Cargo profile for schema generation so its -# dependencies do not need to be rebuilt in a separate profile. +# Stage bundled skills next to the binary so the standalone TUI can resolve them at runtime. +# License generation remains skipped for local runs, and so is settings schema generation: the +# TUI binaries don't implement `dump-settings-schema` (only the full app binary does), and +# packaged TUI builds consume a schema generated from that binary during release instead. prepare_tui_bundled_resources() { echo "Preparing bundled resources at ${BUILD_DIR}/resources (channel: ${CHANNEL})..." if [ "${PROFILE_DIR}" = "debug" ]; then - NO_LICENSES=1 "${REPO_ROOT}/script/prepare_bundled_resources" \ + NO_LICENSES=1 SKIP_SETTINGS_SCHEMA=1 "${REPO_ROOT}/script/prepare_bundled_resources" \ "${BUILD_DIR}/resources" "${CHANNEL}" else - NO_LICENSES=1 "${REPO_ROOT}/script/prepare_bundled_resources" \ + NO_LICENSES=1 SKIP_SETTINGS_SCHEMA=1 "${REPO_ROOT}/script/prepare_bundled_resources" \ "${BUILD_DIR}/resources" "${CHANNEL}" "${PROFILE_DIR}" fi } diff --git a/script/test_prepare_bundled_resources b/script/test_prepare_bundled_resources index a02a1ad4839..dcda01d257c 100755 --- a/script/test_prepare_bundled_resources +++ b/script/test_prepare_bundled_resources @@ -42,3 +42,14 @@ if NO_LICENSES=1 \ echo "Expected conflicting schema inputs to fail." >&2 exit 1 fi + +# Local TUI runs (script/run-tui) skip schema generation entirely, since the TUI +# binaries don't implement dump-settings-schema. +SKIPPED_DESTINATION="$TEMP_DIR/skipped-schema" +NO_LICENSES=1 \ + SKIP_SETTINGS_SCHEMA=1 \ + "$WORKSPACE_ROOT/script/prepare_bundled_resources" "$SKIPPED_DESTINATION" dev +if [ -e "$SKIPPED_DESTINATION/settings_schema.json" ]; then + echo "Expected no settings schema to be written when SKIP_SETTINGS_SCHEMA=1." >&2 + exit 1 +fi From 1c5901b474e909c8e78e760be7eb548969e2997e Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Fri, 21 Aug 2026 23:12:53 +0000 Subject: [PATCH 04/16] Give the local TUI a real settings schema instead of skipping it Review found that script/run-tui's SKIP_SETTINGS_SCHEMA=1 silently disabled the bundled modify-settings skill for local TUI sessions (SkillManager gates it on resources/settings_schema.json existing). warp_tui already links the shared warp crate with its full default feature set to build at all, so generating the schema from the just-built TUI binary itself costs nothing extra: - Expose settings::dump_settings_schema as pub (was pub(crate)) so warp_tui can call it directly. - Add a dump-settings-schema [output_path] subcommand to the TUI's own CLI (crates/warp_tui/src/session.rs), mirroring warp_cli::Command::DumpSettingsSchema's shape, dispatched before the normal TUI bootstrap. - script/run-tui now points SETTINGS_SCHEMA_EXECUTABLE at the binary it just built instead of skipping schema generation. - Add script/test_run_tui_settings_schema: builds warp-tui-oss, checks it implements dump-settings-schema directly, and confirms the prepared resources satisfy modify-settings' exact activation gate. Packaging is untouched: release TUI packages still consume the canonical app-generated schema, and this local dev-only producer never enters the release path, so no schema-parity checks are needed. Also corrects the PR description bullet that said TUI binaries don't implement dump-settings-schema; that was incidental (TUI binaries parse their own CLI args rather than warp_cli::Command), not policy. --- app/src/settings/mod.rs | 2 ++ app/src/settings/schema_generation.rs | 2 +- crates/warp_tui/src/session.rs | 24 +++++++++++++- script/run-tui | 16 ++++++---- script/test_run_tui_settings_schema | 46 +++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 8 deletions(-) create mode 100755 script/test_run_tui_settings_schema diff --git a/app/src/settings/mod.rs b/app/src/settings/mod.rs index 2a79d1acf84..7f5cb5a08e2 100644 --- a/app/src/settings/mod.rs +++ b/app/src/settings/mod.rs @@ -68,6 +68,8 @@ pub use onboarding::*; pub use pane::*; pub use privacy::*; pub use same_line_prompt_block::*; +#[cfg(not(target_family = "wasm"))] +pub use schema_generation::dump_settings_schema; pub use scroll::*; pub use select::*; pub use shared_object_limit_banner::*; diff --git a/app/src/settings/schema_generation.rs b/app/src/settings/schema_generation.rs index b500562b5e1..e4d52f57d3f 100644 --- a/app/src/settings/schema_generation.rs +++ b/app/src/settings/schema_generation.rs @@ -11,7 +11,7 @@ use warp_core::channel::ChannelState; use warp_core::features::FeatureFlag; /// Writes the settings schema to a file or prints it to standard output. -pub(crate) fn dump_settings_schema(output_path: Option<&Path>) -> Result<()> { +pub fn dump_settings_schema(output_path: Option<&Path>) -> Result<()> { let output = settings_schema_json(|flag| flag.is_enabled())?; if let Some(path) = output_path { diff --git a/crates/warp_tui/src/session.rs b/crates/warp_tui/src/session.rs index 041544b3b10..a6f7f018b9c 100644 --- a/crates/warp_tui/src/session.rs +++ b/crates/warp_tui/src/session.rs @@ -6,12 +6,13 @@ //! allowing authentication to complete in the background. use std::io::{self, IsTerminal as _, Read as _}; +use std::path::PathBuf; use ai::LLMProvider; use ai::api_keys::ApiKeyManager; use anyhow::{Context, Result, anyhow}; -use clap::Parser; use clap::error::ErrorKind; +use clap::{Parser, Subcommand}; use inquire::{InquireError, Password, PasswordDisplayMode}; use warp::settings::{TuiThemeSettings, TuiZeroStateSettings, TuiZeroStateSettingsChangedEvent}; #[cfg(feature = "voice_input")] @@ -50,6 +51,9 @@ const CLI_VERSION: &str = match option_env!("GIT_RELEASE_TAG") { #[derive(Debug, Parser)] #[command(name = "warp", version = CLI_VERSION)] struct TuiArgs { + #[command(subcommand)] + command: Option, + /// Resume an Oz/Warp conversation by server token. #[arg(long)] resume: Option, @@ -91,6 +95,20 @@ enum ProviderApiKeyCommand { }, } +/// One-shot commands available on the standalone TUI binaries. The full app +/// binary implements the same `dump-settings-schema` contract (see +/// `warp_cli::Command::DumpSettingsSchema`); this local copy lets +/// `script/run-tui` seed a real settings schema from whichever TUI binary is +/// about to run, without building the full GUI app binary just for that. +#[derive(Debug, Subcommand)] +enum TuiCommand { + /// Print the JSON schema for the current Warp channel's settings and exit. + DumpSettingsSchema { + /// Write the schema to this path instead of standard output. + output_path: Option, + }, +} + /// Reads a provider API key from a masked TTY prompt or, when stdin is piped, /// from stdin. Empty input and interactive cancellation return `Ok(None)`. fn read_provider_api_key() -> Result> { @@ -144,6 +162,10 @@ pub fn run() -> Result<()> { } Err(error) => return Err(anyhow::Error::new(error)), }; + if let Some(TuiCommand::DumpSettingsSchema { output_path }) = args.command { + warp::features::init_feature_flags(); + return warp::settings::dump_settings_schema(output_path.as_deref()); + } let provider_api_key_command = if let Some(provider) = args.set_provider_api_key { if !provider.supports_pasted_api_key() { return Err(anyhow!( diff --git a/script/run-tui b/script/run-tui index 84f961a4f99..3bb664f6045 100755 --- a/script/run-tui +++ b/script/run-tui @@ -110,17 +110,21 @@ if [ -n "${TARGET_TRIPLE}" ]; then fi BUILD_DIR="${BUILD_DIR}/${PROFILE_DIR}" -# Stage bundled skills next to the binary so the standalone TUI can resolve them at runtime. -# License generation remains skipped for local runs, and so is settings schema generation: the -# TUI binaries don't implement `dump-settings-schema` (only the full app binary does), and -# packaged TUI builds consume a schema generated from that binary during release instead. +# Stage bundled skills and the settings schema next to the binary so the +# standalone TUI can resolve them at runtime, including activating the +# modify-settings skill. The schema comes from the just-built TUI binary +# itself and is for local development only; packaged TUI builds still +# consume the canonical schema generated from the full app binary. License +# generation remains skipped for local runs. prepare_tui_bundled_resources() { echo "Preparing bundled resources at ${BUILD_DIR}/resources (channel: ${CHANNEL})..." if [ "${PROFILE_DIR}" = "debug" ]; then - NO_LICENSES=1 SKIP_SETTINGS_SCHEMA=1 "${REPO_ROOT}/script/prepare_bundled_resources" \ + NO_LICENSES=1 SETTINGS_SCHEMA_EXECUTABLE="${BUILD_DIR}/${BIN}" \ + "${REPO_ROOT}/script/prepare_bundled_resources" \ "${BUILD_DIR}/resources" "${CHANNEL}" else - NO_LICENSES=1 SKIP_SETTINGS_SCHEMA=1 "${REPO_ROOT}/script/prepare_bundled_resources" \ + NO_LICENSES=1 SETTINGS_SCHEMA_EXECUTABLE="${BUILD_DIR}/${BIN}" \ + "${REPO_ROOT}/script/prepare_bundled_resources" \ "${BUILD_DIR}/resources" "${CHANNEL}" "${PROFILE_DIR}" fi } diff --git a/script/test_run_tui_settings_schema b/script/test_run_tui_settings_schema new file mode 100755 index 00000000000..5599796efcd --- /dev/null +++ b/script/test_run_tui_settings_schema @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# +# Regression test for the `script/run-tui` settings-schema fix. +# +# Builds the real `warp-tui-oss` binary, confirms it implements +# `dump-settings-schema` directly (the standalone TUI binaries used to have no +# such command, which is what caused script/run-tui to fail closed), and then +# runs it through the exact `prepare_bundled_resources` invocation +# script/run-tui uses to confirm the local TUI's `modify-settings` bundled +# skill actually activates: `activation_for_bundled_skill` in +# `app/src/ai/skills/bundled.rs` gates that skill on `settings_schema.json` +# existing in the prepared resources directory, so checking for that file is +# equivalent to checking the skill is active. + +set -e + +WORKSPACE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TEMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TEMP_DIR"' EXIT + +echo "Building warp-tui-oss..." +cargo build --manifest-path "$WORKSPACE_ROOT/Cargo.toml" -p warp_tui --bin warp-tui-oss \ + --features standalone +TUI_BIN="$(cargo metadata --manifest-path "$WORKSPACE_ROOT/Cargo.toml" --no-deps \ + --format-version 1 | jq -r .target_directory)/debug/warp-tui-oss" + +echo "Checking warp-tui-oss implements dump-settings-schema directly..." +DIRECT_SCHEMA="$TEMP_DIR/direct-settings-schema.json" +"$TUI_BIN" dump-settings-schema "$DIRECT_SCHEMA" +test -s "$DIRECT_SCHEMA" +python3 -c "import json; json.load(open('$DIRECT_SCHEMA'))" + +echo "Checking the local TUI resources activate the modify-settings skill..." +RESOURCES_DIR="$TEMP_DIR/resources" +NO_LICENSES=1 \ + SETTINGS_SCHEMA_EXECUTABLE="$TUI_BIN" \ + "$WORKSPACE_ROOT/script/prepare_bundled_resources" "$RESOURCES_DIR" oss + +# This is the exact file `activation_for_bundled_skill` checks for to decide +# whether the "modify-settings" bundled skill is active; see +# `app/src/ai/skills/bundled.rs`. +test -s "$RESOURCES_DIR/settings_schema.json" +python3 -c "import json; json.load(open('$RESOURCES_DIR/settings_schema.json'))" +test -f "$RESOURCES_DIR/bundled/skills/modify-settings/SKILL.md" + +echo "OK: local TUI resources contain a real settings schema and the modify-settings skill is active." From 40a5dec0aeb8a435eb70128e6a510a84f183a730 Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Fri, 21 Aug 2026 23:41:55 +0000 Subject: [PATCH 05/16] Split TUI build from packaging so only packaging waits on the schema This PR made release_macos_tui, release_linux_tui, and release_windows_tui wait on the job that produces their platform's canonical schema (release_macos_single_arch, release_linux_x86, build_windows_binaries respectively). Those jobs previously needed only prepare_release, so this serialized the TUI build+notarize/package phase behind the (slower) app build phase, undoing the parallelism the release pipeline relied on. Split each into a build-only job (needs: prepare_release, builds the raw TUI binary, no schema/signing) and the existing release_*_tui job trimmed to packaging only (signs/notarizes/packages using the schema and the binary handed off from the build job). This mirrors the build_windows_binaries -> release_windows split already used for the GUI Windows binary: - macOS: new build_macos_tui_binaries (per arch) builds with --nosign, NO_LICENSES=1, SKIP_SETTINGS_SCHEMA=1, and uploads the binary+dSYM from their per-target-triple Cargo path. release_macos_tui now downloads that artifact and packages with --skip-build. - Linux: new build_linux_tui_binaries (per arch, native runners) builds with NO_LICENSES=1, SKIP_SETTINGS_SCHEMA=1, and uploads the binary. release_linux_tui downloads it and packages with --skip-build. - Windows: new build_windows_tui_binaries builds with -skip_build_installer (already schema-free) and uploads the binary+pdb. release_windows_tui downloads it and locates the same outputs via -skip_build_binary -skip_build_installer (the same locate idiom release_windows already uses) before signing and packaging. release_linux_arm is intentionally left alone: it builds nothing it can execute (consumes pre-built artifacts from build_linux_arm_binaries / build_linux_cli_arm_binaries), so it must keep waiting on release_linux_x86 for the schema regardless. No packaging behavior, published artifact, or the fail-closed SETTINGS_SCHEMA_EXECUTABLE/SOURCE contract changed -- only when each half of the split runs and what it waits on. --- .github/workflows/create_release.yml | 270 +++++++++++++++++++++++++-- 1 file changed, 256 insertions(+), 14 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index dc6e8121856..ca6c4a30ae1 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -546,10 +546,16 @@ jobs: env: DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_cli.outputs.dsym_path }} - release_macos_tui: - name: Build Release (macOS TUI ${{ matrix.arch }}) + # Builds the TUI binary only, with no dependency on release_macos_single_arch, + # so this heavy build+notarize step keeps running in parallel with the app + # build instead of serializing behind it. Packaging (which needs the + # canonical schema release_macos_single_arch produces) happens in + # release_macos_tui below, mirroring the build_windows_binaries / + # release_windows split. + build_macos_tui_binaries: + name: Build Release Binary (macOS TUI ${{ matrix.arch }}) runs-on: macos-26-xlarge - needs: [prepare_release, release_macos_single_arch] + needs: prepare_release if: ${{ inputs.build_macos != false }} timeout-minutes: 60 strategy: @@ -586,16 +592,100 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} + # Only the binary matters here; this job's own bundled resources are + # discarded, so skip signing/notarization and the (otherwise mandatory) + # schema, both of which belong to the packaging step below. + - name: Build ${{ matrix.arch }} binary + id: build_tui + run: | + script/bundle --channel $CHANNEL --arch ${{ matrix.arch }} --artifact tui --nosign + shell: bash + env: + CHANNEL: ${{ steps.get-config.outputs.channel }} + GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + NO_LICENSES: "1" + SKIP_SETTINGS_SCHEMA: "1" + + # binary_path is already copied out of the per-target-triple Cargo output + # directory; --skip-build (used by release_macos_tui below) re-derives + # that copy from the triple directory, so preserve it here instead. + - name: Create tar archive with build artifacts + run: | + BINARY_PATH="${{ steps.build_tui.outputs.binary_path }}" + PROFILE_DIR="${BINARY_PATH#target/}" + PROFILE_DIR="${PROFILE_DIR%/bundle/osx/*}" + RAW_BINARY_PATH="target/${{ steps.build_tui.outputs.rust_target }}/$PROFILE_DIR/$(basename "$BINARY_PATH")" + TAR_PATHS=("$RAW_BINARY_PATH") + if [[ -e "$RAW_BINARY_PATH.dSYM" ]]; then + TAR_PATHS+=("$RAW_BINARY_PATH.dSYM") + fi + tar czfv build-artifacts.tar.gz "${TAR_PATHS[@]}" + shell: bash + + - name: Archive ${{ matrix.arch }} TUI build artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: macos-tui-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + retention-days: 1 + # We compress the tar archive, so no need to re-compress. + compression-level: 0 + path: build-artifacts.tar.gz + + release_macos_tui: + name: Build Release (macOS TUI ${{ matrix.arch }}) + runs-on: macos-26-xlarge + needs: [prepare_release, release_macos_single_arch, build_macos_tui_binaries] + if: ${{ inputs.build_macos != false }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - arch: aarch64 + - arch: x86_64 + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - uses: ./.github/actions/prepare_environment + with: + target_os: macos + is_self_hosted: false + ref: ${{ needs.prepare_release.outputs.release_branch }} + install_release_deps: true + ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} + + # Install go toolchain, as it may be needed for some build steps. + - name: Setup Go + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + with: + go-version: stable + + - name: Get channel configuration + id: get-config + uses: ./.github/actions/get_channel_config/ + with: + config_file: ${{ env.CONFIG_FILE }} + channel: ${{ inputs.channel }} + + - name: Download ${{ matrix.arch }} TUI build artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: macos-tui-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + + - name: Extract TUI build artifacts + run: tar xvfz build-artifacts.tar.gz + shell: bash + - name: Download canonical macOS settings schema uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: settings-schema-macos-aarch64-${{ steps.get-config.outputs.channel }} path: .release-artifacts/settings-schema-macos - - name: Build ${{ matrix.arch }} binary + - name: Bundle ${{ matrix.arch }} binary id: bundle_tui run: | - script/bundle --read-passwords-from-env --channel $CHANNEL --arch ${{ matrix.arch }} --artifact tui + script/bundle --skip-build --read-passwords-from-env --channel $CHANNEL --arch ${{ matrix.arch }} --artifact tui shell: bash env: CHANNEL: ${{ steps.get-config.outputs.channel }} @@ -668,10 +758,93 @@ jobs: name: release-macos-tui-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} path: warp-tui-${{ steps.get-config.outputs.channel }}-macos-${{ matrix.arch }}.tar.gz + # Builds the TUI binary only, with no dependency on release_linux_x86, so + # this native, per-arch build keeps running in parallel with the app build + # instead of serializing behind it. Packaging (which needs the canonical + # schema release_linux_x86 produces) happens in release_linux_tui below, + # mirroring the build_windows_binaries / release_windows split. + build_linux_tui_binaries: + name: Build Release Binary (Linux TUI ${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + needs: prepare_release + if: ${{ inputs.build_linux != false }} + timeout-minutes: 180 + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + runner: namespace-profile-ubuntu-20-04 + - arch: aarch64 + runner: namespace-profile-ubuntu-20-04-arm + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - uses: ./.github/actions/prepare_environment + with: + target_os: linux + is_self_hosted: false + ref: ${{ needs.prepare_release.outputs.release_branch }} + install_release_deps: true + ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} + + # Install gcc 10, as gcc 9.5 has a bug with memcmp that is incompatible + # with aws-lc-rs (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95189). + - name: Install and configure gcc-10 + run: | + sudo apt update + sudo apt install -y gcc-10 g++-10 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 60 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + gcc --version + g++ --version + + - name: Get channel configuration + id: get-config + uses: ./.github/actions/get_channel_config/ + with: + config_file: ${{ env.CONFIG_FILE }} + channel: ${{ inputs.channel }} + + # Namespace's User Bundled Cache persists target/ between jobs on the same profile. + - name: Clean stale bundle output + run: | + if [[ -d target ]]; then + find target -path '*/bundle/linux' -type d -prune -exec rm -rf {} + + fi + shell: bash + + # Only the binary matters here; this job's own bundled resources are + # discarded, so skip the (otherwise mandatory) schema and third-party + # license generation, both of which belong to the packaging step below. + - name: Build TUI binary + id: build_tui + run: script/bundle --channel "$CHANNEL" --artifact tui --arch "${{ matrix.arch }}" + shell: bash + env: + CHANNEL: ${{ steps.get-config.outputs.channel }} + GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + NO_LICENSES: "1" + SKIP_SETTINGS_SCHEMA: "1" + + - name: Create tar archive with build artifacts + run: | + EXECUTABLE_PATH="$(realpath --relative-to=$(pwd) "$EXECUTABLE_PATH")" + tar czvf build-artifacts.tar.gz "$EXECUTABLE_PATH" + shell: bash + env: + EXECUTABLE_PATH: ${{ steps.build_tui.outputs.executable_path }} + + - name: Archive ${{ matrix.arch }} TUI build artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: linux-tui-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + retention-days: 1 + path: build-artifacts.tar.gz + release_linux_tui: name: Build Release (Linux TUI ${{ matrix.arch }}) runs-on: ${{ matrix.runner }} - needs: [prepare_release, release_linux_x86] + needs: [prepare_release, release_linux_x86, build_linux_tui_binaries] if: ${{ inputs.build_linux != false }} timeout-minutes: 180 strategy: @@ -710,6 +883,15 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} + - name: Download ${{ matrix.arch }} TUI build artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: linux-tui-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + + - name: Extract TUI build artifacts + run: tar xzvf build-artifacts.tar.gz + shell: bash + - name: Download canonical Linux settings schema uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -726,7 +908,7 @@ jobs: - name: Bundle TUI id: bundle_tui - run: script/bundle --channel "$CHANNEL" --artifact tui --arch "${{ matrix.arch }}" + run: script/bundle --channel "$CHANNEL" --artifact tui --arch "${{ matrix.arch }}" --skip-build shell: bash env: CHANNEL: ${{ steps.get-config.outputs.channel }} @@ -1686,10 +1868,68 @@ jobs: name: release-web-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.packages_dir }} + # Builds the TUI binary only, with no dependency on build_windows_binaries, + # so this build keeps running in parallel with the app build instead of + # serializing behind it. Packaging (which needs the canonical schema + # build_windows_binaries produces) happens in release_windows_tui below, + # following the same split build_windows_binaries already uses for + # release_windows. + build_windows_tui_binaries: + name: Build Release Binary (Windows TUI ${{ matrix.arch }}) + runs-on: windows-latest-large + needs: prepare_release + if: ${{ inputs.build_windows != false }} + timeout-minutes: 150 + strategy: + fail-fast: false + matrix: + include: + - arch: x64 + - arch: arm64 + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - uses: ./.github/actions/prepare_environment + with: + target_os: windows + cache_key: tui-${{ matrix.arch }} + is_self_hosted: false + ref: ${{ needs.prepare_release.outputs.release_branch }} + install_release_deps: true + ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} + - name: Add arm64 target + if: ${{ matrix.arch == 'arm64' }} + run: rustup target add aarch64-pc-windows-msvc + shell: bash + + - name: Get channel configuration + id: get-config + uses: ./.github/actions/get_channel_config/ + with: + config_file: ${{ env.CONFIG_FILE }} + channel: ${{ inputs.channel }} + + - name: Build Warp Agent CLI + id: build_tui + run: script/bundle --artifact tui --channel "$CHANNEL" --skip_build_installer --arch ${{ matrix.arch }} + shell: bash + env: + CHANNEL: ${{ steps.get-config.outputs.channel }} + GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + + - name: Archive Windows TUI binary build + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: windows-tui-binary-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + retention-days: 1 + path: | + ${{ steps.build_tui.outputs.binary_path }} + ${{ steps.build_tui.outputs.pdb_file_path }} + release_windows_tui: name: Build Release (Windows TUI ${{ matrix.arch }}) runs-on: windows-latest-large - needs: [prepare_release, build_windows_binaries] + needs: [prepare_release, build_windows_binaries, build_windows_tui_binaries] if: ${{ inputs.build_windows != false }} timeout-minutes: 150 strategy: @@ -1715,10 +1955,6 @@ jobs: ref: ${{ needs.prepare_release.outputs.release_branch }} install_release_deps: true ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} - - name: Add arm64 target - if: ${{ matrix.arch == 'arm64' }} - run: rustup target add aarch64-pc-windows-msvc - shell: bash - name: Get channel configuration id: get-config @@ -1727,6 +1963,12 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} + - name: Download Windows TUI binary build + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: windows-tui-binary-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + path: . + - name: Download canonical Windows settings schema uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -1738,9 +1980,9 @@ jobs: run: ./script/windows/test_tui_installer.ps1 shell: pwsh - - name: Build Warp Agent CLI + - name: Locate TUI binary build outputs id: build_tui - run: script/bundle --artifact tui --channel "$CHANNEL" --skip_build_installer --arch ${{ matrix.arch }} + run: script/bundle --artifact tui --channel "$CHANNEL" --skip_build_binary --skip_build_installer --arch ${{ matrix.arch }} shell: bash env: CHANNEL: ${{ steps.get-config.outputs.channel }} From cc1bb0a7237c5213a218b384acc1ec3668b38a04 Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Fri, 21 Aug 2026 23:52:07 +0000 Subject: [PATCH 06/16] Fix TUI build-artifact handoff bugs: wrong Linux path, dangling macOS dSYM symlink Both were artifact-handoff bugs from the build/package split in 30771d469: - Linux: the build job archived executable_path, which script/linux/bundle already reports as the staged target//bundle/linux/ copy. But --skip-build (used by release_linux_tui) still unconditionally cp's from the raw target// Cargo output before staging it again, and that raw path was never in the archive. Archive the raw path instead, derived from debug_executable_path (raw path + ".debug") so we don't have to duplicate the channel/profile-naming logic that lives in the script. - macOS: the build job archived the raw per-triple dSYM as a bare path. Cargo's dSYM there is a symlink into target/.../deps, so once extracted in a fresh packaging job the link dangled and the dwarfdump/sentry-cli verification in release_macos_tui failed. Also archive the symlink's resolved target (as a relative path), mirroring the existing app artifact's binary_path/dsym_path/dsym_realpath pattern. Verified both fixes locally with tar archive/extract simulations in a separate directory, confirming the raw Linux binary path is restored and the macOS dSYM symlink resolves (non-dangling) post-extraction. Swept the rest of each TUI packaging job for the same class of assumption (a path resolving into target/ that must survive the new build/package artifact boundary): - macOS: only the binary and dSYM are read from target/ in the tui branch of script/macos/bundle; both are now covered. Codesigning/notarization operate on the OUT_DIR copy created fresh in the packaging job itself. - Linux: only the executable path is read from target/ for the tui artifact; now covered. prepare_bundled_resources and the tarball validation step operate on the OUT_DIR copy created fresh in the packaging job. - Windows: already used direct actions/upload-artifact and actions/download-artifact on the script's own absolute-path outputs (binary_path, pdb_file_path), matching the existing build_windows_binaries -> release_windows handoff exactly, so this class of bug does not apply there. actionlint diagnostic count is unchanged (17, same pre-existing custom-runner-label and unrelated action-metadata noise as before this commit) -- no new issues introduced. --- .github/workflows/create_release.yml | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index ca6c4a30ae1..52333e4f47a 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -608,16 +608,24 @@ jobs: # binary_path is already copied out of the per-target-triple Cargo output # directory; --skip-build (used by release_macos_tui below) re-derives - # that copy from the triple directory, so preserve it here instead. + # that copy from the triple directory, so preserve it here instead. The + # dSYM Cargo produces there is itself a symlink into target/.../deps, so + # (as with the app archive above) also include its resolved target; + # otherwise the link dangles once extracted into a fresh job. - name: Create tar archive with build artifacts run: | BINARY_PATH="${{ steps.build_tui.outputs.binary_path }}" PROFILE_DIR="${BINARY_PATH#target/}" PROFILE_DIR="${PROFILE_DIR%/bundle/osx/*}" RAW_BINARY_PATH="target/${{ steps.build_tui.outputs.rust_target }}/$PROFILE_DIR/$(basename "$BINARY_PATH")" + RAW_DSYM_PATH="$RAW_BINARY_PATH.dSYM" TAR_PATHS=("$RAW_BINARY_PATH") - if [[ -e "$RAW_BINARY_PATH.dSYM" ]]; then - TAR_PATHS+=("$RAW_BINARY_PATH.dSYM") + if [[ -e "$RAW_DSYM_PATH" ]]; then + TAR_PATHS+=("$RAW_DSYM_PATH") + if [[ -L "$RAW_DSYM_PATH" ]]; then + REAL_DSYM_PATH="$(python3 -c 'import os,sys; print(os.path.relpath(sys.argv[1]))' "$(realpath "$RAW_DSYM_PATH")")" + TAR_PATHS+=("$REAL_DSYM_PATH") + fi fi tar czfv build-artifacts.tar.gz "${TAR_PATHS[@]}" shell: bash @@ -826,13 +834,19 @@ jobs: NO_LICENSES: "1" SKIP_SETTINGS_SCHEMA: "1" + # executable_path is the copy already staged into bundle/linux; + # --skip-build (used by release_linux_tui below) instead re-copies from + # the raw target// Cargo output, which debug_executable_path + # (that raw path plus ".debug") lets us recover without duplicating the + # channel/profile-naming logic that lives in script/linux/bundle. - name: Create tar archive with build artifacts run: | - EXECUTABLE_PATH="$(realpath --relative-to=$(pwd) "$EXECUTABLE_PATH")" - tar czvf build-artifacts.tar.gz "$EXECUTABLE_PATH" + DEBUG_EXECUTABLE_PATH="$(realpath --relative-to=$(pwd) "$DEBUG_EXECUTABLE_PATH")" + RAW_EXECUTABLE_PATH="${DEBUG_EXECUTABLE_PATH%.debug}" + tar czvf build-artifacts.tar.gz "$RAW_EXECUTABLE_PATH" shell: bash env: - EXECUTABLE_PATH: ${{ steps.build_tui.outputs.executable_path }} + DEBUG_EXECUTABLE_PATH: ${{ steps.build_tui.outputs.debug_executable_path }} - name: Archive ${{ matrix.arch }} TUI build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 From a17ec6d5574e0252e0b5d0083b1da31c0c35a571 Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Sat, 22 Aug 2026 00:07:16 +0000 Subject: [PATCH 07/16] Harden settings-schema defaults against cross-compiled/non-executable targets A live release run failed: build_linux_cli_arm_binaries cross-compiles an aarch64 CLI binary on an x86_64 runner and set no SETTINGS_SCHEMA_SOURCE/SETTINGS_SCHEMA_EXECUTABLE, so script/linux/bundle defaulted to executing the just-built aarch64 binary on the x86_64 host to generate its settings schema, failing with an exec format error. Specific fix: skip schema generation in build_linux_cli_arm_binaries via SKIP_SETTINGS_SCHEMA=1 - release_linux_arm, which packages this binary, always supplies its own SETTINGS_SCHEMA_SOURCE, so this job never needs one. General hardening: script/linux/bundle, script/macos/bundle, and script/windows/bundle.ps1 all default SETTINGS_SCHEMA_EXECUTABLE to the just-built binary when unset, assuming it's executable on the host. Compute whether the host can actually execute the target architecture (accounting for Rosetta 2 on macOS and Windows' arm64-host x64-emulation) and fail with a clear error instead of defaulting blindly when it can't. script/windows/prepare_bundled_resources.ps1 was already fail-closed with no such default, so it needed no change. --- .github/workflows/create_release.yml | 5 ++++ script/linux/bundle | 43 +++++++++++++++++----------- script/macos/bundle | 42 +++++++++++++++++++++++---- script/windows/bundle.ps1 | 17 +++++++++++ 4 files changed, 85 insertions(+), 22 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 52333e4f47a..adc5baf311b 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -1468,6 +1468,10 @@ jobs: run: rm -rf target/*/bundle/linux shell: bash + # This cross-compiles aarch64 on an x86_64 host, so the just-built binary + # cannot be executed here to generate a schema. release_linux_arm, which + # packages this binary, supplies the canonical SETTINGS_SCHEMA_SOURCE + # itself, so this job never needs one. - name: Build CLI id: build_cli run: | @@ -1477,6 +1481,7 @@ jobs: env: CHANNEL: ${{ steps.get-config.outputs.channel }} GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + SKIP_SETTINGS_SCHEMA: "1" - name: Package binaries id: package_binaries diff --git a/script/linux/bundle b/script/linux/bundle index 16da564d156..d05014e98ab 100755 --- a/script/linux/bundle +++ b/script/linux/bundle @@ -120,23 +120,27 @@ done # set positional arguments in their proper place eval set -- "$PARAMS" +# Linux has no transparent cross-arch execution (unlike macOS's Rosetta), so a +# binary built for a different architecture than the host can never be run +# here. Resolve the host architecture once and reuse it both for the TUI +# native-build restriction below and for deciding whether the just-built +# binary can stand in for a settings-schema executable further down. +case "$(uname -m)" in + arm64|aarch64) HOST_ARCH="aarch64" ;; + x86_64|amd64) HOST_ARCH="x86_64" ;; + *) + echo "Error: Unsupported host architecture: $(uname -m)" >&2 + exit 1 + ;; +esac + # TUI Linux bundles use the native GNU target because their clipboard backends # link against host Wayland/X11 libraries. Refuse to compile on a different # architecture so an x86 binary can never be published under an ARM filename # (or vice versa). Packaging-only runs may still stage a prebuilt native binary. -if [[ "$ARTIFACT" == "tui" && "$BUILD" == "true" ]]; then - case "$(uname -m)" in - arm64|aarch64) HOST_ARCH="aarch64" ;; - x86_64|amd64) HOST_ARCH="x86_64" ;; - *) - echo "Error: Unsupported native TUI build architecture: $(uname -m)" >&2 - exit 1 - ;; - esac - if [[ "$BUILD_ARCH" != "$HOST_ARCH" ]]; then - echo "Error: TUI bundles must be built natively; requested '$BUILD_ARCH' on '$HOST_ARCH'." >&2 - exit 1 - fi +if [[ "$ARTIFACT" == "tui" && "$BUILD" == "true" && "$BUILD_ARCH" != "$HOST_ARCH" ]]; then + echo "Error: TUI bundles must be built natively; requested '$BUILD_ARCH' on '$HOST_ARCH'." >&2 + exit 1 fi # Statically compile the CLI and warpctrl artifacts so they can run on older Linux distros. @@ -348,11 +352,18 @@ if [[ "${SKIP_SETTINGS_SCHEMA:-}" != "1" && -z "${SETTINGS_SCHEMA_EXECUTABLE:-}" if [[ "$ARTIFACT" == "tui" ]]; then echo "Error: TUI bundles require SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 exit 1 - elif [[ "$BUILD" == "true" ]]; then - export SETTINGS_SCHEMA_EXECUTABLE="$EXECUTABLE_PATH" - else + elif [[ "$BUILD" != "true" ]]; then echo "Error: --skip-build requires SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 exit 1 + elif [[ "$BUILD_ARCH" != "$HOST_ARCH" ]]; then + # The CLI/warpctrl artifacts can be cross-compiled (see the musl toolchain + # setup above); the resulting binary cannot then be executed on this host + # to generate a schema, so require the caller to supply one explicitly + # instead of failing later with an opaque exec format error. + echo "Error: Cannot execute the cross-compiled '$BUILD_ARCH' binary on this '$HOST_ARCH' host to generate a settings schema; pass SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 + exit 1 + else + export SETTINGS_SCHEMA_EXECUTABLE="$EXECUTABLE_PATH" fi fi diff --git a/script/macos/bundle b/script/macos/bundle index 246baa6f8db..e03ee65353f 100755 --- a/script/macos/bundle +++ b/script/macos/bundle @@ -467,6 +467,21 @@ else fi fi +# Apple Silicon hosts transparently execute Intel binaries via Rosetta 2, but +# Intel hosts cannot execute Apple Silicon binaries, so only that direction is +# unsafe. Resolve this once, ahead of the artifact-specific configuration +# below (which determines UNIVERSAL_BINARY's final value), so the +# settings-schema defaults further down can fail clearly instead of hitting +# an exec format error. +case "$(uname -m)" in + arm64|aarch64) HOST_ARCH="aarch64" ;; + x86_64) HOST_ARCH="x86_64" ;; + *) + echo "Error: Unsupported host architecture: $(uname -m)" >&2 + exit 1 + ;; +esac + # Set artifact-specific configuration. if [[ "$ARTIFACT" == cli || "$ARTIFACT" == warpctrl ]]; then UNIVERSAL_BINARY=false @@ -495,6 +510,15 @@ elif [[ "$ARTIFACT" == app ]]; then FEATURES="$FEATURES,gui,nld_classifier_v3,nld_heuristic_v2" fi +# A universal binary always has a native slice, so it is unaffected by the +# host/target mismatch below. This must run after the artifact-specific +# configuration above, since that's what gives UNIVERSAL_BINARY its final +# value (e.g. cli/warpctrl/tui always force it to false). +CAN_EXECUTE_DEFAULT_TARGET=true +if [[ "$UNIVERSAL_BINARY" != true && "$HOST_ARCH" == "x86_64" && "$DEFAULT_TARGET" == "$ARM_TARGET" ]]; then + CAN_EXECUTE_DEFAULT_TARGET=false +fi + # If we're building a universal bundle for the app artifact, make sure the additional target is available. if [[ $UNIVERSAL_BINARY = true ]]; then rustup target add "$ADDITIONAL_TARGET" @@ -663,11 +687,14 @@ if [[ "$ARTIFACT" == "app" ]]; then BUNDLED_RESOURCES_DIR="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/Resources" echo "Preparing bundled resources..." if [[ "${SKIP_SETTINGS_SCHEMA:-}" != "1" && -z "${SETTINGS_SCHEMA_EXECUTABLE:-}" && -z "${SETTINGS_SCHEMA_SOURCE:-}" ]]; then - if [[ $BUILD_BINARY == true ]]; then - export SETTINGS_SCHEMA_EXECUTABLE="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/MacOS/$WARP_BIN" - else + if [[ $BUILD_BINARY != true ]]; then echo "Error: --skip-build requires SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 exit 1 + elif [[ "$CAN_EXECUTE_DEFAULT_TARGET" != true ]]; then + echo "Error: Cannot execute the just-built $DEFAULT_TARGET binary on this $HOST_ARCH host to generate a settings schema; pass SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 + exit 1 + else + export SETTINGS_SCHEMA_EXECUTABLE="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/MacOS/$WARP_BIN" fi fi "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" @@ -771,11 +798,14 @@ elif [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then echo "Preparing CLI resources directory" BUNDLED_RESOURCES_DIR="$OUT_DIR/resources" if [[ "${SKIP_SETTINGS_SCHEMA:-}" != "1" && -z "${SETTINGS_SCHEMA_EXECUTABLE:-}" && -z "${SETTINGS_SCHEMA_SOURCE:-}" ]]; then - if [[ $BUILD_BINARY == true ]]; then - export SETTINGS_SCHEMA_EXECUTABLE="$OUT_DIR/$WARP_BIN" - else + if [[ $BUILD_BINARY != true ]]; then echo "Error: --skip-build requires SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 exit 1 + elif [[ "$CAN_EXECUTE_DEFAULT_TARGET" != true ]]; then + echo "Error: Cannot execute the just-built $DEFAULT_TARGET binary on this $HOST_ARCH host to generate a settings schema; pass SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 + exit 1 + else + export SETTINGS_SCHEMA_EXECUTABLE="$OUT_DIR/$WARP_BIN" fi fi "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" diff --git a/script/windows/bundle.ps1 b/script/windows/bundle.ps1 index 3fd84be52b3..f0a9e91d968 100644 --- a/script/windows/bundle.ps1 +++ b/script/windows/bundle.ps1 @@ -60,6 +60,20 @@ if ($ARCH -eq 'arm64') { $PLATFORM_TARGET = 'x86_64-pc-windows-msvc' } +# Windows-on-ARM64 hosts can run x64 binaries via built-in emulation, but x64 +# hosts cannot run arm64 binaries at all, so only that direction is unsafe. +# Resolve it once so the settings-schema default below (which assumes the +# just-built binary is executable) can fail clearly instead of the process +# simply failing to start. +$HOST_ARCH = if ($env:PROCESSOR_ARCHITECTURE -eq 'AMD64') { + 'x64' +} elseif ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { + 'arm64' +} else { + throw "Unsupported host architecture: $env:PROCESSOR_ARCHITECTURE" +} +$CAN_EXECUTE_ARCH = -not ($ARCH -eq 'arm64' -and $HOST_ARCH -eq 'x64') + $ErrorActionPreference = 'Stop' function Assert-ValidSignature { @@ -256,6 +270,9 @@ if ($env:SKIP_SETTINGS_SCHEMA -ne '1' -and -not $env:SETTINGS_SCHEMA_EXECUTABLE } elseif ($SKIP_BUILD_BINARY) { Write-Error '-skip_build_binary requires SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE.' exit 1 + } elseif (-not $CAN_EXECUTE_ARCH) { + Write-Error "Cannot execute the just-built $ARCH binary on this $HOST_ARCH host to generate a settings schema; pass SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." + exit 1 } $env:SETTINGS_SCHEMA_EXECUTABLE = $BINARY_PATH } From ebecb145e3acaeeaa5971acd20a49974b9858bc2 Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Sat, 22 Aug 2026 00:11:01 +0000 Subject: [PATCH 08/16] Fix host-architecture detection false positives in the new guards script/macos/bundle: uname -m reports the running process's architecture, not the hardware. In a Rosetta-translated x86_64 shell on Apple Silicon it reports x86_64, which made the new cross-compile guard reject --arch aarch64 CLI/warpctrl/TUI builds that would actually succeed. Check hw.optional.arm64 first, which reflects the real hardware. script/windows/bundle.ps1: similarly, PROCESSOR_ARCHITECTURE reports AMD64 for an x64 process running under WOW64 on a Windows-on-ARM host. PROCESSOR_ARCHITEW6432 carries the true native host architecture in that case, so prefer it when present. --- script/macos/bundle | 26 ++++++++++++++++++-------- script/windows/bundle.ps1 | 17 ++++++++++++++--- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/script/macos/bundle b/script/macos/bundle index e03ee65353f..66aaf18c4f1 100755 --- a/script/macos/bundle +++ b/script/macos/bundle @@ -473,14 +473,24 @@ fi # below (which determines UNIVERSAL_BINARY's final value), so the # settings-schema defaults further down can fail clearly instead of hitting # an exec format error. -case "$(uname -m)" in - arm64|aarch64) HOST_ARCH="aarch64" ;; - x86_64) HOST_ARCH="x86_64" ;; - *) - echo "Error: Unsupported host architecture: $(uname -m)" >&2 - exit 1 - ;; -esac +# +# `uname -m` reports the architecture of the running process, not the +# hardware: inside a Rosetta-translated x86_64 shell on Apple Silicon it +# reports x86_64 even though the hardware can execute arm64 binaries +# directly. `hw.optional.arm64` reflects the actual hardware instead, so +# check it first. +if [[ "$(sysctl -n hw.optional.arm64 2>/dev/null)" == "1" ]]; then + HOST_ARCH="aarch64" +else + case "$(uname -m)" in + arm64|aarch64) HOST_ARCH="aarch64" ;; + x86_64) HOST_ARCH="x86_64" ;; + *) + echo "Error: Unsupported host architecture: $(uname -m)" >&2 + exit 1 + ;; + esac +fi # Set artifact-specific configuration. if [[ "$ARTIFACT" == cli || "$ARTIFACT" == warpctrl ]]; then diff --git a/script/windows/bundle.ps1 b/script/windows/bundle.ps1 index f0a9e91d968..41f2b388fca 100644 --- a/script/windows/bundle.ps1 +++ b/script/windows/bundle.ps1 @@ -65,12 +65,23 @@ if ($ARCH -eq 'arm64') { # Resolve it once so the settings-schema default below (which assumes the # just-built binary is executable) can fail clearly instead of the process # simply failing to start. -$HOST_ARCH = if ($env:PROCESSOR_ARCHITECTURE -eq 'AMD64') { +# +# PROCESSOR_ARCHITECTURE reports the architecture of the running process, not +# the host: an x64 PowerShell process running under WOW64 on a Windows-on-ARM +# machine reports AMD64 even though the host is natively ARM64. +# PROCESSOR_ARCHITEW6432 carries the true native host architecture in that +# case, so prefer it when present. +$NATIVE_PROCESSOR_ARCHITECTURE = if ($env:PROCESSOR_ARCHITEW6432) { + $env:PROCESSOR_ARCHITEW6432 +} else { + $env:PROCESSOR_ARCHITECTURE +} +$HOST_ARCH = if ($NATIVE_PROCESSOR_ARCHITECTURE -eq 'AMD64') { 'x64' -} elseif ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { +} elseif ($NATIVE_PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { - throw "Unsupported host architecture: $env:PROCESSOR_ARCHITECTURE" + throw "Unsupported host architecture: $NATIVE_PROCESSOR_ARCHITECTURE" } $CAN_EXECUTE_ARCH = -not ($ARCH -eq 'arm64' -and $HOST_ARCH -eq 'x64') From bc7a950a41375b9df5ad1a925c59a23461cdb7dc Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Sat, 22 Aug 2026 01:47:28 +0000 Subject: [PATCH 09/16] Split release_macos_cli and release_linux_cli_x86 into build-only + packaging-only jobs Both jobs serialized their (slow, notarize/codesign- or gpg-signing-heavy) build behind release_macos_single_arch / release_linux_x86 respectively, even though they only need the canonical settings schema those jobs produce, not their binaries. Split each into a build-only job (needs only prepare_release) and a trimmed packaging job that downloads the build artifact and the canonical schema, mirroring the build_*_tui_binaries / release_*_tui split already established for the TUI jobs. - build_macos_cli_binaries builds with --nosign and SKIP_SETTINGS_SCHEMA=1; release_macos_cli downloads it and runs --skip-build with the real schema and signing secrets. Preserves both the raw-vs-staged binary_path distinction and the dSYM symlink target when archiving, per the same class of bug fixed for the TUI split. - build_linux_cli_x86_binaries builds with --packages none and SKIP_SETTINGS_SCHEMA=1; release_linux_cli_x86 downloads it and runs --skip-build with the real schema. Unlike the TUI job, the cli artifact's executable_path/debug_executable_path are already raw target/ paths (no staging), so no raw-vs-staged derivation is needed here. --- .github/workflows/create_release.yml | 206 +++++++++++++++++++++++++-- 1 file changed, 198 insertions(+), 8 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index adc5baf311b..a5c338a67cb 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -435,10 +435,16 @@ jobs: name: release-macos-universal-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.dmg_path }} - release_macos_cli: - name: Build Release (macOS CLI ${{ matrix.arch }}) + # Builds the CLI binary only, with no dependency on release_macos_single_arch, + # so this heavy build+notarize step keeps running in parallel with the app + # build instead of serializing behind it. Packaging (which needs the + # canonical schema release_macos_single_arch produces) happens in + # release_macos_cli below, mirroring the build_macos_tui_binaries / + # release_macos_tui split. + build_macos_cli_binaries: + name: Build Release Binary (macOS CLI ${{ matrix.arch }}) runs-on: macos-26-xlarge - needs: [prepare_release, release_macos_single_arch] + needs: prepare_release if: ${{ inputs.build_macos != false }} timeout-minutes: 60 strategy: @@ -475,16 +481,108 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} + # Only the binary matters here; this job's own bundled resources are + # discarded, so skip signing/notarization and the (otherwise mandatory) + # schema, both of which belong to the packaging step below. + - name: Build ${{ matrix.arch }} binary + id: build_cli + run: | + script/bundle --channel $CHANNEL --arch ${{ matrix.arch }} --artifact cli --nosign + shell: bash + env: + CHANNEL: ${{ steps.get-config.outputs.channel }} + GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + NO_LICENSES: "1" + SKIP_SETTINGS_SCHEMA: "1" + + # binary_path is already copied out of the per-target-triple Cargo output + # directory; --skip-build (used by release_macos_cli below) re-derives + # that copy from the triple directory, so preserve it here instead. The + # dSYM Cargo produces there is itself a symlink into target/.../deps, so + # (as with the app archive above) also include its resolved target; + # otherwise the link dangles once extracted into a fresh job. + - name: Create tar archive with build artifacts + run: | + BINARY_PATH="${{ steps.build_cli.outputs.binary_path }}" + PROFILE_DIR="${BINARY_PATH#target/}" + PROFILE_DIR="${PROFILE_DIR%/bundle/osx/*}" + RAW_BINARY_PATH="target/${{ steps.build_cli.outputs.rust_target }}/$PROFILE_DIR/$(basename "$BINARY_PATH")" + RAW_DSYM_PATH="$RAW_BINARY_PATH.dSYM" + TAR_PATHS=("$RAW_BINARY_PATH") + if [[ -e "$RAW_DSYM_PATH" ]]; then + TAR_PATHS+=("$RAW_DSYM_PATH") + if [[ -L "$RAW_DSYM_PATH" ]]; then + REAL_DSYM_PATH="$(python3 -c 'import os,sys; print(os.path.relpath(sys.argv[1]))' "$(realpath "$RAW_DSYM_PATH")")" + TAR_PATHS+=("$REAL_DSYM_PATH") + fi + fi + tar czfv build-artifacts.tar.gz "${TAR_PATHS[@]}" + shell: bash + + - name: Archive ${{ matrix.arch }} CLI build artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: macos-cli-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + retention-days: 1 + # We compress the tar archive, so no need to re-compress. + compression-level: 0 + path: build-artifacts.tar.gz + + release_macos_cli: + name: Build Release (macOS CLI ${{ matrix.arch }}) + runs-on: macos-26-xlarge + needs: [prepare_release, release_macos_single_arch, build_macos_cli_binaries] + if: ${{ inputs.build_macos != false }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - arch: aarch64 + - arch: x86_64 + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - uses: ./.github/actions/prepare_environment + with: + target_os: macos + is_self_hosted: false + ref: ${{ needs.prepare_release.outputs.release_branch }} + install_release_deps: true + ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} + + # Install go toolchain, as it may be needed for some build steps. + - name: Setup Go + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + with: + go-version: stable + + - name: Get channel configuration + id: get-config + uses: ./.github/actions/get_channel_config/ + with: + config_file: ${{ env.CONFIG_FILE }} + channel: ${{ inputs.channel }} + + - name: Download ${{ matrix.arch }} CLI build artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: macos-cli-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} + + - name: Extract CLI build artifacts + run: tar xvfz build-artifacts.tar.gz + shell: bash + - name: Download canonical macOS settings schema uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: settings-schema-macos-aarch64-${{ steps.get-config.outputs.channel }} path: .release-artifacts/settings-schema-macos - - name: Build ${{ matrix.arch }} binary + - name: Bundle ${{ matrix.arch }} binary id: bundle_cli run: | - script/bundle --read-passwords-from-env --channel $CHANNEL --arch ${{ matrix.arch }} --artifact cli + script/bundle --skip-build --read-passwords-from-env --channel $CHANNEL --arch ${{ matrix.arch }} --artifact cli shell: bash env: CHANNEL: ${{ steps.get-config.outputs.channel }} @@ -1146,10 +1244,93 @@ jobs: name: release-linux-x86_64-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.packages_dir }} + # Builds the CLI binary only, with no dependency on release_linux_x86, so + # this build keeps running in parallel with the app build instead of + # serializing behind it. Packaging (which needs the canonical schema + # release_linux_x86 produces) happens in release_linux_cli_x86 below, + # mirroring the build_linux_tui_binaries / release_linux_tui split. + build_linux_cli_x86_binaries: + name: Build Release Binary (Linux CLI x86_64) + runs-on: namespace-profile-ubuntu-22-04 + needs: prepare_release + if: ${{ inputs.build_linux != false }} + timeout-minutes: 90 + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - uses: ./.github/actions/prepare_environment + with: + target_os: linux + is_self_hosted: false + ref: ${{ needs.prepare_release.outputs.release_branch }} + install_release_deps: true + ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} + + # Install gcc 10, as gcc 9.5 has a bug with memcmp that is incompatible + # with aws-lc-rs (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95189). + - name: Install and configure gcc-10 + run: | + sudo apt update + sudo apt install -y gcc-10 g++-10 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 60 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + gcc --version + g++ --version + + - name: Get channel configuration + id: get-config + uses: ./.github/actions/get_channel_config/ + with: + config_file: ${{ env.CONFIG_FILE }} + channel: ${{ inputs.channel }} + + # Namespace's User Bundled Cache persists target/ between jobs on the same profile. + - name: Clean stale bundle output + run: | + if [[ -d target ]]; then + find target -path '*/bundle/linux' -type d -prune -exec rm -rf {} + + fi + shell: bash + + # Only the binary matters here; this job's own bundled resources are + # discarded, so skip the (otherwise mandatory) schema and third-party + # license generation, both of which belong to the packaging step below. + # --packages none avoids building the (app-only) AppImage that + # --packages otherwise defaults to. + - name: Build CLI binary + id: build_cli + run: script/bundle --channel "$CHANNEL" --artifact cli --packages none + shell: bash + env: + CHANNEL: ${{ steps.get-config.outputs.channel }} + GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + NO_LICENSES: "1" + SKIP_SETTINGS_SCHEMA: "1" + + # Unlike the TUI job above, the cli artifact doesn't stage a copy into + # bundle/linux, so executable_path and debug_executable_path are + # already the raw target//[.debug] Cargo output paths; + # no derivation is needed to satisfy --skip-build below. + - name: Create tar archive with build artifacts + run: | + EXECUTABLE_PATH="$(realpath --relative-to=$(pwd) "$EXECUTABLE_PATH")" + DEBUG_EXECUTABLE_PATH="$(realpath --relative-to=$(pwd) "$DEBUG_EXECUTABLE_PATH")" + tar czvf build-artifacts.tar.gz "$EXECUTABLE_PATH" "$DEBUG_EXECUTABLE_PATH" + shell: bash + env: + EXECUTABLE_PATH: ${{ steps.build_cli.outputs.executable_path }} + DEBUG_EXECUTABLE_PATH: ${{ steps.build_cli.outputs.debug_executable_path }} + + - name: Archive Linux CLI build artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: linux-cli-x86_64-${{ steps.get-config.outputs.channel }} + retention-days: 1 + path: build-artifacts.tar.gz + release_linux_cli_x86: name: Build Release (Linux CLI x86_64) runs-on: namespace-profile-ubuntu-22-04 - needs: [prepare_release, release_linux_x86] + needs: [prepare_release, release_linux_x86, build_linux_cli_x86_binaries] if: ${{ inputs.build_linux != false }} timeout-minutes: 90 steps: @@ -1224,6 +1405,15 @@ jobs: fi shell: bash + - name: Download Linux CLI build artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: linux-cli-x86_64-${{ steps.get-config.outputs.channel }} + + - name: Extract Linux CLI build artifacts + run: tar xzvf build-artifacts.tar.gz + shell: bash + - name: Download canonical Linux settings schema uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -1233,8 +1423,8 @@ jobs: - name: Bundle CLI id: bundle_cli run: | - # Build CLI only - script/bundle --channel $CHANNEL --artifact cli --packages deb,rpm + # Package the already-built CLI binary + script/bundle --channel $CHANNEL --skip-build --artifact cli --packages deb,rpm shell: bash env: CHANNEL: ${{ steps.get-config.outputs.channel }} From b0142ed074b576efb04df7e59232edcabc95da8d Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Sat, 22 Aug 2026 01:49:05 +0000 Subject: [PATCH 10/16] Replace gate_tui_release_artifacts with a single collect_results job gate_tui_release_artifacts only existed because the TUI jobs were never added to generate_changelogs' own needs list; it also treated a deliberately skipped platform (build_macos=false, build_linux=false, or web on the preview channel) the same as a genuine failure, since it compared job results directly to 'success' with no notion of 'this platform was never supposed to run'. Replace it with collect_results, which aggregates every build/release job (TUI and non-TUI alike) into one place: - if: always() so it still runs, and still reports failures, when any of its dependencies did not succeed. - A platform disabled via a workflow input is excluded outright, regardless of its (necessarily 'skipped') result. Any other non-'success' result -- a genuine failure, a cancellation, or a job skipped because one of its own dependencies (e.g. a build-only job) failed -- counts as a failure. - The job itself fails when there is a genuine failure, which is the publish gate: generate_changelogs needs collect_results with default (success-required) semantics, so it is skipped whenever collect_results fails, same as any other failed dependency. should_publish is still independently honored via generate_changelogs' own if: condition. - The failure list is exposed as a job output that send_slack_notif_if_unsuccessful now consumes directly, instead of recomputing it from a second, separately-maintained list of the same jobs. That job keeps if: always() so it can still report on a collect_results failure. The existing web-failure-ignored-on-preview special case, and the exact user-visible failure-name strings, are preserved. generate_changelogs' needs list collapses from nine individual job dependencies plus gate_tui_release_artifacts down to prepare_release (for its release_tag/release_branch/should_publish outputs) and collect_results (the sole success/failure gate). --- .github/workflows/create_release.yml | 167 ++++++++++++++------------- 1 file changed, 89 insertions(+), 78 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index a5c338a67cb..1fb65e3a7cb 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -2507,8 +2507,18 @@ jobs: name: release-windows-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.installer_path }} - send_slack_notif_if_unsuccessful: - name: Send Slack notification if any release jobs were unsuccessful + # Aggregates the result of every build/release job into a single place, so + # downstream jobs don't each need their own copy of the same "which + # platforms actually matter, and did they succeed" logic. A platform whose + # build was deliberately disabled via a workflow input (build_macos, + # build_linux, build_web, or web-on-preview) is excluded entirely, rather + # than being treated as a failure. Anything else that isn't a plain + # `success` -- a genuine failure, a cancellation, or a job skipped because + # one of ITS OWN dependencies failed (e.g. a build-only job) -- counts as a + # failure. `if: always()` is required so this job still runs, and still + # reports failures, even when one of the jobs it depends on did not succeed. + collect_results: + name: Collect release job results runs-on: ubuntu-latest needs: - prepare_release @@ -2523,55 +2533,92 @@ jobs: - release_web - release_windows_tui - release_windows - if: ${{ always() && needs.prepare_release.outputs.should_publish == 'true' }} + if: always() + outputs: + failed: ${{ steps.collect.outputs.failed }} steps: - - name: Figure out which releases failed, if any - id: failed_releases + # This job intentionally fails (after recording `failed` as an output) + # when any non-disabled platform did not succeed. That failure is the + # publish gate: generate_changelogs needs this job with default + # (success-required) semantics, so it is skipped whenever this step + # fails, exactly like any other failed dependency. Jobs that only need + # to observe the failure list regardless of outcome (e.g. the Slack + # notification below) declare `if: always()` on themselves instead. + - name: Determine which release jobs genuinely failed + id: collect shell: bash + env: + BUILD_MACOS: ${{ inputs.build_macos }} + BUILD_LINUX: ${{ inputs.build_linux }} + BUILD_WEB: ${{ inputs.build_web }} + CHANNEL: ${{ inputs.channel }} + MACOS_SINGLE_ARCH_RESULT: ${{ needs.release_macos_single_arch.result }} + MACOS_UNIVERSAL_RESULT: ${{ needs.release_macos_universal.result }} + MACOS_CLI_RESULT: ${{ needs.release_macos_cli.result }} + MACOS_TUI_RESULT: ${{ needs.release_macos_tui.result }} + LINUX_TUI_RESULT: ${{ needs.release_linux_tui.result }} + LINUX_X86_RESULT: ${{ needs.release_linux_x86.result }} + LINUX_ARM_RESULT: ${{ needs.release_linux_arm.result }} + LINUX_CLI_X86_RESULT: ${{ needs.release_linux_cli_x86.result }} + WEB_RESULT: ${{ needs.release_web.result }} + WINDOWS_TUI_RESULT: ${{ needs.release_windows_tui.result }} + WINDOWS_RESULT: ${{ needs.release_windows.result }} run: | FAILED=() - if [[ ${{ needs.release_macos_single_arch.result }} != 'success' ]]; then - FAILED+=("MacOS Single Arch") - fi - if [[ ${{ needs.release_macos_universal.result }} != 'success' ]]; then - FAILED+=("MacOS Universal") - fi - if [[ ${{ needs.release_macos_cli.result }} != 'success' ]]; then - FAILED+=('MacOS CLI') - fi - if [[ ${{ needs.release_macos_tui.result }} != 'success' ]]; then - FAILED+=('MacOS TUI') - fi - if [[ ${{ needs.release_linux_tui.result }} != 'success' ]]; then - FAILED+=('Linux TUI') - fi - if [[ ${{ needs.release_linux_x86.result }} != 'success' ]]; then - FAILED+=("Linux x86_64") - fi - if [[ ${{ needs.release_linux_arm.result }} != 'success' ]]; then - FAILED+=("Linux ARM64") - fi - if [[ ${{ needs.release_linux_cli_x86.result }} != 'success' ]]; then - FAILED+=("Linux CLI x86_64") - fi - if [[ ${{ needs.release_web.result }} != 'success' && ${{ inputs.channel }} != 'preview' ]]; then - FAILED+=("WASM") - fi - if [[ ${{ needs.release_windows.result }} != 'success' ]]; then - FAILED+=("Windows") - fi - if [[ ${{ needs.release_windows_tui.result }} != 'success' ]]; then - FAILED+=("Windows TUI") - fi + + # A disabled platform is excluded outright, regardless of its + # result (which will be "skipped" anyway, since that's what a + # false job-level `if:` produces). Otherwise, anything other than + # "success" -- including "skipped", which for a non-disabled + # platform only happens because one of its own dependencies + # failed -- is a genuine failure. + check() { + local name="$1" result="$2" disabled="$3" + if [[ "$disabled" == "true" ]]; then + return + fi + if [[ "$result" != "success" ]]; then + FAILED+=("$name") + fi + } + + MACOS_DISABLED=false + [[ "$BUILD_MACOS" == "false" ]] && MACOS_DISABLED=true + LINUX_DISABLED=false + [[ "$BUILD_LINUX" == "false" ]] && LINUX_DISABLED=true + WEB_DISABLED=false + [[ "$BUILD_WEB" == "false" || "$CHANNEL" == "preview" ]] && WEB_DISABLED=true + + check "MacOS Single Arch" "$MACOS_SINGLE_ARCH_RESULT" "$MACOS_DISABLED" + check "MacOS Universal" "$MACOS_UNIVERSAL_RESULT" "$MACOS_DISABLED" + check "MacOS CLI" "$MACOS_CLI_RESULT" "$MACOS_DISABLED" + check "MacOS TUI" "$MACOS_TUI_RESULT" "$MACOS_DISABLED" + check "Linux TUI" "$LINUX_TUI_RESULT" "$LINUX_DISABLED" + check "Linux x86_64" "$LINUX_X86_RESULT" "$LINUX_DISABLED" + check "Linux ARM64" "$LINUX_ARM_RESULT" "$LINUX_DISABLED" + check "Linux CLI x86_64" "$LINUX_CLI_X86_RESULT" "$LINUX_DISABLED" + check "WASM" "$WEB_RESULT" "$WEB_DISABLED" + check "Windows TUI" "$WINDOWS_TUI_RESULT" "false" + check "Windows" "$WINDOWS_RESULT" "false" if (( ${#FAILED[@]} )); then printf -v FORMATTED "%s, " "${FAILED[@]}" - echo "failed=$(echo "{${FORMATTED%, }}")" >> $GITHUB_OUTPUT + echo "failed=$(echo "{${FORMATTED%, }}")" >> "$GITHUB_OUTPUT" + echo "::error::The following release jobs did not succeed: ${FAILED[*]}" + exit 1 fi + send_slack_notif_if_unsuccessful: + name: Send Slack notification if any release jobs were unsuccessful + runs-on: ubuntu-latest + needs: + - prepare_release + - collect_results + if: ${{ always() && needs.prepare_release.outputs.should_publish == 'true' }} + steps: - name: Send Slack notification if unsuccessful uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5 - if: ${{ steps.failed_releases.outputs.failed != '' }} + if: ${{ needs.collect_results.outputs.failed != '' }} with: # Fail the step when Slack rejects the message. errors: true @@ -2581,35 +2628,7 @@ jobs: channel: "#oncall-client" # Follows https://api.slack.com/reference/surfaces/formatting#mentioning-users. # S06N82RS7FA is the user group ID for @oncall-client-eng. - text: "Failed to create ${{ steps.failed_releases.outputs.failed }} release artifacts for ${{ inputs.channel }}. See https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}. cc: " - - gate_tui_release_artifacts: - name: Gate changelog publication on TUI release artifacts - runs-on: ubuntu-latest - needs: - - prepare_release - - release_macos_tui - - release_linux_tui - - release_windows_tui - if: ${{ always() }} - steps: - - name: Verify TUI release artifacts - shell: bash - env: - SHOULD_PUBLISH: ${{ needs.prepare_release.outputs.should_publish }} - MACOS_TUI_RESULT: ${{ needs.release_macos_tui.result }} - LINUX_TUI_RESULT: ${{ needs.release_linux_tui.result }} - WINDOWS_TUI_RESULT: ${{ needs.release_windows_tui.result }} - run: | - if [[ "$SHOULD_PUBLISH" != "true" ]]; then - exit 0 - fi - if [[ "$MACOS_TUI_RESULT" != "success" || - "$LINUX_TUI_RESULT" != "success" || - "$WINDOWS_TUI_RESULT" != "success" ]]; then - echo "::error::TUI release artifacts were not published successfully (macOS: $MACOS_TUI_RESULT, Linux: $LINUX_TUI_RESULT, Windows: $WINDOWS_TUI_RESULT)" - exit 1 - fi + text: "Failed to create ${{ needs.collect_results.outputs.failed }} release artifacts for ${{ inputs.channel }}. See https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}. cc: " generate_changelogs: name: Generate Changelogs @@ -2617,15 +2636,7 @@ jobs: runs-on: ubuntu-latest needs: - prepare_release - - release_macos_single_arch - - release_macos_universal - - release_macos_cli - - release_linux_x86 - - release_linux_arm - - release_linux_cli_x86 - - release_web - - release_windows - - gate_tui_release_artifacts + - collect_results steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 name: Checkout sources From 19e5519598ababfc201f00945e50435fdbe46660 Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Sat, 22 Aug 2026 01:54:24 +0000 Subject: [PATCH 11/16] Fix collect_results: honor build_windows as a disable input collect_results passed a literal false for both Windows leaf jobs' disabled flag instead of deriving it from inputs.build_windows the same way as build_macos/build_linux/build_web. With Windows disabled, both Windows release jobs are correctly skipped by their own if:, but collect_results treated that skip as a genuine failure, failed the job, and blocked generate_changelogs (plus fired a false Slack notification) for an otherwise-publishable partial-platform release. The complete set of disable inputs enumerated from the workflow's own workflow_call/workflow_dispatch inputs blocks: build_linux, build_windows, build_macos, build_web. (should_publish is a separate publish gate, not a per-platform disable switch.) All four are now represented. --- .github/workflows/create_release.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 1fb65e3a7cb..c2e2fda04fa 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -2511,12 +2511,13 @@ jobs: # downstream jobs don't each need their own copy of the same "which # platforms actually matter, and did they succeed" logic. A platform whose # build was deliberately disabled via a workflow input (build_macos, - # build_linux, build_web, or web-on-preview) is excluded entirely, rather - # than being treated as a failure. Anything else that isn't a plain - # `success` -- a genuine failure, a cancellation, or a job skipped because - # one of ITS OWN dependencies failed (e.g. a build-only job) -- counts as a - # failure. `if: always()` is required so this job still runs, and still - # reports failures, even when one of the jobs it depends on did not succeed. + # build_linux, build_windows, build_web, or web-on-preview) is excluded + # entirely, rather than being treated as a failure. Anything else that + # isn't a plain `success` -- a genuine failure, a cancellation, or a job + # skipped because one of ITS OWN dependencies failed (e.g. a build-only + # job) -- counts as a failure. `if: always()` is required so this job + # still runs, and still reports failures, even when one of the jobs it + # depends on did not succeed. collect_results: name: Collect release job results runs-on: ubuntu-latest @@ -2550,6 +2551,7 @@ jobs: env: BUILD_MACOS: ${{ inputs.build_macos }} BUILD_LINUX: ${{ inputs.build_linux }} + BUILD_WINDOWS: ${{ inputs.build_windows }} BUILD_WEB: ${{ inputs.build_web }} CHANNEL: ${{ inputs.channel }} MACOS_SINGLE_ARCH_RESULT: ${{ needs.release_macos_single_arch.result }} @@ -2586,6 +2588,8 @@ jobs: [[ "$BUILD_MACOS" == "false" ]] && MACOS_DISABLED=true LINUX_DISABLED=false [[ "$BUILD_LINUX" == "false" ]] && LINUX_DISABLED=true + WINDOWS_DISABLED=false + [[ "$BUILD_WINDOWS" == "false" ]] && WINDOWS_DISABLED=true WEB_DISABLED=false [[ "$BUILD_WEB" == "false" || "$CHANNEL" == "preview" ]] && WEB_DISABLED=true @@ -2598,8 +2602,8 @@ jobs: check "Linux ARM64" "$LINUX_ARM_RESULT" "$LINUX_DISABLED" check "Linux CLI x86_64" "$LINUX_CLI_X86_RESULT" "$LINUX_DISABLED" check "WASM" "$WEB_RESULT" "$WEB_DISABLED" - check "Windows TUI" "$WINDOWS_TUI_RESULT" "false" - check "Windows" "$WINDOWS_RESULT" "false" + check "Windows TUI" "$WINDOWS_TUI_RESULT" "$WINDOWS_DISABLED" + check "Windows" "$WINDOWS_RESULT" "$WINDOWS_DISABLED" if (( ${#FAILED[@]} )); then printf -v FORMATTED "%s, " "${FAILED[@]}" From ea93d5994c81bc362e1d60faae18db22a8f553f3 Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Sat, 22 Aug 2026 02:24:36 +0000 Subject: [PATCH 12/16] Address David's review: prune unused code, defensive comments, and dead params Six items from David's review pass: - Removed the 'Setup Go' step from the two new build-only jobs (build_macos_cli_binaries, build_macos_tui_binaries). It was copied wholesale from the packaging jobs they were split off from, but nothing in the actual build path (cargo build, prepare_bundled_resources) needs a Go toolchain. Left the packaging jobs' pre-existing Setup Go steps alone, since those predate this PR's TUI/CLI split work. - Rewrote five 'Builds the X binary only, with no dependency on , so ...' comments (build_macos_cli_binaries, build_macos_tui_binaries, build_linux_tui_binaries, build_linux_cli_x86_binaries, build_windows_tui_binaries) to drop the defensive 'no dependency on' framing while keeping the actual rationale (packaging happens in a separate job so the build can run in parallel instead of serializing). - Added a blank line between two steps in build_windows_tui_binaries that were missing one. - Removed the unused cargo_profile parameter from script/prepare_bundled_resources (and its Windows PowerShell counterpart): the value was accepted and stored but never read anywhere in either script. Its doc comment claimed it was 'retained for compatibility with existing callers' without naming them; the real callers (script/linux/bundle, script/linux/bundle_install, script/macos/bundle x3, script/windows/bundle.ps1) do exist, but since the parameter does nothing on the receiving end, updated all of them to stop passing it instead of keeping a misleading claim. - Deleted script/test_run_tui_settings_schema. It requires a full cargo build (several minutes) to prove two things: that warp-tui-oss wires up its dump-settings-schema subcommand to the same function schema_generation_tests.rs already unit-tests, and that the resulting schema activates the modify-settings skill -- which reduces to checking settings_schema.json exists, already exercised (with a fake executable) by script/test_prepare_bundled_resources's SETTINGS_SCHEMA_EXECUTABLE case. It isn't wired into presubmit or any CI workflow. The one-time verification it was added for has already happened; the cost of keeping it running (and its real-build dependency) outweighs the thin, mostly redundant regression coverage it provides. - Kept script/test_prepare_bundled_resources: it directly exercises the core branching contract of prepare_bundled_resources (the SETTINGS_SCHEMA_EXECUTABLE / SETTINGS_SCHEMA_SOURCE / SKIP_SETTINGS_SCHEMA paths and their mutual-exclusivity and missing-input failure modes) that this PR's cross-compile and host-detection fixes have repeatedly touched, at near-zero cost (no real build, self-contained fake executable). - Added a brief comment on release_linux_tui (previously the only packaging job in the split with no comment of its own) explaining why it exists as a separate job from build_linux_tui_binaries. Verified: actionlint 18 findings, unchanged; ./script/format --check clean; bash -n on all touched shell scripts; PowerShell parser confirms no syntax errors in both touched .ps1 files; ./script/test_prepare_bundled_resources still passes after the cargo_profile parameter removal. --- .github/workflows/create_release.yml | 67 ++++++++------------ script/linux/bundle | 2 +- script/linux/bundle_install | 2 +- script/macos/bundle | 6 +- script/prepare_bundled_resources | 11 ++-- script/test_run_tui_settings_schema | 46 -------------- script/windows/bundle.ps1 | 2 +- script/windows/prepare_bundled_resources.ps1 | 5 +- 8 files changed, 39 insertions(+), 102 deletions(-) delete mode 100755 script/test_run_tui_settings_schema diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index c2e2fda04fa..7d0e69efad8 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -435,12 +435,11 @@ jobs: name: release-macos-universal-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.dmg_path }} - # Builds the CLI binary only, with no dependency on release_macos_single_arch, - # so this heavy build+notarize step keeps running in parallel with the app - # build instead of serializing behind it. Packaging (which needs the - # canonical schema release_macos_single_arch produces) happens in - # release_macos_cli below, mirroring the build_macos_tui_binaries / - # release_macos_tui split. + # Builds the CLI binary only. Packaging in release_macos_cli below adds the + # canonical schema and performs the actual signing and notarization; + # splitting the build out lets it run in parallel with the app build + # instead of serializing behind it, mirroring the build_macos_tui_binaries + # / release_macos_tui split. build_macos_cli_binaries: name: Build Release Binary (macOS CLI ${{ matrix.arch }}) runs-on: macos-26-xlarge @@ -468,12 +467,6 @@ jobs: run: rustup target add ${{ matrix.arch }}-apple-darwin shell: bash - # Install go toolchain, as it may be needed for some build steps. - - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 - with: - go-version: stable - - name: Get channel configuration id: get-config uses: ./.github/actions/get_channel_config/ @@ -644,11 +637,10 @@ jobs: env: DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_cli.outputs.dsym_path }} - # Builds the TUI binary only, with no dependency on release_macos_single_arch, - # so this heavy build+notarize step keeps running in parallel with the app - # build instead of serializing behind it. Packaging (which needs the - # canonical schema release_macos_single_arch produces) happens in - # release_macos_tui below, mirroring the build_windows_binaries / + # Builds the TUI binary only. Packaging in release_macos_tui below adds the + # canonical schema and performs the actual signing and notarization; + # splitting the build out lets it run in parallel with the app build + # instead of serializing behind it, mirroring the build_windows_binaries / # release_windows split. build_macos_tui_binaries: name: Build Release Binary (macOS TUI ${{ matrix.arch }}) @@ -677,12 +669,6 @@ jobs: run: rustup target add ${{ matrix.arch }}-apple-darwin shell: bash - # Install go toolchain, as it may be needed for some build steps. - - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 - with: - go-version: stable - - name: Get channel configuration id: get-config uses: ./.github/actions/get_channel_config/ @@ -864,11 +850,10 @@ jobs: name: release-macos-tui-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} path: warp-tui-${{ steps.get-config.outputs.channel }}-macos-${{ matrix.arch }}.tar.gz - # Builds the TUI binary only, with no dependency on release_linux_x86, so - # this native, per-arch build keeps running in parallel with the app build - # instead of serializing behind it. Packaging (which needs the canonical - # schema release_linux_x86 produces) happens in release_linux_tui below, - # mirroring the build_windows_binaries / release_windows split. + # Builds the TUI binary only. Packaging in release_linux_tui below adds the + # canonical schema; splitting the build out lets this native, per-arch + # build run in parallel with the app build instead of serializing behind + # it, mirroring the build_windows_binaries / release_windows split. build_linux_tui_binaries: name: Build Release Binary (Linux TUI ${{ matrix.arch }}) runs-on: ${{ matrix.runner }} @@ -953,6 +938,11 @@ jobs: retention-days: 1 path: build-artifacts.tar.gz + # Packaging step for the binary build_linux_tui_binaries builds above: it + # downloads that build output, adds the canonical schema release_linux_x86 + # produces, and does the actual bundling and upload. Kept separate so the + # (slow) native per-arch build isn't stuck waiting on that schema before it + # can start. release_linux_tui: name: Build Release (Linux TUI ${{ matrix.arch }}) runs-on: ${{ matrix.runner }} @@ -1244,11 +1234,10 @@ jobs: name: release-linux-x86_64-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.packages_dir }} - # Builds the CLI binary only, with no dependency on release_linux_x86, so - # this build keeps running in parallel with the app build instead of - # serializing behind it. Packaging (which needs the canonical schema - # release_linux_x86 produces) happens in release_linux_cli_x86 below, - # mirroring the build_linux_tui_binaries / release_linux_tui split. + # Builds the CLI binary only. Packaging in release_linux_cli_x86 below adds + # the canonical schema; splitting the build out lets it run in parallel + # with the app build instead of serializing behind it, mirroring the + # build_linux_tui_binaries / release_linux_tui split. build_linux_cli_x86_binaries: name: Build Release Binary (Linux CLI x86_64) runs-on: namespace-profile-ubuntu-22-04 @@ -2077,12 +2066,11 @@ jobs: name: release-web-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.packages_dir }} - # Builds the TUI binary only, with no dependency on build_windows_binaries, - # so this build keeps running in parallel with the app build instead of - # serializing behind it. Packaging (which needs the canonical schema - # build_windows_binaries produces) happens in release_windows_tui below, - # following the same split build_windows_binaries already uses for - # release_windows. + # Builds the TUI binary only. Packaging in release_windows_tui below adds + # the canonical schema and performs the actual signing; splitting the + # build out lets it run in parallel with the app build instead of + # serializing behind it, following the same split build_windows_binaries + # already uses for release_windows. build_windows_tui_binaries: name: Build Release Binary (Windows TUI ${{ matrix.arch }}) runs-on: windows-latest-large @@ -2106,6 +2094,7 @@ jobs: ref: ${{ needs.prepare_release.outputs.release_branch }} install_release_deps: true ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} + - name: Add arm64 target if: ${{ matrix.arch == 'arm64' }} run: rustup target add aarch64-pc-windows-msvc diff --git a/script/linux/bundle b/script/linux/bundle index d05014e98ab..696c8e0f911 100755 --- a/script/linux/bundle +++ b/script/linux/bundle @@ -384,7 +384,7 @@ fi if [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" || "$ARTIFACT" == "tui" ]]; then echo "Preparing standalone resources directory" BUNDLED_RESOURCES_DIR="$OUT_DIR/resources" - "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" + "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" fi diff --git a/script/linux/bundle_install b/script/linux/bundle_install index 818d596338a..78a72e53fc7 100755 --- a/script/linux/bundle_install +++ b/script/linux/bundle_install @@ -44,4 +44,4 @@ if [[ "$ARTIFACT" == "app" ]]; then fi # Prepare the bundled resources directory. -"$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "${TARGET_DIR}/${OPT_DIR}/resources" "$RELEASE_CHANNEL" "$CARGO_PROFILE" +"$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "${TARGET_DIR}/${OPT_DIR}/resources" "$RELEASE_CHANNEL" diff --git a/script/macos/bundle b/script/macos/bundle index 66aaf18c4f1..aa1a141f749 100755 --- a/script/macos/bundle +++ b/script/macos/bundle @@ -707,7 +707,7 @@ if [[ "$ARTIFACT" == "app" ]]; then export SETTINGS_SCHEMA_EXECUTABLE="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/MacOS/$WARP_BIN" fi fi - "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" + "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$WORKSPACE_ROOT_DIR/script/compile_icon" "$RELEASE_CHANNEL" "$BUNDLE_DIR/$WARP_APP_NAME.app" @@ -818,7 +818,7 @@ elif [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then export SETTINGS_SCHEMA_EXECUTABLE="$OUT_DIR/$WARP_BIN" fi fi - "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" + "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" # Set the primary binary path to output. @@ -852,7 +852,7 @@ elif [[ "$ARTIFACT" == "tui" ]]; then echo "Error: TUI bundles require SETTINGS_SCHEMA_SOURCE or SETTINGS_SCHEMA_EXECUTABLE." >&2 exit 1 fi - "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" + "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" BINARY_PATH="$OUT_DIR/$WARP_BIN" else diff --git a/script/prepare_bundled_resources b/script/prepare_bundled_resources index e1951e0e2c0..2c48dea1688 100755 --- a/script/prepare_bundled_resources +++ b/script/prepare_bundled_resources @@ -6,7 +6,7 @@ # destination directory. It is used by macOS and Linux build scripts. # # Usage: -# prepare_bundled_resources [channel] [cargo_profile] +# prepare_bundled_resources [channel] # # Arguments: # destination_directory: The directory where resources should be installed. @@ -14,8 +14,6 @@ # this path (e.g., $DEST_DIR/skills). # channel: (Optional) Release channel (local, dev, preview, # stable). Used to include channel-gated skills. -# cargo_profile: (Optional) Retained for compatibility with existing -# callers. # # Environment variables: # SKIP_SETTINGS_SCHEMA: Set to "1" to skip generating the JSON settings @@ -30,15 +28,14 @@ set -e -if [ $# -lt 1 ] || [ $# -gt 3 ]; then - echo "Error: Expected 1-3 arguments but received $#" >&2 - echo "Usage: $0 [channel] [cargo_profile]" >&2 +if [ $# -lt 1 ] || [ $# -gt 2 ]; then + echo "Error: Expected 1-2 arguments but received $#" >&2 + echo "Usage: $0 [channel]" >&2 exit 1 fi DEST_DIR="$1" CHANNEL="${2:-}" -CARGO_PROFILE="${3:-}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" RESOURCES_SRC="$REPO_ROOT/resources" diff --git a/script/test_run_tui_settings_schema b/script/test_run_tui_settings_schema deleted file mode 100755 index 5599796efcd..00000000000 --- a/script/test_run_tui_settings_schema +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -# -# Regression test for the `script/run-tui` settings-schema fix. -# -# Builds the real `warp-tui-oss` binary, confirms it implements -# `dump-settings-schema` directly (the standalone TUI binaries used to have no -# such command, which is what caused script/run-tui to fail closed), and then -# runs it through the exact `prepare_bundled_resources` invocation -# script/run-tui uses to confirm the local TUI's `modify-settings` bundled -# skill actually activates: `activation_for_bundled_skill` in -# `app/src/ai/skills/bundled.rs` gates that skill on `settings_schema.json` -# existing in the prepared resources directory, so checking for that file is -# equivalent to checking the skill is active. - -set -e - -WORKSPACE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -TEMP_DIR="$(mktemp -d)" -trap 'rm -rf "$TEMP_DIR"' EXIT - -echo "Building warp-tui-oss..." -cargo build --manifest-path "$WORKSPACE_ROOT/Cargo.toml" -p warp_tui --bin warp-tui-oss \ - --features standalone -TUI_BIN="$(cargo metadata --manifest-path "$WORKSPACE_ROOT/Cargo.toml" --no-deps \ - --format-version 1 | jq -r .target_directory)/debug/warp-tui-oss" - -echo "Checking warp-tui-oss implements dump-settings-schema directly..." -DIRECT_SCHEMA="$TEMP_DIR/direct-settings-schema.json" -"$TUI_BIN" dump-settings-schema "$DIRECT_SCHEMA" -test -s "$DIRECT_SCHEMA" -python3 -c "import json; json.load(open('$DIRECT_SCHEMA'))" - -echo "Checking the local TUI resources activate the modify-settings skill..." -RESOURCES_DIR="$TEMP_DIR/resources" -NO_LICENSES=1 \ - SETTINGS_SCHEMA_EXECUTABLE="$TUI_BIN" \ - "$WORKSPACE_ROOT/script/prepare_bundled_resources" "$RESOURCES_DIR" oss - -# This is the exact file `activation_for_bundled_skill` checks for to decide -# whether the "modify-settings" bundled skill is active; see -# `app/src/ai/skills/bundled.rs`. -test -s "$RESOURCES_DIR/settings_schema.json" -python3 -c "import json; json.load(open('$RESOURCES_DIR/settings_schema.json'))" -test -f "$RESOURCES_DIR/bundled/skills/modify-settings/SKILL.md" - -echo "OK: local TUI resources contain a real settings schema and the modify-settings skill is active." diff --git a/script/windows/bundle.ps1 b/script/windows/bundle.ps1 index 41f2b388fca..fe18894a882 100644 --- a/script/windows/bundle.ps1 +++ b/script/windows/bundle.ps1 @@ -289,7 +289,7 @@ if ($env:SKIP_SETTINGS_SCHEMA -ne '1' -and -not $env:SETTINGS_SCHEMA_EXECUTABLE } $BUNDLED_RESOURCES_DIR = "$CARGO_TARGET_OUTPUT_DIR\resources" Write-Output 'Preparing bundled resources...' -& "$WINDOWS_INSTALLER_DIR\prepare_bundled_resources.ps1" -DestinationDir "$BUNDLED_RESOURCES_DIR" -Channel "$CHANNEL" -CargoProfile "$CARGO_PROFILE" +& "$WINDOWS_INSTALLER_DIR\prepare_bundled_resources.ps1" -DestinationDir "$BUNDLED_RESOURCES_DIR" -Channel "$CHANNEL" if (-Not $?) { Write-Error 'Failed to prepare bundled resources' exit 1 diff --git a/script/windows/prepare_bundled_resources.ps1 b/script/windows/prepare_bundled_resources.ps1 index 058729dace7..6e0183e4921 100644 --- a/script/windows/prepare_bundled_resources.ps1 +++ b/script/windows/prepare_bundled_resources.ps1 @@ -17,10 +17,7 @@ Param( [String]$DestinationDir, [Parameter(Mandatory = $false)] - [String]$Channel = '', - - [Parameter(Mandatory = $false)] - [String]$CargoProfile = '' + [String]$Channel = '' ) $ErrorActionPreference = 'Stop' From 3b47bafdd9d58370f35ac2d03efd043a8405721d Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Sat, 22 Aug 2026 02:41:47 +0000 Subject: [PATCH 13/16] Fix script/run-tui breakage from the cargo_profile removal; tighten split comments The cargo_profile arity change in 074ad8a0 missed three callers because the sweep grepped for the parameter name on a single line rather than counting every call site's actual argument count: - script/run-tui's prepare_tui_bundled_resources branched on $PROFILE_DIR = debug, and only the else branch (any non-debug profile, e.g. --release) passed a third positional argument. That branch now dies immediately with 'Expected 1-2 arguments but received 3' on the exact path that isn't exercised by a plain debug run. Since the parameter is gone, both branches are identical now, so the if/else collapses to a single call. - script/deploy_remote_server and script/deploy_remote_server_to_test_vm both still passed "$CARGO_PROFILE" as a third argument on its own continuation line, which the earlier single-line grep for 'prepare_bundled_resources.*CARGO_PROFILE' never matched. - flake.nix's Nix build script passed a literal 'release' as a third argument, for the same reason. Verified the run-tui fix for real, not just by reasoning about it: freed space on the build cache volume (it was at 100%, unrelated to this change, from accumulated incremental build artifacts), built the real warp-tui-oss release binary, then ran the exact SETTINGS_SCHEMA_EXECUTABLE + 2-argument prepare_bundled_resources invocation the now-unified prepare_tui_bundled_resources performs for a non-debug profile against that real binary, and confirmed it generates a valid settings_schema.json. Also tightened the build/package split comments per feedback that they were still too long and, for the Linux TUI pair, explained the same split twice from both sides. Each pair now gets exactly one 1-2 line comment on the packaging job (release_macos_cli, release_macos_tui, release_linux_tui, release_linux_cli_x86, release_windows_tui), stating only what's packaged here and where the binary came from; the build-only jobs are no longer separately commented, and the 'mirroring the X / Y split' tails are gone from all five. Re-audited every prepare_bundled_resources / prepare_bundled_resources.ps1 caller by argument count rather than by name: script/macos/bundle (x3), script/linux/bundle, script/linux/bundle_install, script/windows/bundle.ps1, script/macos/run, script/test_prepare_bundled_resources, script/test_factory_files_skill.py, script/run-tui, script/deploy_remote_server, script/deploy_remote_server_to_test_vm, and flake.nix -- all now pass at most 2 positional arguments (or the .ps1 equivalent), matching what both scripts accept. --- .github/workflows/create_release.yml | 38 +++++++------------------- flake.nix | 3 +- script/deploy_remote_server | 3 +- script/deploy_remote_server_to_test_vm | 3 +- script/run-tui | 12 ++------ 5 files changed, 16 insertions(+), 43 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 7d0e69efad8..587bf6022f2 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -435,11 +435,6 @@ jobs: name: release-macos-universal-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.dmg_path }} - # Builds the CLI binary only. Packaging in release_macos_cli below adds the - # canonical schema and performs the actual signing and notarization; - # splitting the build out lets it run in parallel with the app build - # instead of serializing behind it, mirroring the build_macos_tui_binaries - # / release_macos_tui split. build_macos_cli_binaries: name: Build Release Binary (macOS CLI ${{ matrix.arch }}) runs-on: macos-26-xlarge @@ -521,6 +516,8 @@ jobs: compression-level: 0 path: build-artifacts.tar.gz + # Packages the CLI binary build_macos_cli_binaries built separately (in + # parallel), then adds the canonical schema and signs/notarizes. release_macos_cli: name: Build Release (macOS CLI ${{ matrix.arch }}) runs-on: macos-26-xlarge @@ -637,11 +634,6 @@ jobs: env: DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_cli.outputs.dsym_path }} - # Builds the TUI binary only. Packaging in release_macos_tui below adds the - # canonical schema and performs the actual signing and notarization; - # splitting the build out lets it run in parallel with the app build - # instead of serializing behind it, mirroring the build_windows_binaries / - # release_windows split. build_macos_tui_binaries: name: Build Release Binary (macOS TUI ${{ matrix.arch }}) runs-on: macos-26-xlarge @@ -723,6 +715,8 @@ jobs: compression-level: 0 path: build-artifacts.tar.gz + # Packages the TUI binary build_macos_tui_binaries built separately (in + # parallel), then adds the canonical schema and signs/notarizes. release_macos_tui: name: Build Release (macOS TUI ${{ matrix.arch }}) runs-on: macos-26-xlarge @@ -850,10 +844,6 @@ jobs: name: release-macos-tui-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} path: warp-tui-${{ steps.get-config.outputs.channel }}-macos-${{ matrix.arch }}.tar.gz - # Builds the TUI binary only. Packaging in release_linux_tui below adds the - # canonical schema; splitting the build out lets this native, per-arch - # build run in parallel with the app build instead of serializing behind - # it, mirroring the build_windows_binaries / release_windows split. build_linux_tui_binaries: name: Build Release Binary (Linux TUI ${{ matrix.arch }}) runs-on: ${{ matrix.runner }} @@ -938,11 +928,8 @@ jobs: retention-days: 1 path: build-artifacts.tar.gz - # Packaging step for the binary build_linux_tui_binaries builds above: it - # downloads that build output, adds the canonical schema release_linux_x86 - # produces, and does the actual bundling and upload. Kept separate so the - # (slow) native per-arch build isn't stuck waiting on that schema before it - # can start. + # Packages the TUI binary build_linux_tui_binaries built separately (in + # parallel), then adds the canonical schema. release_linux_tui: name: Build Release (Linux TUI ${{ matrix.arch }}) runs-on: ${{ matrix.runner }} @@ -1234,10 +1221,6 @@ jobs: name: release-linux-x86_64-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.packages_dir }} - # Builds the CLI binary only. Packaging in release_linux_cli_x86 below adds - # the canonical schema; splitting the build out lets it run in parallel - # with the app build instead of serializing behind it, mirroring the - # build_linux_tui_binaries / release_linux_tui split. build_linux_cli_x86_binaries: name: Build Release Binary (Linux CLI x86_64) runs-on: namespace-profile-ubuntu-22-04 @@ -1316,6 +1299,8 @@ jobs: retention-days: 1 path: build-artifacts.tar.gz + # Packages the CLI binary build_linux_cli_x86_binaries built separately (in + # parallel), then adds the canonical schema. release_linux_cli_x86: name: Build Release (Linux CLI x86_64) runs-on: namespace-profile-ubuntu-22-04 @@ -2066,11 +2051,6 @@ jobs: name: release-web-${{ steps.get-config.outputs.channel }} path: ${{ steps.bundle_app.outputs.packages_dir }} - # Builds the TUI binary only. Packaging in release_windows_tui below adds - # the canonical schema and performs the actual signing; splitting the - # build out lets it run in parallel with the app build instead of - # serializing behind it, following the same split build_windows_binaries - # already uses for release_windows. build_windows_tui_binaries: name: Build Release Binary (Windows TUI ${{ matrix.arch }}) runs-on: windows-latest-large @@ -2124,6 +2104,8 @@ jobs: ${{ steps.build_tui.outputs.binary_path }} ${{ steps.build_tui.outputs.pdb_file_path }} + # Packages the TUI binary build_windows_tui_binaries built separately (in + # parallel), then adds the canonical schema and signs it. release_windows_tui: name: Build Release (Windows TUI ${{ matrix.arch }}) runs-on: windows-latest-large diff --git a/flake.nix b/flake.nix index 59a41548793..ddc7ba3abff 100644 --- a/flake.nix +++ b/flake.nix @@ -187,8 +187,7 @@ SETTINGS_SCHEMA_EXECUTABLE="${installDir}/warp-oss" ./script/prepare_bundled_resources \ "${resourcesDir}" \ - "${releaseChannel}" \ - release + "${releaseChannel}" install -Dm644 \ "${resourcesDir}/THIRD_PARTY_LICENSES.txt" \ diff --git a/script/deploy_remote_server b/script/deploy_remote_server index 2d0f6543ba6..2608c71d04c 100755 --- a/script/deploy_remote_server +++ b/script/deploy_remote_server @@ -149,8 +149,7 @@ echo "==> Build complete ($BINARY_SIZE)" rm -rf "$LOCAL_RESOURCES_DIR" SKIP_SETTINGS_SCHEMA=1 "$WORKSPACE_ROOT/script/prepare_bundled_resources" \ "$LOCAL_RESOURCES_DIR" \ - local \ - "$CARGO_PROFILE" + local # Resolve $HOME on the remote so our upload lands in the same directory the # Warp client checks. The client runs `test -x ~/.warp-local/...` and lets diff --git a/script/deploy_remote_server_to_test_vm b/script/deploy_remote_server_to_test_vm index b529eba73c3..f2a5d8ac412 100755 --- a/script/deploy_remote_server_to_test_vm +++ b/script/deploy_remote_server_to_test_vm @@ -100,8 +100,7 @@ mkdir -p "$LOCAL_BUNDLE_DIR" cp "$BUILT_BINARY" "$LOCAL_BUNDLE_DIR/$BUNDLE_BINARY_NAME" SKIP_SETTINGS_SCHEMA=1 "$WORKSPACE_ROOT/script/prepare_bundled_resources" \ "$LOCAL_BUNDLE_DIR/resources" \ - integration \ - "$CARGO_PROFILE" + integration tar -czf "$LOCAL_BUNDLE_TARBALL" -C "$LOCAL_BUNDLE_DIR" . # ── Upload via SCP through GCP IAP tunnel ───────────────────────── diff --git a/script/run-tui b/script/run-tui index 3bb664f6045..7aa4f230ee1 100755 --- a/script/run-tui +++ b/script/run-tui @@ -118,15 +118,9 @@ BUILD_DIR="${BUILD_DIR}/${PROFILE_DIR}" # generation remains skipped for local runs. prepare_tui_bundled_resources() { echo "Preparing bundled resources at ${BUILD_DIR}/resources (channel: ${CHANNEL})..." - if [ "${PROFILE_DIR}" = "debug" ]; then - NO_LICENSES=1 SETTINGS_SCHEMA_EXECUTABLE="${BUILD_DIR}/${BIN}" \ - "${REPO_ROOT}/script/prepare_bundled_resources" \ - "${BUILD_DIR}/resources" "${CHANNEL}" - else - NO_LICENSES=1 SETTINGS_SCHEMA_EXECUTABLE="${BUILD_DIR}/${BIN}" \ - "${REPO_ROOT}/script/prepare_bundled_resources" \ - "${BUILD_DIR}/resources" "${CHANNEL}" "${PROFILE_DIR}" - fi + NO_LICENSES=1 SETTINGS_SCHEMA_EXECUTABLE="${BUILD_DIR}/${BIN}" \ + "${REPO_ROOT}/script/prepare_bundled_resources" \ + "${BUILD_DIR}/resources" "${CHANNEL}" } if [[ "$(uname -s)" = "Darwin" ]]; then From e81d8b99901db2d25dcb4f51dd40fb73a0733347 Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Sat, 22 Aug 2026 02:55:50 +0000 Subject: [PATCH 14/16] Fix stale comment in test_prepare_bundled_resources The comment justifying the SKIP_SETTINGS_SCHEMA=1 test case said local TUI binaries don't implement dump-settings-schema. That was true when written, but this PR added that subcommand to the TUI binaries themselves (crates/warp_tui/src/session.rs) and script/run-tui now calls it, so the comment is a false explanation left behind by a capability change it predates. The flag itself is still genuinely exercised elsewhere (script/macos/run, script/linux/test_bundle_warpctrl, and the release workflow's build-only jobs all set it to defer schema generation to a later packaging step), so the test case stays; only the justification was wrong. Replaced it with that real reason, kept to one line. Swept every comment this PR touched or added, across the workflow, the bundle/deploy scripts, and the Rust settings/CLI code, for the same kind of staleness against the now-final TUI schema capability. Found nothing else: script/run-tui's own function comment, the DumpSettingsSchema doc comments in crates/warp_tui and crates/warp_cli, the macOS/Linux cross-arch schema comparison caveats, and the PR description's TUI bullet all already describe the current, post-iteration state accurately. bash -n and a re-run of script/test_prepare_bundled_resources both pass. --- script/test_prepare_bundled_resources | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/script/test_prepare_bundled_resources b/script/test_prepare_bundled_resources index dcda01d257c..e9a8dad1bcc 100755 --- a/script/test_prepare_bundled_resources +++ b/script/test_prepare_bundled_resources @@ -43,8 +43,7 @@ if NO_LICENSES=1 \ exit 1 fi -# Local TUI runs (script/run-tui) skip schema generation entirely, since the TUI -# binaries don't implement dump-settings-schema. +# SKIP_SETTINGS_SCHEMA=1 lets build-only jobs defer schema generation to a later packaging step. SKIPPED_DESTINATION="$TEMP_DIR/skipped-schema" NO_LICENSES=1 \ SKIP_SETTINGS_SCHEMA=1 \ From 4d5e71b632d6bf47803031028cc158510486982c Mon Sep 17 00:00:00 2001 From: Warp Agent Date: Sat, 22 Aug 2026 03:52:19 +0000 Subject: [PATCH 15/16] Fix Windows TUI artifact handoff: tar with explicit relative paths build_windows_tui_binaries uploaded binary_path and pdb_file_path directly. Both live in the same target// directory, so actions/upload-artifact collapsed the least common ancestor to that directory and stripped the prefix. release_windows_tui downloaded with path: ., landing the exe at the workspace root, while its own script/bundle invocation (--skip_build_binary --skip_build_installer) recomputes binary_path as the full target/... path. The Trusted Signing step then failed with 'File not found' on a real release run -- the app asset DLLs signed fine, only the TUI binary was missing. The pre-existing app flow (build_windows_binaries / release_windows) doesn't hit this because it uploads a third, workspace-relative path (app/assets/windows/) alongside the same-directory binary_path/ pdb_file_path pair. That third path drags the LCA up to the workspace root, which preserves the full relative structure for every file in the artifact -- an accidental property of which paths happen to be listed, not a guarantee. The Windows TUI split copied the two-path shape without that accidental third path, so it lost the guarantee. Fixed by tar-ing with explicit relative paths, the same approach already used for every macOS/Linux split job in this PR: convert the Windows absolute path to POSIX with cygpath -u, compute it relative to the job's cwd with realpath, then tar/extract that. This makes the preserved structure an explicit property of the archive contents rather than an emergent property of upload-artifact's LCA algorithm. Verified the realpath+tar+extract chain directly (not just by reasoning): simulated the exact target/// layout in an isolated directory, ran the same realpath --relative-to and tar/extract commands the workflow now runs, and confirmed both files land back at the identical relative path a fresh consumer's script/bundle invocation expects. cygpath -u's Windows-path-to-POSIX translation itself is a standard, long-established git-bash utility I could not exercise directly in this (Linux) environment, since no Windows runner was available to me. Audited every upload/download artifact pair introduced by this PR's platform splits, per the specific question of whether the structure its consumer resolves is guaranteed or incidental: - macOS CLI (build_macos_cli_binaries -> release_macos_cli): guaranteed. Uploads a single build-artifacts.tar.gz; the internal layout is set entirely by the paths passed to , so upload-artifact's LCA computation over a one-element path list never enters into it. - macOS TUI (build_macos_tui_binaries -> release_macos_tui): guaranteed, same reasoning. - Linux CLI x86_64 (build_linux_cli_x86_binaries -> release_linux_cli_x86): guaranteed, same reasoning (paths made relative via realpath before tar-ing). - Linux TUI (build_linux_tui_binaries -> release_linux_tui): guaranteed, same reasoning. - Windows TUI (build_windows_tui_binaries -> release_windows_tui): was incidental (relied on the two paths happening to share a directory in a way that made the bug latent rather than immediate); now guaranteed via the same tar approach. actionlint: 18 findings, unchanged. --- .github/workflows/create_release.yml | 29 ++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 587bf6022f2..17e1eebb6ce 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -2095,14 +2095,32 @@ jobs: CHANNEL: ${{ steps.get-config.outputs.channel }} GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} + # actions/upload-artifact stores paths relative to the least common + # ancestor of everything it's given; binary_path and pdb_file_path live + # in the same directory, so uploading them directly would collapse the + # LCA to that directory and strip the target// prefix + # that release_windows_tui's own script/bundle invocation expects to + # find them at. Tar with explicit relative paths instead, so the + # structure is guaranteed rather than an emergent property of which + # paths happen to be listed. + - name: Create tar archive with build artifacts + run: | + BINARY_PATH="$(realpath --relative-to="$(pwd)" "$(cygpath -u "$BINARY_PATH")")" + PDB_FILE_PATH="$(realpath --relative-to="$(pwd)" "$(cygpath -u "$PDB_FILE_PATH")")" + tar czfv build-artifacts.tar.gz "$BINARY_PATH" "$PDB_FILE_PATH" + shell: bash + env: + BINARY_PATH: ${{ steps.build_tui.outputs.binary_path }} + PDB_FILE_PATH: ${{ steps.build_tui.outputs.pdb_file_path }} + - name: Archive Windows TUI binary build uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: windows-tui-binary-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} retention-days: 1 - path: | - ${{ steps.build_tui.outputs.binary_path }} - ${{ steps.build_tui.outputs.pdb_file_path }} + # We compress the tar archive, so no need to re-compress. + compression-level: 0 + path: build-artifacts.tar.gz # Packages the TUI binary build_windows_tui_binaries built separately (in # parallel), then adds the canonical schema and signs it. @@ -2147,7 +2165,10 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: windows-tui-binary-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }} - path: . + + - name: Extract Windows TUI binary build + run: tar xzvf build-artifacts.tar.gz + shell: bash - name: Download canonical Windows settings schema uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 From bc4e29c37556763d01b945c7fbaa40fb4ad07ef3 Mon Sep 17 00:00:00 2001 From: David Stern Date: Sat, 22 Aug 2026 20:37:28 -0400 Subject: [PATCH 16/16] A bit of manual cleanup. --- .github/workflows/create_release.yml | 31 +++++++--------------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 17e1eebb6ce..d65fcdbe46f 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -139,7 +139,7 @@ jobs: release_macos_single_arch: name: Build Release (macOS ${{ matrix.dmg_name_suffix }}) - runs-on: ${{ matrix.runner }} + runs-on: macos-26-xlarge needs: prepare_release if: ${{ inputs.build_macos != false }} timeout-minutes: 60 @@ -149,10 +149,8 @@ jobs: include: - arch: aarch64 dmg_name_suffix: arm64 - runner: macos-26-xlarge - arch: x86_64 dmg_name_suffix: x86_64 - runner: macos-26-large steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -168,12 +166,6 @@ jobs: run: rustup target add ${{ matrix.arch }}-apple-darwin shell: bash - # Install go toolchain, as it may be needed for some build steps. - - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 - with: - go-version: stable - - name: Get channel configuration id: get-config uses: ./.github/actions/get_channel_config/ @@ -291,12 +283,6 @@ jobs: install_release_deps: true ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} - # Install go toolchain, as it may be needed for some build steps. - - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 - with: - go-version: stable - - name: Get channel configuration id: get-config uses: ./.github/actions/get_channel_config/ @@ -740,12 +726,6 @@ jobs: install_release_deps: true ssh_key: ${{ secrets.WARP_CHANNEL_CONFIG_ACCESS_SSH_KEY }} - # Install go toolchain, as it may be needed for some build steps. - - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 - with: - go-version: stable - - name: Get channel configuration id: get-config uses: ./.github/actions/get_channel_config/ @@ -886,7 +866,8 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} - # Namespace's User Bundled Cache persists target/ between jobs on the same profile. + # Remove cached _bundle_ output that can be shared across different _jobs_. This won't + # remove any incremental build artifacts, only the final bundled output. - name: Clean stale bundle output run: | if [[ -d target ]]; then @@ -987,7 +968,8 @@ jobs: name: settings-schema-linux-${{ steps.get-config.outputs.channel }} path: .release-artifacts/settings-schema-linux - # Namespace's User Bundled Cache persists target/ between jobs on the same profile. + # Remove cached _bundle_ output that can be shared across different _jobs_. This won't + # remove any incremental build artifacts, only the final bundled output. - name: Clean stale bundle output run: | if [[ -d target ]]; then @@ -1255,7 +1237,8 @@ jobs: config_file: ${{ env.CONFIG_FILE }} channel: ${{ inputs.channel }} - # Namespace's User Bundled Cache persists target/ between jobs on the same profile. + # Remove cached _bundle_ output that can be shared across different _jobs_. This won't + # remove any incremental build artifacts, only the final bundled output. - name: Clean stale bundle output run: | if [[ -d target ]]; then