Files
warp/.github/workflows/create_release.yml
Sathvik Vangavolu 557897d202 Add Slack changelog artifact handoff (#11493)
## Description
- Stack this follow-up on #10971 so the link-normalization review stays
focused.
- Keep the stable-release Slack changelog payload readable and
link-safe, including contributor/profile links.
- Upload the generated raw Markdown changelog as a workflow artifact and
add a Slack link to that artifact for easier oncall/client copy-paste
workflows.

## Linked Issue
N/A — release workflow follow-up.
- [ ] The linked issue is labeled `ready-to-spec` or
`ready-to-implement`.
- [ ] Where appropriate, screenshots or a short video of the
implementation are included below (especially for user-visible or UI
changes).

## Testing
- `python3 -m py_compile
.agents/skills/changelog-draft/scripts/build_slack_payload.py
.agents/skills/changelog-draft/scripts/convert_to_release_json.py
.agents/skills/changelog-draft/scripts/fetch_issue_reporters.py`
- Local Slack payload regression covering:
  - Markdown PR/profile link conversion to Slack `<url|label>` links
  - raw Markdown artifact link block generation
- percent-encoding of Slack link delimiters (`|`, `<`, `>`) in Markdown
URLs and artifact URLs
- Ruby YAML parse of `.github/workflows/create_release.yml`
- `git diff --check`
- `cargo fmt`
- `cargo clippy --workspace --all-targets --all-features --tests -- -D
warnings`

- [ ] I have manually tested my changes locally with `./script/run`

### Example Slack output
Representative generated Slack text after this PR, with the artifact URL
shortened here for readability:

**Header**
Changelog for v0.2026.05.20.09.21.stable_00

**Artifact block**
Raw Markdown changelog:
<https://github.com/warpdotdev/warp-internal/actions/runs/.../artifacts/...|Download
raw Markdown changelog artifact>

**Changelog block**
*New Features*
• Added tab dragging between windows.
(<https://github.com/warpdotdev/warp/pull/9275|#9275>)
*Improvements*
• Added `warposs://pane/{uuid}` deep links.
(<https://github.com/warpdotdev/warp/pull/9655|#9655>) —
<https://github.com/Akeuuh|@Akeuuh> 
*Bug Fixes*
• Fixed file picker path truncation.
(<https://github.com/warpdotdev/warp/pull/9885|#9885>) —
<https://github.com/bradleyjames|@bradleyjames> 
*oz_updates*
• Configurable max context window per profile.
(<https://github.com/warpdotdev/warp/pull/9352|#9352>)

### Screenshots / Videos
Not applicable for this release workflow change.

## Agent Mode
- [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode

## Agent context
- Conversation:
https://staging.warp.dev/conversation/f4d16867-0550-46b3-bf0e-7509d9bbbe9e
- Plan: https://staging.warp.dev/drive/notebook/P70nhN6zYmnv5PsxrcEkJ0

Co-Authored-By: Oz <oz-agent@warp.dev>

---------

Co-authored-by: Oz <oz-agent@warp.dev>
2026-05-27 19:08:08 -04:00

1808 lines
81 KiB
YAML

# A reusable workflow that encapsulates all of the logic surrounding building
# all bundles for a single release channel, either new releases (when run on the
# `master` branch) or updated release candidates (when run within a `*_release`
# branch).
#
# This can either be triggered:
# * Indirectly, using workflow_call, for creating real releases.
# * Directly, using workflow_dispatch, for testing. By default, this will not create GitHub or Sentry releases, or upload to GCS.
name: Create Release
on:
workflow_call:
inputs:
channel:
description: The channel to create a release or release candidate for.
required: true
type: string
build_linux:
description: Build Linux artifacts.
type: boolean
default: true
build_windows:
description: Build Windows artifacts.
type: boolean
default: true
build_macos:
description: Build macOS artifacts.
type: boolean
default: true
build_web:
description: Build web artifacts.
type: boolean
default: true
should_publish:
description: Publish this release
type: boolean
default: true
workflow_dispatch:
inputs:
channel:
type: string
required: true
build_linux:
description: Build Linux artifacts.
type: boolean
default: true
build_windows:
description: Build Windows artifacts.
type: boolean
default: true
build_macos:
description: Build macOS artifacts.
type: boolean
default: true
build_web:
description: Build web artifacts.
type: boolean
default: true
env:
CARGO_TERM_COLOR: always
CONFIG_FILE: ".github/workflows/release_configurations.json"
jobs:
# Perform all once-per-release steps. Dependent jobs will build release
# assets and upload them as appropriate.
prepare_release:
name: Prepare release
runs-on: ubuntu-latest
outputs:
release_branch: ${{ steps.create_branch_and_tag.outputs.branch }}
release_tag: ${{ steps.create_branch_and_tag.outputs.tag }}
should_publish: ${{ steps.set_publish.outputs.should_publish }}
steps:
- name: Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Get channel configuration
id: get-config
uses: ./.github/actions/get_channel_config/
with:
config_file: ${{ env.CONFIG_FILE }}
channel: ${{ inputs.channel }}
- name: Set publish flag based on the workflow trigger
id: set_publish
run: |
# The should_publish input is only set on the workflow_call trigger.
# This means it's not possible to trigger publishing via workflow_dispatch, and
# publishing is enabled by default when called for a new release or RC.
if [[ "${{ inputs.should_publish }}" == "true" ]]; then
echo "should_publish=true" >> $GITHUB_OUTPUT
else
echo "should_publish=false" >> $GITHUB_OUTPUT
fi
shell: bash
- name: Update branch and tag
id: create_branch_and_tag
run: |
if [[ "${{ steps.set_publish.outputs.should_publish }}" == "true" ]]; then
GIT_BRANCH_NAME="${GITHUB_REF#refs/heads/}"
source ./script/create_release_tag_and_branch --channel $CHANNEL --branch-name "$GIT_BRANCH_NAME"
echo "branch=$(git branch --show-current)" >> $GITHUB_OUTPUT
echo "tag=$tag" >> $GITHUB_OUTPUT
else
echo "branch=$(git branch --show-current)" >> $GITHUB_OUTPUT
# Use a fake version tag that will pass package validation.
echo "tag=v0.$GITHUB_SHA" >> $GITHUB_OUTPUT
fi
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
# See https://github.com/orgs/community/discussions/151442 for why we need to use
# a PAT here.
GITHUB_TOKEN: ${{ secrets.CREATE_RELEASE_TAG_PUSH_PAT }}
- name: Create GitHub release
if: ${{ steps.set_publish.outputs.should_publish == 'true' }}
id: create_release
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
name: ${{ steps.get-config.outputs.release_base_name }} ${{ steps.create_branch_and_tag.outputs.tag }}
tag_name: ${{ steps.create_branch_and_tag.outputs.tag }}
body: ${{ steps.get-config.outputs.release_body_text }}
draft: false
prerelease: ${{ steps.get-config.outputs.is_prerelease }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Create Sentry release
if: ${{ steps.set_publish.outputs.should_publish == 'true' }}
uses: getsentry/action-release@5657c9e888b4e2cc85f4d29143ea4131fde4a73a # v3.6.0
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_RELEASE_TOKEN }}
SENTRY_ORG: warpdotdev
SENTRY_PROJECT: ${{ steps.get-config.outputs.sentry_project }}
with:
environment: ${{ steps.get-config.outputs.sentry_environment }}
version: ${{ steps.create_branch_and_tag.outputs.tag }}
release_macos_single_arch:
name: Build Release (macOS ${{ matrix.dmg_name_suffix }})
runs-on: macos-26-xlarge
needs: prepare_release
if: ${{ inputs.build_macos != false }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- arch: aarch64
dmg_name_suffix: arm64
- arch: x86_64
dmg_name_suffix: x86_64
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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 }}
- name: Ensure rust target is installed
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@4a3601121dd01d1626a1e23e37211e3254c1c06c # 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: Install cargo-bundle
run: script/install_cargo_bundle
- name: Install create-dmg
run: brew install create-dmg
- name: Build ${{ matrix.arch }} bundle
id: bundle_app
run: |
script/bundle --read-passwords-from-env --channel $CHANNEL --arch ${{ matrix.arch }} --dmg-name-suffix "${{ matrix.dmg_name_suffix }}"
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
WARP_DEVELOPER_ID_CERT: ${{ secrets.WARP_DEVELOPER_ID_CERT }}
WARP_DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.WARP_DEVELOPER_ID_CERT_PASSWORD }}
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 }}
- name: Add DMG to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_app.outputs.dmg_path }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Upload DMG to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: ${{ steps.bundle_app.outputs.dmg_path }}
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Upload DMG as workflow artifact
if: ${{ needs.prepare_release.outputs.should_publish != 'true' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: release-macos-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }}
path: ${{ steps.bundle_app.outputs.dmg_path }}
- name: Set up Sentry CLI
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b # v2.0.0
with:
token: ${{ secrets.SENTRY_RELEASE_TOKEN }}
organization: warpdotdev
project: ${{ steps.get-config.outputs.sentry_project }}
- name: Upload debug symbols from frameworks to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
run: |
script/sentry_upload_dif.sh
shell: bash
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_app.outputs.frameworks_dir }}
# Create a tar archive instead of simply using upload-artifact so we can
# preserve permissions and symlinks. If we don't preserve symlinks, we'll
# break the macOS framework directory structure.
- name: Create tar archive with build artifacts
run: |
tar czfv build-artifacts.tar.gz \
${{ steps.bundle_app.outputs.binary_path }} \
${{ steps.bundle_app.outputs.dock_tile_plugin_dir }} \
${{ steps.bundle_app.outputs.dsym_path }} \
${{ steps.bundle_app.outputs.dsym_realpath }} \
${{ steps.bundle_app.outputs.frameworks_dir }}
- name: Archive ${{ matrix.arch }} build artifacts for universal build
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: macos-${{ 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_universal:
name: Build Release (macOS Universal)
runs-on: macos-26-xlarge
needs: [prepare_release, release_macos_single_arch]
if: ${{ inputs.build_macos != false }}
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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@4a3601121dd01d1626a1e23e37211e3254c1c06c # 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 aarch64 build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: macos-aarch64-${{ steps.get-config.outputs.channel }}
- name: Extract aarch64 build artifacts
run: tar xvfz build-artifacts.tar.gz
shell: bash
- name: Download x86_64 build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: macos-x86_64-${{ steps.get-config.outputs.channel }}
- name: Extract x86_64 build artifacts
run: tar xvfz build-artifacts.tar.gz
shell: bash
- name: Install cargo-bundle
run: script/install_cargo_bundle
- name: Install create-dmg
run: brew install create-dmg
- name: Bundle universal app
id: bundle_app
run: |
script/bundle --skip-build --read-passwords-from-env --channel $CHANNEL
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
WARP_DEVELOPER_ID_CERT: ${{ secrets.WARP_DEVELOPER_ID_CERT }}
WARP_DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.WARP_DEVELOPER_ID_CERT_PASSWORD }}
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 }}
- name: Set up Sentry CLI
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b # v2.0.0
with:
token: ${{ secrets.SENTRY_RELEASE_TOKEN }}
organization: warpdotdev
project: ${{ steps.get-config.outputs.sentry_project }}
- name: Upload debugging symbols to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
run: |
script/sentry_upload_dif.sh
shell: bash
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_app.outputs.dsym_folder_path }}
- name: Add DMG to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
id: upload_github_assets
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
# Continue if the upload fails; the next step will retry it.
continue-on-error: true
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_app.outputs.dmg_path }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Add DMG to GitHub release assets (retry 1/2)
id: upload_github_assets_retry
# If the first attempt failed, try again.
if: ${{ needs.prepare_release.outputs.should_publish == 'true' && steps.upload_github_assets.outcome == 'failure' }}
# Continue if the upload fails; the next step will retry it.
continue-on-error: true
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_app.outputs.dmg_path }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Add DMG to GitHub release assets (retry 2/2)
id: upload_github_assets_retry2
# If the first attempt failed, try again.
if: ${{ needs.prepare_release.outputs.should_publish == 'true' && steps.upload_github_assets_retry.outcome == 'failure' }}
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_app.outputs.dmg_path }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Upload DMG to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: ${{ steps.bundle_app.outputs.dmg_path }}
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}
# IMPORTANT: both the key and value of the cache-control header must be all lowercase;
# uppercase values will not be respected by Cloud CDN and using "Cache-Control" will be
# ignored by the upload-cloud-storage action.
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Upload DMG as workflow artifact
if: ${{ needs.prepare_release.outputs.should_publish != 'true' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
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 }})
runs-on: macos-26-xlarge
needs: prepare_release
if: ${{ inputs.build_macos != false }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- arch: aarch64
- arch: x86_64
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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 }}
- name: Ensure rust target is installed
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@4a3601121dd01d1626a1e23e37211e3254c1c06c # 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: Build ${{ matrix.arch }} binary
id: bundle_cli
run: |
script/bundle --read-passwords-from-env --channel $CHANNEL --arch ${{ matrix.arch }} --artifact cli
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
WARP_DEVELOPER_ID_CERT: ${{ secrets.WARP_DEVELOPER_ID_CERT }}
WARP_DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.WARP_DEVELOPER_ID_CERT_PASSWORD }}
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 }}
- name: Package CLI binary
run: |
mv ${{ steps.bundle_cli.outputs.binary_path }} oz-${{ steps.get-config.outputs.channel }}
tar czf oz-${{ steps.get-config.outputs.channel }}-macos-${{ matrix.arch }}.tar.gz oz-${{ steps.get-config.outputs.channel }} -C "$(dirname "${{ steps.bundle_cli.outputs.bundled_resources_dir }}")" resources
- name: Add CLI to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: oz-${{ steps.get-config.outputs.channel }}-macos-${{ matrix.arch }}.tar.gz
token: ${{ secrets.GITHUB_TOKEN }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Upload CLI to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: oz-${{ steps.get-config.outputs.channel }}-macos-${{ matrix.arch }}.tar.gz
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}/cli/macos/${{ matrix.arch }}
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Upload CLI as workflow artifact
if: ${{ needs.prepare_release.outputs.should_publish != 'true' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: release-macos-cli-${{ matrix.arch }}-${{ steps.get-config.outputs.channel }}
path: oz-${{ steps.get-config.outputs.channel }}-macos-${{ matrix.arch }}.tar.gz
- name: Set up Sentry CLI
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b # v2.0.0
with:
token: ${{ secrets.SENTRY_RELEASE_TOKEN }}
organization: warpdotdev
project: ${{ steps.get-config.outputs.sentry_project }}
- name: Upload debugging symbols to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
run: |
script/sentry_upload_dif.sh
shell: bash
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_cli.outputs.dsym_path }}
release_linux_x86:
name: Build Release (Linux x86_64)
runs-on: namespace-profile-ubuntu-20-04
needs: prepare_release
if: ${{ inputs.build_linux != false }}
timeout-minutes: 90
env:
# Automatically extract AppImages before running them instead of mounting
# 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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 }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Install linuxdeploy
run: script/linux/install_linuxdeploy
- name: Load PGP signing key and passphrase
run: |
mkdir -p ~/.gnupg
echo "default-cache-ttl 7200" >> ~/.gnupg/gpg-agent.conf
# Set the appropriate permissions for files and directories.
find ~/.gnupg -type f -exec chmod 600 {} \;
find ~/.gnupg -type d -exec chmod 700 {} \;
# Test the agent configuration. This is helpful for debugging if
# something is invalid.
gpgconf --check-options gpg-agent
gpg-agent --gpgconf-test
# Kill the agent, if one is running, and then start a new one - this
# ensures our updated configuration is applied correctly.
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
# Retrieve our signing key and import it into gpg.
gcloud --project astral-field-294621 secrets versions access latest --secret=linux-releases-pgp-key | gpg --batch --import
# Populate the passphrase for our signing key in the gpg-agent cache.
# by signing an empty file.
touch emptyfile
gpg --no-tty --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor --batch --yes --output emptyfile.sig emptyfile << EOF
${{ secrets.LINUX_RELEASES_PGP_KEY_PASSPHRASE }}
EOF
rm emptyfile.sig
shell: bash
# Namespace's User Bundled Cache persists target/ between jobs on the same profile, which can
# leave stale packages from a previous channel's build in the linux bundle output dir.
- name: Clean stale bundle output
run: rm -rf target/*/bundle/linux
shell: bash
- name: Bundle app
id: bundle_app
run: |
# Build everything but the arch package, as we need to build it
# within a Docker container.
script/bundle --channel $CHANNEL --packages appimage,deb,rpm
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
- 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
- name: Sign Arch Linux packages
run: |
./script/linux/sign_arch_packages "$PKGDIR"
shell: bash
env:
PKGDIR: ${{ steps.bundle_app.outputs.packages_dir }}
- name: Set up Sentry CLI
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b # v2.0.0
with:
token: ${{ secrets.SENTRY_RELEASE_TOKEN }}
organization: warpdotdev
project: ${{ steps.get-config.outputs.sentry_project }}
- name: Upload debugging symbols to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
run: |
script/sentry_upload_dif.sh
shell: bash
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_app.outputs.debug_executable_path }}
- name: Add packages to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
id: upload_github_assets
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_app.outputs.packages_dir }}/*
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload packages to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: ${{ steps.bundle_app.outputs.packages_dir }}
# Only upload the files from within `packages_dir`, don't include the
# (parent) folder itself.
parent: false
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}
# IMPORTANT: both the key and value of the cache-control header must be all lowercase;
# uppercase values will not be respected by Cloud CDN and using "Cache-Control" will be
# ignored by the upload-cloud-storage action.
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Upload packages as workflow artifact
if: ${{ needs.prepare_release.outputs.should_publish != 'true' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: release-linux-x86_64-${{ steps.get-config.outputs.channel }}
path: ${{ steps.bundle_app.outputs.packages_dir }}
release_linux_cli_x86:
name: Build Release (Linux CLI x86_64)
runs-on: namespace-profile-ubuntu-20-04
needs: prepare_release
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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 }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Load PGP signing key and passphrase
run: |
mkdir -p ~/.gnupg
echo "default-cache-ttl 7200" >> ~/.gnupg/gpg-agent.conf
# Set the appropriate permissions for files and directories.
find ~/.gnupg -type f -exec chmod 600 {} \;
find ~/.gnupg -type d -exec chmod 700 {} \;
# Test the agent configuration. This is helpful for debugging if
# something is invalid.
gpgconf --check-options gpg-agent
gpg-agent --gpgconf-test
# Kill the agent, if one is running, and then start a new one - this
# ensures our updated configuration is applied correctly.
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
# Retrieve our signing key and import it into gpg.
gcloud --project astral-field-294621 secrets versions access latest --secret=linux-releases-pgp-key | gpg --batch --import
# Populate the passphrase for our signing key in the gpg-agent cache.
# by signing an empty file.
touch emptyfile
gpg --no-tty --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor --batch --yes --output emptyfile.sig emptyfile << EOF
${{ secrets.LINUX_RELEASES_PGP_KEY_PASSPHRASE }}
EOF
rm emptyfile.sig
shell: bash
# Namespace's User Bundled Cache persists target/ between jobs on the same profile, which can
# leave stale packages from a previous channel's build in the linux bundle output dir.
- name: Clean stale bundle output
run: rm -rf target/*/bundle/linux
shell: bash
- name: Bundle CLI
id: bundle_cli
run: |
# Build CLI only
script/bundle --channel $CHANNEL --artifact cli --packages deb,rpm
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
- name: Package CLI tar.gz
run: |
cp ${{ steps.bundle_cli.outputs.executable_path }} oz-${{ steps.get-config.outputs.channel }}
tar czf oz-${{ steps.get-config.outputs.channel }}-linux-x86_64.tar.gz oz-${{ steps.get-config.outputs.channel }} -C "$(dirname "${{ steps.bundle_cli.outputs.bundled_resources_dir }}")" resources
- name: Bundle Arch Linux CLI 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
artifact: cli
- name: Sign Arch Linux packages
run: |
./script/linux/sign_arch_packages "$PKGDIR"
shell: bash
env:
PKGDIR: ${{ steps.bundle_cli.outputs.packages_dir }}
- name: Set up Sentry CLI
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b # v2.0.0
with:
token: ${{ secrets.SENTRY_RELEASE_TOKEN }}
organization: warpdotdev
project: ${{ steps.get-config.outputs.sentry_project }}
- name: Upload debugging symbols to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
run: |
script/sentry_upload_dif.sh
shell: bash
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_cli.outputs.debug_executable_path }}
- name: Add CLI packages to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
id: upload_github_assets
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_cli.outputs.packages_dir }}/*
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload CLI packages to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: ${{ steps.bundle_cli.outputs.packages_dir }}
# Only upload the files from within `packages_dir`, don't include the
# (parent) folder itself.
parent: false
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}
# IMPORTANT: both the key and value of the cache-control header must be all lowercase;
# uppercase values will not be respected by Cloud CDN and using "Cache-Control" will be
# ignored by the upload-cloud-storage action.
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Add CLI tar.gz to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: oz-${{ steps.get-config.outputs.channel }}-linux-x86_64.tar.gz
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload CLI tar.gz to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: oz-${{ steps.get-config.outputs.channel }}-linux-x86_64.tar.gz
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}/cli/linux/x86_64
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Upload CLI packages as workflow artifact
if: ${{ needs.prepare_release.outputs.should_publish != 'true' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: release-linux-cli-x86_64-${{ steps.get-config.outputs.channel }}
path: |
${{ steps.bundle_cli.outputs.packages_dir }}
oz-${{ steps.get-config.outputs.channel }}-linux-x86_64.tar.gz
build_linux_arm_binaries:
name: Build Release (Linux ARM)
runs-on: namespace-profile-ubuntu-20-04-arm
needs: prepare_release
if: ${{ inputs.build_linux != false }}
timeout-minutes: 90
env:
# Automatically extract AppImages before running them instead of mounting
# 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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: Update path
run: |
# Exports .local/bin into the path for subsequent runs.
# We need this for the linuxdeploy install and bundle steps,
# as the linuxdeploy appimage is installed to $HOME/local/bin.
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
echo "Adding $HOME/.local/bin to PATH"
echo "$HOME/.local/bin" >> $GITHUB_PATH
fi
shell: bash
- name: Get channel configuration
id: get-config
uses: ./.github/actions/get_channel_config/
with:
config_file: ${{ env.CONFIG_FILE }}
channel: ${{ inputs.channel }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Install linuxdeploy
run: script/linux/install_linuxdeploy
# Namespace's User Bundled Cache persists target/ between jobs on the same profile, which can
# leave stale packages from a previous channel's build in the linux bundle output dir.
- name: Clean stale bundle output
run: rm -rf target/*/bundle/linux
shell: bash
- name: Build app
id: build_app
run: |
# Build the code, but only bundle the appimage
script/bundle --channel $CHANNEL --packages appimage
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
- name: Package binaries
id: package_binaries
run: |
# Tar up all the binaries we need for the next stage
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
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}}
- name: Archive build files
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: built-arm64-linux-${{steps.get-config.outputs.channel}}
retention-days: 1
path: binaries.tar.gz
build_linux_cli_arm_binaries:
name: Build Release (Linux CLI ARM)
runs-on: namespace-profile-ubuntu-20-04-arm
needs: prepare_release
if: ${{ inputs.build_linux != false }}
timeout-minutes: 90
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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 }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
# Namespace's User Bundled Cache persists target/ between jobs on the same profile, which can
# leave stale packages from a previous channel's build in the linux bundle output dir.
- name: Clean stale bundle output
run: rm -rf target/*/bundle/linux
shell: bash
- name: Build CLI
id: build_cli
run: |
# Build the CLI only
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 }}
- name: Package binaries
id: package_binaries
run: |
# Tar up all the binaries we need for the next stage
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-cli.tar.gz $EXECUTABLE_PATH $DEBUG_EXECUTABLE_PATH $PACKAGES_DIR
shell: bash
env:
EXECUTABLE_PATH: ${{ steps.build_cli.outputs.executable_path }}
DEBUG_EXECUTABLE_PATH: ${{ steps.build_cli.outputs.debug_executable_path }}
PACKAGES_DIR: ${{steps.build_cli.outputs.packages_dir}}
- name: Archive build files
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: built-arm64-linux-cli-${{steps.get-config.outputs.channel}}
retention-days: 1
path: binaries-cli.tar.gz
# This is done as a separate job from `build_linux_arm_binaries` and `build_linux_cli_arm_binaries`
# because the steps here were hard to get running on the self-hosted runners.
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 ]
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.prepare_release.outputs.release_branch }}
- name: Get channel configuration
id: get-config
uses: ./.github/actions/get_channel_config/
with:
config_file: ${{ env.CONFIG_FILE }}
channel: ${{ inputs.channel }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Set up Sentry CLI
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b # v2.0.0
with:
token: ${{ secrets.SENTRY_RELEASE_TOKEN }}
organization: warpdotdev
project: ${{ steps.get-config.outputs.sentry_project }}
# 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
# 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.
- name: Install Linux build dependencies
shell: bash
run: ./script/linux/install_build_deps
- name: Install release dependencies
shell: bash
run: |
./script/install_cargo_release_deps --no-build-deps
- name: Load PGP signing key and passphrase
run: |
mkdir -p ~/.gnupg
echo "default-cache-ttl 7200" >> ~/.gnupg/gpg-agent.conf
# Set the appropriate permissions for files and directories.
find ~/.gnupg -type f -exec chmod 600 {} \;
find ~/.gnupg -type d -exec chmod 700 {} \;
# Test the agent configuration. This is helpful for debugging if
# something is invalid.
gpgconf --check-options gpg-agent
gpg-agent --gpgconf-test
# Kill the agent, if one is running, and then start a new one - this
# ensures our updated configuration is applied correctly.
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
# Retrieve our signing key and import it into gpg.
gcloud --project astral-field-294621 secrets versions access latest --secret=linux-releases-pgp-key | gpg --batch --import
# Populate the passphrase for our signing key in the gpg-agent cache.
# by signing an empty file.
touch emptyfile
gpg --no-tty --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor --batch --yes --output emptyfile.sig emptyfile << EOF
${{ secrets.LINUX_RELEASES_PGP_KEY_PASSPHRASE }}
EOF
rm emptyfile.sig
echo "Populated gpg-agent cache"
shell: bash
# Bundle the app packages
# Namespace's User Bundled Cache persists target/ between jobs on the same profile, which can
# leave stale packages from a previous channel's build in the linux bundle output dir.
- name: Clean stale bundle output
run: rm -rf target/*/bundle/linux
shell: bash
- name: Download app build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: built-arm64-linux-${{steps.get-config.outputs.channel}}
- name: Unpackage app binaries
id: unpackage_app_binaries
run: |
tar xzvf binaries.tar.gz
shell: bash
- name: Bundle deb/rpm app packages
id: bundle_app
run: |
# Bundle everything but the arch package, as we need to bundle it
# within a Docker container.
script/bundle --channel $CHANNEL --skip-build --packages deb,rpm --arch aarch64
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
- name: Bundle Arch Linux app package
uses: ./.github/actions/bundle_arch_package
with:
channel: ${{ steps.get-config.outputs.channel }}
release-tag: ${{ needs.prepare_release.outputs.release_tag }}
arch: aarch64
- name: Upload app debugging symbols to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
# Important: we have to do this before the binary is overwritten by the CLI version, which has the same filename
run: |
script/sentry_upload_dif.sh
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_app.outputs.debug_executable_path }}
shell: bash
# Bundle the CLI packages
- name: Download CLI build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: built-arm64-linux-cli-${{steps.get-config.outputs.channel}}
- name: Unpackage CLI binaries
id: unpackage_cli_binaries
run: |
tar xzvf binaries-cli.tar.gz
shell: bash
- name: Bundle deb/rpm CLI packages
id: bundle_cli
run: |
script/bundle --channel $CHANNEL --skip-build --arch aarch64 --packages deb,rpm --artifact cli
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
- name: Package CLI tar.gz
run: |
cp ${{ steps.bundle_cli.outputs.executable_path }} oz-${{ steps.get-config.outputs.channel }}
tar czf oz-${{ steps.get-config.outputs.channel }}-linux-aarch64.tar.gz oz-${{ steps.get-config.outputs.channel }} -C "$(dirname "${{ steps.bundle_cli.outputs.bundled_resources_dir }}")" resources
- name: Bundle Arch Linux CLI package
uses: ./.github/actions/bundle_arch_package
with:
channel: ${{ steps.get-config.outputs.channel }}
release-tag: ${{ needs.prepare_release.outputs.release_tag }}
arch: aarch64
artifact: cli
- name: Upload CLI debugging symbols to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
run: |
script/sentry_upload_dif.sh
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_cli.outputs.debug_executable_path }}
shell: bash
# Common uploading steps
- name: Sign Arch Linux packages
run: |
# We technically only need to run `sign_arch_packages` once, as $APP_PKGDIR and $CLI_PKGDIR should be the same.
./script/linux/sign_arch_packages "$APP_PKGDIR"
./script/linux/sign_arch_packages "$CLI_PKGDIR"
shell: bash
env:
APP_PKGDIR: ${{ steps.bundle_app.outputs.packages_dir }}
CLI_PKGDIR: ${{ steps.bundle_cli.outputs.packages_dir }}
- name: Add app packages to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_app.outputs.packages_dir }}/*
token: ${{ secrets.GITHUB_TOKEN }}
- name: Add CLI packages to GitHub release assets
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
if: ${{ needs.prepare_release.outputs.should_publish == 'true' && steps.bundle_cli.outputs.packages_dir != steps.bundle_app.outputs.packages_dir }}
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_cli.outputs.packages_dir }}/*
token: ${{ secrets.GITHUB_TOKEN }}
- name: Add CLI tar.gz to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: oz-${{ steps.get-config.outputs.channel }}-linux-aarch64.tar.gz
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload app packages to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: ${{ steps.bundle_app.outputs.packages_dir }}
parent: false
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}
# IMPORTANT: both the key and value of the cache-control header must be all lowercase;
# uppercase values will not be respected by Cloud CDN and using "Cache-Control" will be
# ignored by the upload-cloud-storage action.
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Upload CLI packages to Google Cloud Storage
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
if: ${{ needs.prepare_release.outputs.should_publish == 'true' && steps.bundle_cli.outputs.packages_dir != steps.bundle_app.outputs.packages_dir }}
with:
path: ${{ steps.bundle_cli.outputs.packages_dir }}
parent: false
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}
# IMPORTANT: both the key and value of the cache-control header must be all lowercase;
# uppercase values will not be respected by Cloud CDN and using "Cache-Control" will be
# ignored by the upload-cloud-storage action.
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Upload CLI tar.gz to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: oz-${{ steps.get-config.outputs.channel }}-linux-aarch64.tar.gz
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}/cli/linux/aarch64
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Upload packages as workflow artifact
if: ${{ needs.prepare_release.outputs.should_publish != 'true' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: release-linux-arm64-${{ steps.get-config.outputs.channel }}
path: |
${{ steps.bundle_app.outputs.packages_dir }}
oz-${{ steps.get-config.outputs.channel }}-linux-aarch64.tar.gz
release_web:
name: Build Release (Web)
runs-on: namespace-profile-ubuntu-22-04
needs: prepare_release
if: ${{ inputs.build_web != false }}
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: ./.github/actions/prepare_environment
with:
target_os: wasm
is_self_hosted: false
ref: ${{ needs.prepare_release.outputs.release_branch }}
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: Bundle app
id: bundle_app
run: |
script/wasm/bundle --channel $CHANNEL
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
- name: Brotli compress app bundle
run: |
# The brotli binary won't let us compress in-place, so we need to rename back to the
# original afterwards.
brotli --rm "${{ steps.bundle_app.outputs.packages_dir }}"/*
for file in "${{ steps.bundle_app.outputs.packages_dir }}"/*.br; do
mv -- "$file" "${file%.br}"
done
shell: bash
- name: Set up Sentry CLI
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b # v2.0.0
with:
token: ${{ secrets.SENTRY_RELEASE_TOKEN }}
organization: warpdotdev
project: ${{ steps.get-config.outputs.sentry_project }}
- name: Upload debugging symbols to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
run: |
script/sentry_upload_dif.sh
shell: bash
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_app.outputs.debug_executable_path }}
- name: Add packages to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
id: upload_github_assets
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_app.outputs.packages_dir }}/*
token: ${{ secrets.GITHUB_TOKEN }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Upload packages to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: ${{ steps.bundle_app.outputs.packages_dir }}
# Only upload the files from within `packages_dir`, don't include the
# (parent) folder itself.
gzip: false
parent: false
destination: ${{ steps.get-config.outputs.web_gcs_bucket_prefix }}-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}
# IMPORTANT: both the key and value of the cache-control header must be all lowercase;
# uppercase values will not be respected by Cloud CDN and using "Cache-Control" will be
# ignored by the upload-cloud-storage action.
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
content-encoding: br
- name: Upload assets to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: ${{ steps.bundle_app.outputs.assets_dir }}
parent: false
# All asset filenames contain the file hash for cashing purposes, so we store them all in
# one directory so they can be cached across releases.
destination: ${{ steps.get-config.outputs.web_gcs_bucket_prefix }}-static-assets/
# IMPORTANT: both the key and value of the cache-control header must be all lowercase;
# uppercase values will not be respected by Cloud CDN and using "Cache-Control" will be
# ignored by the upload-cloud-storage action.
# These assets are all stored under cache busting names, so we want to cache them
# effectively forever. There's no defined maximum for the max-age directive, but a similar
# directive suggests a maximum of one year, so that's what's chosen here.
headers: |-
cache-control: private, max-age=31536000, immutable
- name: Upload web bundle as workflow artifact
if: ${{ needs.prepare_release.outputs.should_publish != 'true' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: release-web-${{ steps.get-config.outputs.channel }}
path: ${{ steps.bundle_app.outputs.packages_dir }}
release_windows:
name: Build Release (Windows ${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
needs: prepare_release
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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: 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 binary
id: build_binary
run: script/bundle -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: Sign the executables with Azure Trusted Signing
uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1.2.0
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: ${{ env.TRUSTED_SIGNING_ENDPOINT }}
trusted-signing-account-name: ${{ env.TRUSTED_SIGNING_ACCOUNT }}
certificate-profile-name: ${{ env.TRUSTED_SIGNING_CERT_PROFILE }}
files: |
${{ steps.build_binary.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
files-folder-recurse: true
files-folder-depth: 2
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
# The azure/artifact-signing-action above downloads signtool.exe and the Trusted Signing dlib.
# We locate them so ISCC can call signtool during compilation to sign the inner setup engine
# (the file extracted to %TEMP% at runtime). This prevents Defender ASR rule D4F940AB from
# blocking the installer in enterprise environments. See this issue:
# https://github.com/warpdotdev/Warp/issues/8794
- name: Build sign-tool command for Inno Setup
id: setup_signing
shell: pwsh
run: |
$Dlib = (Get-ChildItem -Recurse "$env:LOCALAPPDATA" -Filter "Azure.CodeSigning.Dlib.dll" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match 'x64' } | Select-Object -First 1).FullName
if (-not $Dlib) { throw "Azure.CodeSigning.Dlib.dll not found — did the earlier signing action run?" }
Write-Output "Dlib: $Dlib"
$SignTool = (Get-ChildItem -Recurse "C:\Program Files (x86)\Windows Kits" -Filter "signtool.exe" |
Where-Object { $_.FullName -match 'x64' } | Sort-Object FullName -Descending | Select-Object -First 1).FullName
if (-not $SignTool) { throw "signtool.exe not found in Windows SDK" }
Write-Output "SignTool: $SignTool"
# Add signtool's directory to PATH so the ISCC sign command can reference it as just "signtool.exe".
# Embedded quotes in the /Scodesign= value break PowerShell's native-command argument passing to ISCC.
(Split-Path $SignTool) >> $env:GITHUB_PATH
$MetadataPath = "${{ runner.temp }}\tsc-metadata.json"
@{Endpoint=$env:TRUSTED_SIGNING_ENDPOINT;CodeSigningAccountName=$env:TRUSTED_SIGNING_ACCOUNT;CertificateProfileName=$env:TRUSTED_SIGNING_CERT_PROFILE} |
ConvertTo-Json | Set-Content $MetadataPath
"sign_tool_cmd=signtool.exe sign /v /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 /dlib $Dlib /dmdf $MetadataPath `$f" >> $env:GITHUB_OUTPUT
- name: Bundle app
id: bundle_app
run: script/bundle -Channel $CHANNEL -skip_build_binary --arch ${{ matrix.arch }}
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
GIT_RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
SIGN_TOOL_CMD: ${{ steps.setup_signing.outputs.sign_tool_cmd }}
- name: Set up Sentry CLI
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b # v2.0.0
with:
token: ${{ secrets.SENTRY_RELEASE_TOKEN }}
organization: warpdotdev
project: ${{ steps.get-config.outputs.sentry_project }}
- name: Upload binary to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
run: |
script/sentry_upload_dif.sh
shell: bash
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.build_binary.outputs.binary_path }}
- name: Upload debugging symbols to Sentry
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
run: |
script/sentry_upload_dif.sh
shell: bash
env:
DEBUG_FILE_OR_FOLDER_PATH: ${{ steps.bundle_app.outputs.pdb_file_path }}
- name: Add installer to GitHub release assets
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
id: upload_github_assets
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
with:
tag_name: ${{ needs.prepare_release.outputs.release_tag }}
files: ${{ steps.bundle_app.outputs.installer_path }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Upload installer to Google Cloud Storage
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: ${{ steps.bundle_app.outputs.installer_path }}
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}
# IMPORTANT: both the key and value of the cache-control header must be all lowercase;
# uppercase values will not be respected by Cloud CDN and using "Cache-Control" will be
# ignored by the upload-cloud-storage action.
headers: |-
cache-control: ${{ steps.get-config.outputs.gcs_cache_control_value }}
- name: Upload installer as workflow artifact
if: ${{ needs.prepare_release.outputs.should_publish != 'true' }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
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
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
if: ${{ always() && needs.prepare_release.outputs.should_publish == 'true' }}
steps:
- name: Figure out which releases failed, if any
id: failed_releases
shell: bash
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_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 (( ${#FAILED[@]} )); then
printf -v FORMATTED "%s, " "${FAILED[@]}"
echo "failed=$(echo "{${FORMATTED%, }}")" >> $GITHUB_OUTPUT
fi
- name: Send Slack notification if unsuccessful
uses: slackapi/slack-github-action@410ae57cff5c6b682b106440be0e6c7eb8c98c9d # v1.16.0
if: ${{ steps.failed_releases.outputs.failed != '' }}
with:
channel-id: "#oncall-client"
# Follows https://api.slack.com/reference/surfaces/formatting#mentioning-users.
# S06N82RS7FA is the user group ID for @oncall-client-eng.
slack-message: "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: <!subteam^S06N82RS7FA>"
env:
SLACK_BOT_TOKEN: ${{ secrets.ACTION_MONITORING_SLACK }}
generate_changelogs:
name: Generate Changelogs
if: ${{ needs.prepare_release.outputs.should_publish == 'true' }}
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
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
name: Checkout sources
with:
# Fetch history for all tags and branches so we can compare revisions
# in order to generate the changelog.
fetch-depth: 0
- name: Get channel configuration
id: get-config
uses: ./.github/actions/get_channel_config/
with:
config_file: ${{ env.CONFIG_FILE }}
channel: ${{ inputs.channel }}
# Stable releases use the Oz changelog-draft agent for higher-quality,
# human-reviewable output. Non-stable channels (dev/preview/beta) use the
# legacy generate-changelog action to avoid spending Oz agent tokens on
# daily dev cuts and preview RCs.
- name: Generate changelog via Oz (stable only)
if: inputs.channel == 'stable'
uses: warpdotdev/oz-agent-action@ce1621abf6a8ed8afdd4e4cc994545ede8fe1c6f # main
with:
prompt: |
Generate a changelog draft for the ${{ inputs.channel }} channel, release tag ${{ needs.prepare_release.outputs.release_tag }}.
Output directory: ${{ runner.temp }}/changelog-draft
Follow the workflow in .agents/skills/changelog-draft/SKILL.md exactly.
Make sure to produce both output files: changelog-draft.md and changelog-draft.json.
The release workflow may run from warpdotdev/warp-internal. When fetching PR data, pass the checked-out repository ("${{ github.repository }}") to fetch_prs.py and rely on the script's repo-sync normalization to resolve public warpdotdev/warp PR numbers, URLs, and authors. The script intentionally omits non-repo-sync PRs from warp-internal because they are private internal changes. Do not infer or synthesize public PR links manually.
After writing the output files, print the full contents of changelog-draft.md to stdout so it appears in the workflow log.
You are running in a GitHub Actions workflow. The repo is checked out at the default branch (HEAD) with full history. Use the release_tag input as the git range endpoint — do NOT check out the release tag. `gh` is authenticated. Do not commit, push, or create PRs.
warp_api_key: ${{ secrets.WARP_API_KEY }}
share: team
- name: Upload raw Markdown changelog artifact (stable only)
if: inputs.channel == 'stable'
id: upload_changelog_draft_markdown
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: changelog-draft-markdown
path: ${{ runner.temp }}/changelog-draft/changelog-draft.md
if-no-files-found: error
- name: Convert draft JSON to release format (stable only)
if: inputs.channel == 'stable'
shell: bash
run: |
python3 .agents/skills/changelog-draft/scripts/convert_to_release_json.py \
--input "${{ runner.temp }}/changelog-draft/changelog-draft.json" \
--output "${{ runner.temp }}/changelog-draft/changelog-release.json"
- name: Obtain a GitHub App Installation Access Token (non-stable only)
if: inputs.channel != 'stable'
id: github_app_auth
run: |
TOKEN="$(npx obtain-github-app-installation-access-token ci ${{ secrets.GH_APP_CREDENTIALS_TOKEN }})"
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> $GITHUB_OUTPUT
- name: Generate changelog via legacy action (non-stable only)
if: inputs.channel != 'stable'
id: legacy_changelog
uses: warpdotdev/generate-changelog@70f534c1e030dafb45046ae57e4aa4d43a2f5c84 # main
with:
channel: ${{ inputs.channel }}
github_auth_token: ${{ steps.github_app_auth.outputs.token }}
version: ${{ needs.prepare_release.outputs.release_tag }}
- name: Load changelog into step output
id: generate_changelog
shell: bash
env:
LEGACY_CHANGELOG: ${{ steps.legacy_changelog.outputs.changelog }}
run: |
# Bridge step: picks whichever generator ran for this channel and
# re-emits the changelog as outputs.changelog so downstream Slack/GCS
# steps work unchanged.
if [[ "${{ inputs.channel }}" == "stable" ]]; then
CHANGELOG_FILE="${{ runner.temp }}/changelog-draft/changelog-release.json"
if [ ! -f "$CHANGELOG_FILE" ]; then
echo "::error::changelog-release.json not found at $CHANGELOG_FILE"
exit 1
fi
jq empty "$CHANGELOG_FILE"
{
echo "changelog<<CHANGELOG_EOF"
cat "$CHANGELOG_FILE"
echo "CHANGELOG_EOF"
} >> $GITHUB_OUTPUT
else
echo "$LEGACY_CHANGELOG" | jq empty
{
echo "changelog<<CHANGELOG_EOF"
printf '%s\n' "$LEGACY_CHANGELOG"
echo "CHANGELOG_EOF"
} >> $GITHUB_OUTPUT
fi
- name: Build Slack changelog payload
id: build_slack_payload
shell: bash
env:
CHANGELOG_JSON: ${{ steps.generate_changelog.outputs.changelog }}
RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
MARKDOWN_ARTIFACT_URL: ${{ steps.upload_changelog_draft_markdown.outputs.artifact-url }}
run: |
CHANGELOG_INPUT="${{ runner.temp }}/slack-changelog-input.json"
SLACK_PAYLOAD="${{ runner.temp }}/slack-changelog-payload.json"
printf '%s\n' "$CHANGELOG_JSON" > "$CHANGELOG_INPUT"
python3 .agents/skills/changelog-draft/scripts/build_slack_payload.py \
--input "$CHANGELOG_INPUT" \
--release-tag "$RELEASE_TAG" \
--markdown-artifact-url "$MARKDOWN_ARTIFACT_URL" \
--output "$SLACK_PAYLOAD"
if ! jq -e '.blocks | length > 0' "$SLACK_PAYLOAD" >/dev/null; then
echo "has_content=" >> $GITHUB_OUTPUT
exit 0
fi
echo "has_content=true" >> $GITHUB_OUTPUT
echo "payload<<SLACK_PAYLOAD_EOF" >> $GITHUB_OUTPUT
cat "$SLACK_PAYLOAD" >> $GITHUB_OUTPUT
echo "SLACK_PAYLOAD_EOF" >> $GITHUB_OUTPUT
- name: Post to a Slack channel
id: slack
uses: slackapi/slack-github-action@410ae57cff5c6b682b106440be0e6c7eb8c98c9d # v1.16.0
if: ${{ steps.build_slack_payload.outputs.has_content == 'true' }}
# Continue on with the rest of the workflow, even if we fail to send the
# changelog info to Slack. (We still want to stick the changelog JSON
# in Cloud Storage.)
continue-on-error: true
with:
channel-id: ${{ steps.get-config.outputs.changelog_slack_channel }}
payload: ${{ steps.build_slack_payload.outputs.payload }}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_CHANGELOG_BOT }}
- name: Generate changelog.json
id: changelog_json
env:
CHANGELOG: ${{ steps.generate_changelog.outputs.changelog }}
run: |
# Compute the current date/time in the necessary format.
DATE=$(date +%Y-%m-%dT%k:%M:%S%z)
# Rename the "newFeatures" and "improvements" keys in the Changelog JSON to something more human readable
# and ignore everything else since this is for the in-app changelog.
CHANGELOG_SECTIONS=$(echo $CHANGELOG | jq 'with_entries(select(.key == "newFeatures" or .key == "improvements"))' | jq 'with_entries(if .key == "newFeatures" then .key = "New features" else . end)' | jq 'with_entries(if .key == "improvements" then .key = "Improvements" else . end)')
# create markdown strings for the New Features and Improvements sections
NEW_FEATURES=$(echo $CHANGELOG_SECTIONS | jq 'with_entries(select(.key == "New features"))' | jq -r 'to_entries[] | "* \(.value[])"' | awk -v ORS='\n' '1')
IMPROVEMENTS=$(echo $CHANGELOG_SECTIONS | jq 'with_entries(select(.key == "Improvements"))' | jq -r 'to_entries[] | "* \(.value[])"' | awk -v ORS='\n' '1')
# Extract the image URL from the list, and use the latest URL even if there are multiple
IMAGE=$(echo $CHANGELOG | jq -r '.images | if length > 0 then .[-1] else "" end')
# Extract Oz updates as a JSON array
OZ_UPDATES=$(echo $CHANGELOG | jq -c '.oz_updates // []')
# Tweak the structure of the JSON, add in a top-level date field, and add in the markdown_sections field
CHANGELOG=$(echo $CHANGELOG_SECTIONS | jq --arg date "$DATE" --arg new_features "$NEW_FEATURES" --arg improvements "$IMPROVEMENTS" --arg image "$IMAGE" --argjson oz_updates "$OZ_UPDATES" '{"date": $date, "sections": to_entries | map({"title": .key, "items": .value}), "markdown_sections": [{"title": "New features", "markdown": $new_features}, {"title": "Improvements", "markdown": $improvements}, {"title": "Coming soon", "markdown": ""}]} + (if $image != "" then {"image_url": $image} else {} end) + (if ($oz_updates | length) > 0 then {"oz_updates": $oz_updates} else {} end)')
echo $CHANGELOG > changelog.json
cat changelog.json
shell: bash
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: Upload changelog.json to Google Cloud Storage
uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1
with:
path: changelog.json
destination: warp-releases/${{ steps.get-config.outputs.channel }}/${{ needs.prepare_release.outputs.release_tag }}