diff --git a/.github/workflows/add_commented_closed_issue_to_project.yml b/.github/workflows/add_commented_closed_issue_to_project.yml index 5871f5ae0e61f97557ce926c4a2627841f50560d..bd84eaa9446e57c5482ab818df3dbcfe587e040e 100644 --- a/.github/workflows/add_commented_closed_issue_to_project.yml +++ b/.github/workflows/add_commented_closed_issue_to_project.yml @@ -63,13 +63,18 @@ jobs: } - if: steps.is-post-close-comment.outputs.result == 'true' && steps.check-staff.outputs.result == 'true' + env: + ISSUE_NUMBER: ${{ github.event.issue.number }} run: | - echo "::notice::Skipping issue #${{ github.event.issue.number }} - commenter is staff member" + echo "::notice::Skipping issue #$ISSUE_NUMBER - commenter is staff member" # github-script outputs are JSON strings, so we compare against 'false' (string) - if: steps.is-post-close-comment.outputs.result == 'true' && steps.check-staff.outputs.result == 'false' + env: + ISSUE_NUMBER: ${{ github.event.issue.number }} + COMMENT_USER_LOGIN: ${{ github.event.comment.user.login }} run: | - echo "::notice::Adding issue #${{ github.event.issue.number }} to project (comment by ${{ github.event.comment.user.login }})" + echo "::notice::Adding issue #$ISSUE_NUMBER to project (comment by $COMMENT_USER_LOGIN)" - if: steps.is-post-close-comment.outputs.result == 'true' && steps.check-staff.outputs.result == 'false' uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2 diff --git a/.github/workflows/after_release.yml b/.github/workflows/after_release.yml index 9582e3f1956b3ecda383fc03efdb3d7ff67eaa68..95229f9f46bbd34ffe02832114b2b39da1b7e090 100644 --- a/.github/workflows/after_release.yml +++ b/.github/workflows/after_release.yml @@ -76,7 +76,7 @@ jobs: "X-GitHub-Api-Version" = "2022-11-28" } $body = @{ branch = "master" } | ConvertTo-Json - $uri = "https://api.github.com/repos/${{ github.repository_owner }}/winget-pkgs/merge-upstream" + $uri = "https://api.github.com/repos/$env:GITHUB_REPOSITORY_OWNER/winget-pkgs/merge-upstream" try { Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -ContentType "application/json" Write-Host "Successfully synced winget-pkgs fork" @@ -131,11 +131,10 @@ jobs: runs-on: namespace-profile-2x4-ubuntu-2404 steps: - name: release::send_slack_message - run: | - curl -X POST -H 'Content-type: application/json'\ - --data '{"text":"❌ ${{ github.workflow }} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' "$SLACK_WEBHOOK" + run: 'curl -X POST -H ''Content-type: application/json'' --data "$(jq -n --arg text "$SLACK_MESSAGE" ''{"text": $text}'')" "$SLACK_WEBHOOK"' env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_WORKFLOW_FAILURES }} + SLACK_MESSAGE: '❌ ${{ github.workflow }} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' defaults: run: shell: bash -euxo pipefail {0} diff --git a/.github/workflows/autofix_pr.yml b/.github/workflows/autofix_pr.yml index 60cc66294af2cf65e17aaad530a9df511ec61503..1fa271d168a8c3d1744439647ff50b793a854d1d 100644 --- a/.github/workflows/autofix_pr.yml +++ b/.github/workflows/autofix_pr.yml @@ -22,8 +22,9 @@ jobs: with: clean: false - name: autofix_pr::run_autofix::checkout_pr - run: gh pr checkout ${{ inputs.pr_number }} + run: gh pr checkout "$PR_NUMBER" env: + PR_NUMBER: ${{ inputs.pr_number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: steps::setup_cargo_config run: | @@ -104,8 +105,9 @@ jobs: clean: false token: ${{ steps.get-app-token.outputs.token }} - name: autofix_pr::commit_changes::checkout_pr - run: gh pr checkout ${{ inputs.pr_number }} + run: gh pr checkout "$PR_NUMBER" env: + PR_NUMBER: ${{ inputs.pr_number }} GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }} - name: autofix_pr::download_patch_artifact uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 diff --git a/.github/workflows/catch_blank_issues.yml b/.github/workflows/catch_blank_issues.yml index dd425afc886e86c1217a94e90eabced013f66bf0..c6f595ef2e0890ce107829f3e91490332567368a 100644 --- a/.github/workflows/catch_blank_issues.yml +++ b/.github/workflows/catch_blank_issues.yml @@ -42,8 +42,10 @@ jobs: } - if: steps.check-staff.outputs.result == 'true' + env: + ISSUE_NUMBER: ${{ github.event.issue.number }} run: | - echo "::notice::Skipping issue #${{ github.event.issue.number }} - actor is staff member" + echo "::notice::Skipping issue #$ISSUE_NUMBER - actor is staff member" - if: steps.check-staff.outputs.result == 'false' id: add-label diff --git a/.github/workflows/cherry_pick.yml b/.github/workflows/cherry_pick.yml index 9d46f300b509347b2853c00575c4e82fd9a2863c..ee0c1d35d0f9825d7c39b81fba0fe35901de2611 100644 --- a/.github/workflows/cherry_pick.yml +++ b/.github/workflows/cherry_pick.yml @@ -36,8 +36,11 @@ jobs: app-id: ${{ secrets.ZED_ZIPPY_APP_ID }} private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }} - name: cherry_pick::run_cherry_pick::cherry_pick - run: ./script/cherry-pick ${{ inputs.branch }} ${{ inputs.commit }} ${{ inputs.channel }} + run: ./script/cherry-pick "$BRANCH" "$COMMIT" "$CHANNEL" env: + BRANCH: ${{ inputs.branch }} + COMMIT: ${{ inputs.commit }} + CHANNEL: ${{ inputs.channel }} GIT_COMMITTER_NAME: Zed Zippy GIT_COMMITTER_EMAIL: hi@zed.dev GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }} diff --git a/.github/workflows/community_update_all_top_ranking_issues.yml b/.github/workflows/community_update_all_top_ranking_issues.yml index 59926f35563a4b21e3486ecbd454a4ccf951461e..ef3b4fc39ddb5f0db9b09c5e861547ae8cd7eb08 100644 --- a/.github/workflows/community_update_all_top_ranking_issues.yml +++ b/.github/workflows/community_update_all_top_ranking_issues.yml @@ -22,4 +22,6 @@ jobs: - name: Install dependencies run: uv sync --project script/update_top_ranking_issues -p 3.13 - name: Run script - run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 5393 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token "$GITHUB_TOKEN" --issue-reference-number 5393 diff --git a/.github/workflows/community_update_weekly_top_ranking_issues.yml b/.github/workflows/community_update_weekly_top_ranking_issues.yml index 75ba66b934b5861bd51aef4238a1a4188dddefc3..53b548f2bb4286e5de86d3823e67d75c0413a1cb 100644 --- a/.github/workflows/community_update_weekly_top_ranking_issues.yml +++ b/.github/workflows/community_update_weekly_top_ranking_issues.yml @@ -22,4 +22,6 @@ jobs: - name: Install dependencies run: uv sync --project script/update_top_ranking_issues -p 3.13 - name: Run script - run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 6952 --query-day-interval 7 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token "$GITHUB_TOKEN" --issue-reference-number 6952 --query-day-interval 7 diff --git a/.github/workflows/compare_perf.yml b/.github/workflows/compare_perf.yml index e5a2d4f9c928eac2d1b1cf54ed374f8b0cca5d25..f7d78dbbf6a6d04bc47212b6842f894850288fcc 100644 --- a/.github/workflows/compare_perf.yml +++ b/.github/workflows/compare_perf.yml @@ -37,27 +37,40 @@ jobs: - name: compare_perf::run_perf::install_hyperfine uses: taiki-e/install-action@hyperfine - name: steps::git_checkout - run: git fetch origin ${{ inputs.base }} && git checkout ${{ inputs.base }} + run: git fetch origin "$REF_NAME" && git checkout "$REF_NAME" + env: + REF_NAME: ${{ inputs.base }} - name: compare_perf::run_perf::cargo_perf_test run: |2- - if [ -n "${{ inputs.crate_name }}" ]; then - cargo perf-test -p ${{ inputs.crate_name }} -- --json=${{ inputs.base }}; + if [ -n "$CRATE_NAME" ]; then + cargo perf-test -p "$CRATE_NAME" -- --json="$REF_NAME"; else - cargo perf-test -p vim -- --json=${{ inputs.base }}; + cargo perf-test -p vim -- --json="$REF_NAME"; fi + env: + REF_NAME: ${{ inputs.base }} + CRATE_NAME: ${{ inputs.crate_name }} - name: steps::git_checkout - run: git fetch origin ${{ inputs.head }} && git checkout ${{ inputs.head }} + run: git fetch origin "$REF_NAME" && git checkout "$REF_NAME" + env: + REF_NAME: ${{ inputs.head }} - name: compare_perf::run_perf::cargo_perf_test run: |2- - if [ -n "${{ inputs.crate_name }}" ]; then - cargo perf-test -p ${{ inputs.crate_name }} -- --json=${{ inputs.head }}; + if [ -n "$CRATE_NAME" ]; then + cargo perf-test -p "$CRATE_NAME" -- --json="$REF_NAME"; else - cargo perf-test -p vim -- --json=${{ inputs.head }}; + cargo perf-test -p vim -- --json="$REF_NAME"; fi + env: + REF_NAME: ${{ inputs.head }} + CRATE_NAME: ${{ inputs.crate_name }} - name: compare_perf::run_perf::compare_runs - run: cargo perf-compare --save=results.md ${{ inputs.base }} ${{ inputs.head }} + run: cargo perf-compare --save=results.md "$BASE" "$HEAD" + env: + BASE: ${{ inputs.base }} + HEAD: ${{ inputs.head }} - name: '@actions/upload-artifact results.md' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 with: diff --git a/.github/workflows/deploy_collab.yml b/.github/workflows/deploy_collab.yml index b1bdaf61979452a73380226ce1935b43eb05c32b..89fb6980b65f2d09a6571f140ab016a710be230f 100644 --- a/.github/workflows/deploy_collab.yml +++ b/.github/workflows/deploy_collab.yml @@ -119,8 +119,9 @@ jobs: with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - name: deploy_collab::deploy::sign_into_kubernetes - run: | - doctl kubernetes cluster kubeconfig save --expiry-seconds 600 ${{ secrets.CLUSTER_NAME }} + run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 "$CLUSTER_NAME" + env: + CLUSTER_NAME: ${{ secrets.CLUSTER_NAME }} - name: deploy_collab::deploy::start_rollout run: | set -eu @@ -140,7 +141,7 @@ jobs: echo "Deploying collab:$GITHUB_SHA to $ZED_KUBE_NAMESPACE" source script/lib/deploy-helpers.sh - export_vars_for_environment $ZED_KUBE_NAMESPACE + export_vars_for_environment "$ZED_KUBE_NAMESPACE" ZED_DO_CERTIFICATE_ID="$(doctl compute certificate list --format ID --no-header)" export ZED_DO_CERTIFICATE_ID @@ -150,14 +151,14 @@ jobs: export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT export DATABASE_MAX_CONNECTIONS=850 envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f - - kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch + kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}" export ZED_SERVICE_NAME=api export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_API_LOAD_BALANCER_SIZE_UNIT export DATABASE_MAX_CONNECTIONS=60 envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f - - kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch + kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}" defaults: run: diff --git a/.github/workflows/extension_bump.yml b/.github/workflows/extension_bump.yml index b7bb78363ce4ff97680b2a53967938280c3de902..9cc53741e8007a1b3ddd02ad07b191b3ce171cc8 100644 --- a/.github/workflows/extension_bump.yml +++ b/.github/workflows/extension_bump.yml @@ -39,7 +39,7 @@ jobs: run: | CURRENT_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')" - if [[ "${{ github.event_name }}" == "pull_request" ]]; then + if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then PR_FORK_POINT="$(git merge-base origin/main HEAD)" git checkout "$PR_FORK_POINT" elif BRANCH_PARENT_SHA="$(git merge-base origin/main origin/zed-zippy-autobump)"; then @@ -82,8 +82,6 @@ jobs: - id: bump-version name: extension_bump::bump_version run: | - OLD_VERSION="${{ needs.check_version_changed.outputs.current_version }}" - BUMP_FILES=("extension.toml") if [[ -f "Cargo.toml" ]]; then BUMP_FILES+=("Cargo.toml") @@ -93,7 +91,7 @@ jobs: --search "version = \"{current_version}"\" \ --replace "version = \"{new_version}"\" \ --current-version "$OLD_VERSION" \ - --no-configured-files ${{ inputs.bump-type }} "${BUMP_FILES[@]}" + --no-configured-files "$BUMP_TYPE" "${BUMP_FILES[@]}" if [[ -f "Cargo.toml" ]]; then cargo update --workspace @@ -102,6 +100,9 @@ jobs: NEW_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')" echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT" + env: + OLD_VERSION: ${{ needs.check_version_changed.outputs.current_version }} + BUMP_TYPE: ${{ inputs.bump-type }} - name: extension_bump::create_pull_request uses: peter-evans/create-pull-request@v7 with: diff --git a/.github/workflows/extension_tests.yml b/.github/workflows/extension_tests.yml index 5160aba2869b1a3234c686a6508460784b0536b1..53de373c1b79dc3ca9a3637642e10998c781580a 100644 --- a/.github/workflows/extension_tests.yml +++ b/.github/workflows/extension_tests.yml @@ -32,7 +32,7 @@ jobs: git fetch origin "$GITHUB_BASE_REF" --depth=350 COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)" fi - CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" ${{ github.sha }})" + CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")" check_pattern() { local output_name="$1" @@ -129,7 +129,7 @@ jobs: run: | CURRENT_VERSION="$(sed -n 's/^version = \"\(.*\)\"/\1/p' < extension.toml | tr -d '[:space:]')" - if [[ "${{ github.event_name }}" == "pull_request" ]]; then + if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then PR_FORK_POINT="$(git merge-base origin/main HEAD)" git checkout "$PR_FORK_POINT" elif BRANCH_PARENT_SHA="$(git merge-base origin/main origin/zed-zippy-autobump)"; then @@ -147,11 +147,14 @@ jobs: echo "current_version=${CURRENT_VERSION}" >> "$GITHUB_OUTPUT" - name: extension_tests::verify_version_did_not_change run: | - if [[ ${{ steps.compare-versions-check.outputs.version_changed }} == "true" && "${{ github.event_name }}" == "pull_request" && "${{ github.event.pull_request.user.login }}" != "zed-zippy[bot]" ]] ; then + if [[ "$VERSION_CHANGED" == "true" && "$GITHUB_EVENT_NAME" == "pull_request" && "$PR_USER_LOGIN" != "zed-zippy[bot]" ]] ; then echo "Version change detected in your change!" echo "Version changes happen in separate PRs and will be performed by the zed-zippy bot" exit 42 fi + env: + VERSION_CHANGED: ${{ steps.compare-versions-check.outputs.version_changed }} + PR_USER_LOGIN: ${{ github.event.pull_request.user.login }} timeout-minutes: 6 tests_pass: needs: @@ -171,11 +174,15 @@ jobs: if [[ "$2" != "skipped" && "$2" != "success" ]]; then EXIT_CODE=1; fi } - check_result "orchestrate" "${{ needs.orchestrate.result }}" - check_result "check_rust" "${{ needs.check_rust.result }}" - check_result "check_extension" "${{ needs.check_extension.result }}" + check_result "orchestrate" "$RESULT_ORCHESTRATE" + check_result "check_rust" "$RESULT_CHECK_RUST" + check_result "check_extension" "$RESULT_CHECK_EXTENSION" exit $EXIT_CODE + env: + RESULT_ORCHESTRATE: ${{ needs.orchestrate.result }} + RESULT_CHECK_RUST: ${{ needs.check_rust.result }} + RESULT_CHECK_EXTENSION: ${{ needs.check_extension.result }} concurrency: group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} cancel-in-progress: true diff --git a/.github/workflows/extension_workflow_rollout.yml b/.github/workflows/extension_workflow_rollout.yml index 709956fc1bc0b25190638d9f1b5d4cd3cadd7ba2..9bfac06d4527985553ba3d04e64c656ee5bf85e4 100644 --- a/.github/workflows/extension_workflow_rollout.yml +++ b/.github/workflows/extension_workflow_rollout.yml @@ -80,9 +80,7 @@ jobs: - id: calc-changes name: extension_workflow_rollout::rollout_workflows_to_extension::get_removed_files run: | - PREV_COMMIT="${{ steps.prev-tag.outputs.prev_commit }}" - - if [ "${{ matrix.repo }}" = "workflows" ]; then + if [ "$MATRIX_REPO" = "workflows" ]; then WORKFLOW_DIR="extensions/workflows" else WORKFLOW_DIR="extensions/workflows/shared" @@ -101,11 +99,12 @@ jobs: echo "Files to remove: $REMOVED_FILES" echo "removed_files=$REMOVED_FILES" >> "$GITHUB_OUTPUT" + env: + PREV_COMMIT: ${{ steps.prev-tag.outputs.prev_commit }} + MATRIX_REPO: ${{ matrix.repo }} working-directory: zed - name: extension_workflow_rollout::rollout_workflows_to_extension::sync_workflow_files run: | - REMOVED_FILES="${{ steps.calc-changes.outputs.removed_files }}" - mkdir -p extension/.github/workflows cd extension/.github/workflows @@ -119,11 +118,14 @@ jobs: cd - > /dev/null - if [ "${{ matrix.repo }}" = "workflows" ]; then + if [ "$MATRIX_REPO" = "workflows" ]; then cp zed/extensions/workflows/*.yml extension/.github/workflows/ else cp zed/extensions/workflows/shared/*.yml extension/.github/workflows/ fi + env: + REMOVED_FILES: ${{ steps.calc-changes.outputs.removed_files }} + MATRIX_REPO: ${{ matrix.repo }} - id: short-sha name: extension_workflow_rollout::rollout_workflows_to_extension::get_short_sha run: | @@ -148,13 +150,13 @@ jobs: sign-commits: true - name: extension_workflow_rollout::rollout_workflows_to_extension::enable_auto_merge run: | - PR_NUMBER="${{ steps.create-pr.outputs.pull-request-number }}" if [ -n "$PR_NUMBER" ]; then cd extension gh pr merge "$PR_NUMBER" --auto --squash fi env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} + PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }} timeout-minutes: 10 create_rollout_tag: needs: diff --git a/.github/workflows/publish_extension_cli.yml b/.github/workflows/publish_extension_cli.yml index 391baac1cb3aa9da76c4fde39aa6909525541a58..75f1b16b007e33d0c4f346a33a1403648f1cd6c6 100644 --- a/.github/workflows/publish_extension_cli.yml +++ b/.github/workflows/publish_extension_cli.yml @@ -27,7 +27,7 @@ jobs: - name: publish_extension_cli::publish_job::build_extension_cli run: cargo build --release --package extension_cli - name: publish_extension_cli::publish_job::upload_binary - run: script/upload-extension-cli ${{ github.sha }} + run: script/upload-extension-cli "$GITHUB_SHA" env: DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }} @@ -55,10 +55,10 @@ jobs: - id: short-sha name: publish_extension_cli::get_short_sha run: | - echo "sha_short=$(echo "${{ github.sha }}" | cut -c1-7)" >> "$GITHUB_OUTPUT" + echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT" - name: publish_extension_cli::update_sha_in_zed::replace_sha run: | - sed -i "s/ZED_EXTENSION_CLI_SHA: &str = \"[a-f0-9]*\"/ZED_EXTENSION_CLI_SHA: \&str = \"${{ github.sha }}\"/" \ + sed -i "s/ZED_EXTENSION_CLI_SHA: &str = \"[a-f0-9]*\"/ZED_EXTENSION_CLI_SHA: \&str = \"$GITHUB_SHA\"/" \ tooling/xtask/src/tasks/workflows/extension_tests.rs - name: publish_extension_cli::update_sha_in_zed::regenerate_workflows run: cargo xtask workflows @@ -97,7 +97,7 @@ jobs: - id: short-sha name: publish_extension_cli::get_short_sha run: | - echo "sha_short=$(echo "${{ github.sha }}" | cut -c1-7)" >> "$GITHUB_OUTPUT" + echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT" - name: publish_extension_cli::update_sha_in_extensions::checkout_extensions_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: @@ -105,7 +105,7 @@ jobs: token: ${{ steps.generate-token.outputs.token }} - name: publish_extension_cli::update_sha_in_extensions::replace_sha run: | - sed -i "s/ZED_EXTENSION_CLI_SHA: [a-f0-9]*/ZED_EXTENSION_CLI_SHA: ${{ github.sha }}/" \ + sed -i "s/ZED_EXTENSION_CLI_SHA: [a-f0-9]*/ZED_EXTENSION_CLI_SHA: $GITHUB_SHA/" \ .github/workflows/ci.yml - name: publish_extension_cli::create_pull_request_extensions uses: peter-evans/create-pull-request@v7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ac5eeb998f5102d5af9b2775a82093b6ea29858..8adad5cfba278dc68dd227b86455510278c7a1ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -257,8 +257,14 @@ jobs: name: run_tests::check_scripts::download_actionlint run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) - name: run_tests::check_scripts::run_actionlint - run: | - ${{ steps.get_actionlint.outputs.executable }} -color + run: '"$ACTIONLINT_BIN" -color' + env: + ACTIONLINT_BIN: ${{ steps.get_actionlint.outputs.executable }} + - name: steps::cache_rust_dependencies_namespace + uses: namespacelabs/nscloud-cache-action@v1 + with: + cache: rust + path: ~/.rustup - name: run_tests::check_scripts::check_xtask_workflows run: | cargo xtask workflows @@ -654,12 +660,7 @@ jobs: - id: generate-webhook-message name: release::generate_slack_message run: | - MESSAGE=$(DRAFT_RESULT="${{ needs.create_draft_release.result }}" - UPLOAD_RESULT="${{ needs.upload_release_assets.result }}" - VALIDATE_RESULT="${{ needs.validate_release_assets.result }}" - AUTO_RELEASE_RESULT="${{ needs.auto_release_preview.result }}" - TAG="$GITHUB_REF_NAME" - RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + MESSAGE=$(TAG="$GITHUB_REF_NAME" if [ "$DRAFT_RESULT" == "failure" ]; then echo "❌ Draft release creation failed for $TAG: $RUN_URL" @@ -669,19 +670,19 @@ jobs: echo "❌ Release asset upload failed for $TAG: $RELEASE_URL" elif [ "$UPLOAD_RESULT" == "cancelled" ] || [ "$UPLOAD_RESULT" == "skipped" ]; then FAILED_JOBS="" - if [ "${{ needs.run_tests_mac.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS run_tests_mac"; fi - if [ "${{ needs.run_tests_linux.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS run_tests_linux"; fi - if [ "${{ needs.run_tests_windows.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS run_tests_windows"; fi - if [ "${{ needs.clippy_mac.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS clippy_mac"; fi - if [ "${{ needs.clippy_linux.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS clippy_linux"; fi - if [ "${{ needs.clippy_windows.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS clippy_windows"; fi - if [ "${{ needs.check_scripts.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS check_scripts"; fi - if [ "${{ needs.bundle_linux_aarch64.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_linux_aarch64"; fi - if [ "${{ needs.bundle_linux_x86_64.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_linux_x86_64"; fi - if [ "${{ needs.bundle_mac_aarch64.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_mac_aarch64"; fi - if [ "${{ needs.bundle_mac_x86_64.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_mac_x86_64"; fi - if [ "${{ needs.bundle_windows_aarch64.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_windows_aarch64"; fi - if [ "${{ needs.bundle_windows_x86_64.result }}" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_windows_x86_64"; fi + if [ "$RESULT_RUN_TESTS_MAC" == "failure" ];then FAILED_JOBS="$FAILED_JOBS run_tests_mac"; fi + if [ "$RESULT_RUN_TESTS_LINUX" == "failure" ];then FAILED_JOBS="$FAILED_JOBS run_tests_linux"; fi + if [ "$RESULT_RUN_TESTS_WINDOWS" == "failure" ];then FAILED_JOBS="$FAILED_JOBS run_tests_windows"; fi + if [ "$RESULT_CLIPPY_MAC" == "failure" ];then FAILED_JOBS="$FAILED_JOBS clippy_mac"; fi + if [ "$RESULT_CLIPPY_LINUX" == "failure" ];then FAILED_JOBS="$FAILED_JOBS clippy_linux"; fi + if [ "$RESULT_CLIPPY_WINDOWS" == "failure" ];then FAILED_JOBS="$FAILED_JOBS clippy_windows"; fi + if [ "$RESULT_CHECK_SCRIPTS" == "failure" ];then FAILED_JOBS="$FAILED_JOBS check_scripts"; fi + if [ "$RESULT_BUNDLE_LINUX_AARCH64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_linux_aarch64"; fi + if [ "$RESULT_BUNDLE_LINUX_X86_64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_linux_x86_64"; fi + if [ "$RESULT_BUNDLE_MAC_AARCH64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_mac_aarch64"; fi + if [ "$RESULT_BUNDLE_MAC_X86_64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_mac_x86_64"; fi + if [ "$RESULT_BUNDLE_WINDOWS_AARCH64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_windows_aarch64"; fi + if [ "$RESULT_BUNDLE_WINDOWS_X86_64" == "failure" ];then FAILED_JOBS="$FAILED_JOBS bundle_windows_x86_64"; fi FAILED_JOBS=$(echo "$FAILED_JOBS" | xargs) if [ "$UPLOAD_RESULT" == "cancelled" ]; then if [ -n "$FAILED_JOBS" ]; then @@ -710,12 +711,29 @@ jobs: echo "message=$MESSAGE" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ github.token }} + DRAFT_RESULT: ${{ needs.create_draft_release.result }} + UPLOAD_RESULT: ${{ needs.upload_release_assets.result }} + VALIDATE_RESULT: ${{ needs.validate_release_assets.result }} + AUTO_RELEASE_RESULT: ${{ needs.auto_release_preview.result }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + RESULT_RUN_TESTS_MAC: ${{ needs.run_tests_mac.result }} + RESULT_RUN_TESTS_LINUX: ${{ needs.run_tests_linux.result }} + RESULT_RUN_TESTS_WINDOWS: ${{ needs.run_tests_windows.result }} + RESULT_CLIPPY_MAC: ${{ needs.clippy_mac.result }} + RESULT_CLIPPY_LINUX: ${{ needs.clippy_linux.result }} + RESULT_CLIPPY_WINDOWS: ${{ needs.clippy_windows.result }} + RESULT_CHECK_SCRIPTS: ${{ needs.check_scripts.result }} + RESULT_BUNDLE_LINUX_AARCH64: ${{ needs.bundle_linux_aarch64.result }} + RESULT_BUNDLE_LINUX_X86_64: ${{ needs.bundle_linux_x86_64.result }} + RESULT_BUNDLE_MAC_AARCH64: ${{ needs.bundle_mac_aarch64.result }} + RESULT_BUNDLE_MAC_X86_64: ${{ needs.bundle_mac_x86_64.result }} + RESULT_BUNDLE_WINDOWS_AARCH64: ${{ needs.bundle_windows_aarch64.result }} + RESULT_BUNDLE_WINDOWS_X86_64: ${{ needs.bundle_windows_x86_64.result }} - name: release::send_slack_message - run: | - curl -X POST -H 'Content-type: application/json'\ - --data '{"text":"${{ steps.generate-webhook-message.outputs.message }}"}' "$SLACK_WEBHOOK" + run: 'curl -X POST -H ''Content-type: application/json'' --data "$(jq -n --arg text "$SLACK_MESSAGE" ''{"text": $text}'')" "$SLACK_WEBHOOK"' env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_WORKFLOW_FAILURES }} + SLACK_MESSAGE: ${{ steps.generate-webhook-message.outputs.message }} concurrency: group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} cancel-in-progress: true diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 7f243411b4f540d6c7bc611df4883f5341d6a83b..46d8732b08ea658275e1fb21117a09b9e0668933 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -554,11 +554,10 @@ jobs: runs-on: namespace-profile-2x4-ubuntu-2404 steps: - name: release::send_slack_message - run: | - curl -X POST -H 'Content-type: application/json'\ - --data '{"text":"❌ ${{ github.workflow }} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' "$SLACK_WEBHOOK" + run: 'curl -X POST -H ''Content-type: application/json'' --data "$(jq -n --arg text "$SLACK_MESSAGE" ''{"text": $text}'')" "$SLACK_WEBHOOK"' env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_WORKFLOW_FAILURES }} + SLACK_MESSAGE: '❌ ${{ github.workflow }} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' defaults: run: shell: bash -euxo pipefail {0} diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 29f888cbb596593052c6adebe2341171eac9055d..00d69639a53868386157e67aeab5ce7383d32426 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -35,7 +35,7 @@ jobs: git fetch origin "$GITHUB_BASE_REF" --depth=350 COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)" fi - CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" ${{ github.sha }})" + CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")" check_pattern() { local output_name="$1" @@ -653,8 +653,14 @@ jobs: name: run_tests::check_scripts::download_actionlint run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) - name: run_tests::check_scripts::run_actionlint - run: | - ${{ steps.get_actionlint.outputs.executable }} -color + run: '"$ACTIONLINT_BIN" -color' + env: + ACTIONLINT_BIN: ${{ steps.get_actionlint.outputs.executable }} + - name: steps::cache_rust_dependencies_namespace + uses: namespacelabs/nscloud-cache-action@v1 + with: + cache: rust + path: ~/.rustup - name: run_tests::check_scripts::check_xtask_workflows run: | cargo xtask workflows @@ -735,23 +741,39 @@ jobs: if [[ "$2" != "skipped" && "$2" != "success" ]]; then EXIT_CODE=1; fi } - check_result "orchestrate" "${{ needs.orchestrate.result }}" - check_result "check_style" "${{ needs.check_style.result }}" - check_result "clippy_windows" "${{ needs.clippy_windows.result }}" - check_result "clippy_linux" "${{ needs.clippy_linux.result }}" - check_result "clippy_mac" "${{ needs.clippy_mac.result }}" - check_result "run_tests_windows" "${{ needs.run_tests_windows.result }}" - check_result "run_tests_linux" "${{ needs.run_tests_linux.result }}" - check_result "run_tests_mac" "${{ needs.run_tests_mac.result }}" - check_result "doctests" "${{ needs.doctests.result }}" - check_result "check_workspace_binaries" "${{ needs.check_workspace_binaries.result }}" - check_result "check_wasm" "${{ needs.check_wasm.result }}" - check_result "check_dependencies" "${{ needs.check_dependencies.result }}" - check_result "check_docs" "${{ needs.check_docs.result }}" - check_result "check_licenses" "${{ needs.check_licenses.result }}" - check_result "check_scripts" "${{ needs.check_scripts.result }}" + check_result "orchestrate" "$RESULT_ORCHESTRATE" + check_result "check_style" "$RESULT_CHECK_STYLE" + check_result "clippy_windows" "$RESULT_CLIPPY_WINDOWS" + check_result "clippy_linux" "$RESULT_CLIPPY_LINUX" + check_result "clippy_mac" "$RESULT_CLIPPY_MAC" + check_result "run_tests_windows" "$RESULT_RUN_TESTS_WINDOWS" + check_result "run_tests_linux" "$RESULT_RUN_TESTS_LINUX" + check_result "run_tests_mac" "$RESULT_RUN_TESTS_MAC" + check_result "doctests" "$RESULT_DOCTESTS" + check_result "check_workspace_binaries" "$RESULT_CHECK_WORKSPACE_BINARIES" + check_result "check_wasm" "$RESULT_CHECK_WASM" + check_result "check_dependencies" "$RESULT_CHECK_DEPENDENCIES" + check_result "check_docs" "$RESULT_CHECK_DOCS" + check_result "check_licenses" "$RESULT_CHECK_LICENSES" + check_result "check_scripts" "$RESULT_CHECK_SCRIPTS" exit $EXIT_CODE + env: + RESULT_ORCHESTRATE: ${{ needs.orchestrate.result }} + RESULT_CHECK_STYLE: ${{ needs.check_style.result }} + RESULT_CLIPPY_WINDOWS: ${{ needs.clippy_windows.result }} + RESULT_CLIPPY_LINUX: ${{ needs.clippy_linux.result }} + RESULT_CLIPPY_MAC: ${{ needs.clippy_mac.result }} + RESULT_RUN_TESTS_WINDOWS: ${{ needs.run_tests_windows.result }} + RESULT_RUN_TESTS_LINUX: ${{ needs.run_tests_linux.result }} + RESULT_RUN_TESTS_MAC: ${{ needs.run_tests_mac.result }} + RESULT_DOCTESTS: ${{ needs.doctests.result }} + RESULT_CHECK_WORKSPACE_BINARIES: ${{ needs.check_workspace_binaries.result }} + RESULT_CHECK_WASM: ${{ needs.check_wasm.result }} + RESULT_CHECK_DEPENDENCIES: ${{ needs.check_dependencies.result }} + RESULT_CHECK_DOCS: ${{ needs.check_docs.result }} + RESULT_CHECK_LICENSES: ${{ needs.check_licenses.result }} + RESULT_CHECK_SCRIPTS: ${{ needs.check_scripts.result }} concurrency: group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} cancel-in-progress: true diff --git a/.github/workflows/slack_notify_first_responders.yml b/.github/workflows/slack_notify_first_responders.yml index a6f2d557a574778aea6c2a90f9721b5a41bd0724..538d02b582f18db627693b62e439f4142ea29056 100644 --- a/.github/workflows/slack_notify_first_responders.yml +++ b/.github/workflows/slack_notify_first_responders.yml @@ -17,8 +17,9 @@ jobs: id: check-label env: LABEL_NAME: ${{ github.event.label.name }} + FIRST_RESPONDER_LABELS: ${{ env.FIRST_RESPONDER_LABELS }} run: | - if echo '${{ env.FIRST_RESPONDER_LABELS }}' | jq -e --arg label "$LABEL_NAME" 'index($label) != null' > /dev/null; then + if echo "$FIRST_RESPONDER_LABELS" | jq -e --arg label "$LABEL_NAME" 'index($label) != null' > /dev/null; then echo "should_notify=true" >> "$GITHUB_OUTPUT" echo "Label '$LABEL_NAME' requires first responder notification" else diff --git a/.github/workflows/update_duplicate_magnets.yml b/.github/workflows/update_duplicate_magnets.yml index 1c6c5a562532891eb97ceb11f44b81f35612c026..c3832b7bdbec13f74a8136cb1120a682f6e53920 100644 --- a/.github/workflows/update_duplicate_magnets.yml +++ b/.github/workflows/update_duplicate_magnets.yml @@ -21,7 +21,9 @@ jobs: run: pip install requests - name: Update duplicate magnets issue + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | python script/github-find-top-duplicated-bugs.py \ - --github-token ${{ secrets.GITHUB_TOKEN }} \ + --github-token "$GITHUB_TOKEN" \ --issue-number 46355 diff --git a/Cargo.lock b/Cargo.lock index d9c5c69e2d11d9e8f6d5cadd35ec342ebe202e57..4500d7b920358e45887c8c060b8678c22c901be9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -603,6 +603,17 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "annotate-snippets" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86cd1c51b95d71dde52bca69ed225008f6ff4c8cc825b08042aa1ef823e1980" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] + [[package]] name = "anstream" version = "0.6.21" @@ -21529,6 +21540,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" name = "xtask" version = "0.1.0" dependencies = [ + "annotate-snippets", "anyhow", "backtrace", "cargo_metadata", @@ -21537,8 +21549,12 @@ dependencies = [ "gh-workflow", "indexmap", "indoc", + "itertools 0.14.0", + "regex", "serde", "serde_json", + "serde_yaml", + "strum 0.27.2", "toml 0.8.23", "toml_edit 0.22.27", ] diff --git a/tooling/xtask/Cargo.toml b/tooling/xtask/Cargo.toml index 13179b2eb69ba9a63ba6be5784907b78bba1b9f2..21090d1304ea0eab9ad70808b91f76789f2fd923 100644 --- a/tooling/xtask/Cargo.toml +++ b/tooling/xtask/Cargo.toml @@ -9,6 +9,7 @@ license = "GPL-3.0-or-later" workspace = true [dependencies] +annotate-snippets = "0.12.1" anyhow.workspace = true backtrace.workspace = true cargo_metadata.workspace = true @@ -17,7 +18,11 @@ clap = { workspace = true, features = ["derive"] } toml.workspace = true indoc.workspace = true indexmap.workspace = true +itertools.workspace = true +regex.workspace = true serde.workspace = true serde_json.workspace = true +serde_yaml = "0.9.34" +strum.workspace = true toml_edit.workspace = true gh-workflow.workspace = true diff --git a/tooling/xtask/src/main.rs b/tooling/xtask/src/main.rs index 8246b98772184276ecabc685a9b4d2e7c5346edf..05afe3c766829137a7c2ba6e73d57638624d5e6a 100644 --- a/tooling/xtask/src/main.rs +++ b/tooling/xtask/src/main.rs @@ -23,6 +23,7 @@ enum CliCommand { /// Builds GPUI web examples and serves them. WebExamples(tasks::web_examples::WebExamplesArgs), Workflows(tasks::workflows::GenerateWorkflowArgs), + CheckWorkflows(tasks::workflow_checks::WorkflowValidationArgs), } fn main() -> Result<()> { @@ -37,5 +38,6 @@ fn main() -> Result<()> { CliCommand::PublishGpui(args) => tasks::publish_gpui::run_publish_gpui(args), CliCommand::WebExamples(args) => tasks::web_examples::run_web_examples(args), CliCommand::Workflows(args) => tasks::workflows::run_workflows(args), + CliCommand::CheckWorkflows(args) => tasks::workflow_checks::validate(args), } } diff --git a/tooling/xtask/src/tasks.rs b/tooling/xtask/src/tasks.rs index 4701b56d8dd201ad5b5f28764976b0c5397f3a3e..80f504fa0345de0d5bc71c5b44c71846f04c50bc 100644 --- a/tooling/xtask/src/tasks.rs +++ b/tooling/xtask/src/tasks.rs @@ -3,4 +3,5 @@ pub mod licenses; pub mod package_conformity; pub mod publish_gpui; pub mod web_examples; +pub mod workflow_checks; pub mod workflows; diff --git a/tooling/xtask/src/tasks/workflow_checks.rs b/tooling/xtask/src/tasks/workflow_checks.rs new file mode 100644 index 0000000000000000000000000000000000000000..d6be0299327ad2dd4b4a126a61a8b2ae6ddb9fd3 --- /dev/null +++ b/tooling/xtask/src/tasks/workflow_checks.rs @@ -0,0 +1,118 @@ +mod check_run_patterns; + +use std::{fs, path::PathBuf}; + +use annotate_snippets::Renderer; +use anyhow::{Result, anyhow}; +use clap::Parser; +use itertools::{Either, Itertools}; +use serde_yaml::Value; +use strum::IntoEnumIterator; + +use crate::tasks::{ + workflow_checks::check_run_patterns::{ + RunValidationError, WorkflowFile, WorkflowValidationError, + }, + workflows::WorkflowType, +}; + +pub use check_run_patterns::validate_run_command; + +#[derive(Default, Parser)] +pub struct WorkflowValidationArgs {} + +pub fn validate(_: WorkflowValidationArgs) -> Result<()> { + let (parsing_errors, file_errors): (Vec<_>, Vec<_>) = get_all_workflow_files() + .map(check_workflow) + .flat_map(Result::err) + .partition_map(|error| match error { + WorkflowError::ParseError(error) => Either::Left(error), + WorkflowError::ValidationError(error) => Either::Right(error), + }); + + if !parsing_errors.is_empty() { + Err(anyhow!( + "Failed to read or parse some workflow files: {}", + parsing_errors.into_iter().join("\n") + )) + } else if !file_errors.is_empty() { + let errors: Vec<_> = file_errors + .iter() + .map(|error| error.annotation_group()) + .collect(); + + let renderer = + Renderer::styled().decor_style(annotate_snippets::renderer::DecorStyle::Ascii); + println!("{}", renderer.render(errors.as_slice())); + + Err(anyhow!("Workflow checks failed!")) + } else { + Ok(()) + } +} + +enum WorkflowError { + ParseError(anyhow::Error), + ValidationError(Box), +} + +fn get_all_workflow_files() -> impl Iterator { + WorkflowType::iter() + .map(|workflow_type| workflow_type.folder_path()) + .flat_map(|folder_path| { + fs::read_dir(folder_path).into_iter().flat_map(|entries| { + entries + .flat_map(Result::ok) + .map(|entry| entry.path()) + .filter(|path| { + path.extension() + .is_some_and(|ext| ext == "yaml" || ext == "yml") + }) + }) + }) +} + +fn check_workflow(workflow_file_path: PathBuf) -> Result<(), WorkflowError> { + fn collect_errors( + iter: impl Iterator>>, + ) -> Result<(), Vec> { + Some(iter.flat_map(Result::err).flatten().collect::>()) + .filter(|errors| !errors.is_empty()) + .map_or(Ok(()), Err) + } + + fn check_recursive(key: &Value, value: &Value) -> Result<(), Vec> { + match value { + Value::Mapping(mapping) => collect_errors( + mapping + .into_iter() + .map(|(key, value)| check_recursive(key, value)), + ), + Value::Sequence(sequence) => collect_errors( + sequence + .into_iter() + .map(|value| check_recursive(key, value)), + ), + Value::String(string) => check_string(key, string).map_err(|error| vec![error]), + Value::Null | Value::Bool(_) | Value::Number(_) | Value::Tagged(_) => Ok(()), + } + } + + let file_content = + WorkflowFile::load(&workflow_file_path).map_err(WorkflowError::ParseError)?; + + check_recursive(&Value::Null, &file_content.parsed_content).map_err(|errors| { + WorkflowError::ValidationError(Box::new(WorkflowValidationError::new( + errors, + file_content, + workflow_file_path, + ))) + }) +} + +fn check_string(key: &Value, value: &str) -> Result<(), RunValidationError> { + match key { + Value::String(key) if key == "run" => validate_run_command(value), + _ => Ok(()), + } +} diff --git a/tooling/xtask/src/tasks/workflow_checks/check_run_patterns.rs b/tooling/xtask/src/tasks/workflow_checks/check_run_patterns.rs new file mode 100644 index 0000000000000000000000000000000000000000..50c435d033336dd82d2f110f5c880dff0d677e52 --- /dev/null +++ b/tooling/xtask/src/tasks/workflow_checks/check_run_patterns.rs @@ -0,0 +1,124 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Snippet}; +use anyhow::{Result, anyhow}; +use regex::Regex; +use serde_yaml::Value; +use std::{ + collections::HashMap, + fs, + ops::Range, + path::{Path, PathBuf}, + sync::LazyLock, +}; + +static GITHUB_INPUT_PATTERN: LazyLock = LazyLock::new(|| { + Regex::new(r#"\$\{\{[[:blank:]]*([[:alnum:]]|[[:punct:]])+?[[:blank:]]*\}\}"#) + .expect("Should compile") +}); + +pub struct WorkflowFile { + raw_content: String, + pub parsed_content: Value, +} + +impl WorkflowFile { + pub fn load(workflow_file_path: &Path) -> Result { + fs::read_to_string(workflow_file_path) + .map_err(|_| { + anyhow!( + "Could not read workflow file at {}", + workflow_file_path.display() + ) + }) + .and_then(|file_content| { + serde_yaml::from_str(&file_content) + .map(|parsed_content| Self { + raw_content: file_content, + parsed_content, + }) + .map_err(|e| anyhow!("Failed to parse workflow file: {e:?}")) + }) + } +} + +pub struct WorkflowValidationError { + file_path: PathBuf, + contents: WorkflowFile, + errors: Vec, +} + +impl WorkflowValidationError { + pub fn new( + errors: Vec, + contents: WorkflowFile, + file_path: PathBuf, + ) -> Self { + Self { + file_path, + contents, + errors, + } + } + + pub fn annotation_group<'a>(&'a self) -> Group<'a> { + let raw_content = &self.contents.raw_content; + let mut identical_lines = HashMap::new(); + + let ranges = self + .errors + .iter() + .flat_map(|error| error.found_injection_patterns.iter()) + .map(|(line, pattern_range)| { + let initial_offset = identical_lines + .get(&(line.as_str(), pattern_range.start)) + .copied() + .unwrap_or_default(); + + let line_start = raw_content[initial_offset..] + .find(line.as_str()) + .map(|offset| offset + initial_offset) + .unwrap_or_default(); + + let pattern_start = line_start + pattern_range.start; + let pattern_end = pattern_start + pattern_range.len(); + + identical_lines.insert((line.as_str(), pattern_range.start), pattern_end); + + pattern_start..pattern_end + }); + + Level::ERROR + .primary_title("Found GitHub input injection in run command") + .element( + Snippet::source(&self.contents.raw_content) + .path(self.file_path.display().to_string()) + .annotations(ranges.map(|range| { + AnnotationKind::Primary + .span(range) + .label("This should be passed via an environment variable") + })), + ) + } +} + +pub struct RunValidationError { + found_injection_patterns: Vec<(String, Range)>, +} + +pub fn validate_run_command(command: &str) -> Result<(), RunValidationError> { + let patterns: Vec<_> = command + .lines() + .flat_map(move |line| { + GITHUB_INPUT_PATTERN + .find_iter(line) + .map(|m| (line.to_owned(), m.range())) + }) + .collect(); + + if patterns.is_empty() { + Ok(()) + } else { + Err(RunValidationError { + found_injection_patterns: patterns, + }) + } +} diff --git a/tooling/xtask/src/tasks/workflows.rs b/tooling/xtask/src/tasks/workflows.rs index 5663ebec247c4025f7cfbae8e9467733e2c7be2d..9151b9c671ef42e3dc54661f80438a4e31aff1e9 100644 --- a/tooling/xtask/src/tasks/workflows.rs +++ b/tooling/xtask/src/tasks/workflows.rs @@ -4,6 +4,8 @@ use gh_workflow::Workflow; use std::fs; use std::path::{Path, PathBuf}; +use crate::tasks::workflow_checks::{self}; + mod after_release; mod autofix_pr; mod bump_patch_version; @@ -87,8 +89,8 @@ impl WorkflowFile { } } -#[derive(PartialEq, Eq)] -enum WorkflowType { +#[derive(PartialEq, Eq, strum::EnumIter)] +pub enum WorkflowType { /// Workflows living in the Zed repository Zed, /// Workflows living in the `zed-extensions/workflows` repository that are @@ -113,7 +115,7 @@ impl WorkflowType { ) } - fn folder_path(&self) -> PathBuf { + pub fn folder_path(&self) -> PathBuf { match self { WorkflowType::Zed => PathBuf::from(".github/workflows"), WorkflowType::ExtensionCi => PathBuf::from("extensions/workflows"), @@ -155,5 +157,5 @@ pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> { workflow_file.generate_file()?; } - Ok(()) + workflow_checks::validate(Default::default()) } diff --git a/tooling/xtask/src/tasks/workflows/after_release.rs b/tooling/xtask/src/tasks/workflows/after_release.rs index 3936e3ffb7754d167c6c39f02e17f758bed0c1ae..07ff1fba0d4799c463128362ad4ba996ccf8cea0 100644 --- a/tooling/xtask/src/tasks/workflows/after_release.rs +++ b/tooling/xtask/src/tasks/workflows/after_release.rs @@ -123,7 +123,7 @@ fn publish_winget() -> NamedJob { "X-GitHub-Api-Version" = "2022-11-28" } $body = @{ branch = "master" } | ConvertTo-Json - $uri = "https://api.github.com/repos/${{ github.repository_owner }}/winget-pkgs/merge-upstream" + $uri = "https://api.github.com/repos/$env:GITHUB_REPOSITORY_OWNER/winget-pkgs/merge-upstream" try { Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -ContentType "application/json" Write-Host "Successfully synced winget-pkgs fork" diff --git a/tooling/xtask/src/tasks/workflows/autofix_pr.rs b/tooling/xtask/src/tasks/workflows/autofix_pr.rs index c2c89b7cd05394c225c015a6cc83f48bd35b24a4..2779dc2b01fa873bc050be4d873b9a5d502606bd 100644 --- a/tooling/xtask/src/tasks/workflows/autofix_pr.rs +++ b/tooling/xtask/src/tasks/workflows/autofix_pr.rs @@ -55,7 +55,8 @@ fn download_patch_artifact() -> Step { fn run_autofix(pr_number: &WorkflowInput, run_clippy: &WorkflowInput) -> NamedJob { fn checkout_pr(pr_number: &WorkflowInput) -> Step { - named::bash(&format!("gh pr checkout {pr_number}")) + named::bash(r#"gh pr checkout "$PR_NUMBER""#) + .add_env(("PR_NUMBER", pr_number.to_string())) .add_env(("GITHUB_TOKEN", vars::GITHUB_TOKEN)) } @@ -133,7 +134,9 @@ fn run_autofix(pr_number: &WorkflowInput, run_clippy: &WorkflowInput) -> NamedJo fn commit_changes(pr_number: &WorkflowInput, autofix_job: &NamedJob) -> NamedJob { fn checkout_pr(pr_number: &WorkflowInput, token: &StepOutput) -> Step { - named::bash(&format!("gh pr checkout {pr_number}")).add_env(("GITHUB_TOKEN", token)) + named::bash(r#"gh pr checkout "$PR_NUMBER""#) + .add_env(("PR_NUMBER", pr_number.to_string())) + .add_env(("GITHUB_TOKEN", token)) } fn apply_patch() -> Step { diff --git a/tooling/xtask/src/tasks/workflows/cherry_pick.rs b/tooling/xtask/src/tasks/workflows/cherry_pick.rs index eaa786837f84ebf4d4f7e1a579db0c7b4dcc5040..5680bf6b23b85c17e68e531cecadfb31f091520d 100644 --- a/tooling/xtask/src/tasks/workflows/cherry_pick.rs +++ b/tooling/xtask/src/tasks/workflows/cherry_pick.rs @@ -35,7 +35,10 @@ fn run_cherry_pick( channel: &WorkflowInput, token: &StepOutput, ) -> Step { - named::bash(&format!("./script/cherry-pick {branch} {commit} {channel}")) + named::bash(r#"./script/cherry-pick "$BRANCH" "$COMMIT" "$CHANNEL""#) + .add_env(("BRANCH", branch.to_string())) + .add_env(("COMMIT", commit.to_string())) + .add_env(("CHANNEL", channel.to_string())) .add_env(("GIT_COMMITTER_NAME", "Zed Zippy")) .add_env(("GIT_COMMITTER_EMAIL", "hi@zed.dev")) .add_env(("GITHUB_TOKEN", token)) diff --git a/tooling/xtask/src/tasks/workflows/compare_perf.rs b/tooling/xtask/src/tasks/workflows/compare_perf.rs index 1d111acc4f8a4dc47edea6f45c0b93c845b7cda2..74a1fbdc389e2b0dacdf579d9ee96a0366eb5c01 100644 --- a/tooling/xtask/src/tasks/workflows/compare_perf.rs +++ b/tooling/xtask/src/tasks/workflows/compare_perf.rs @@ -29,14 +29,16 @@ pub fn run_perf( crate_name: &WorkflowInput, ) -> NamedJob { fn cargo_perf_test(ref_name: &WorkflowInput, crate_name: &WorkflowInput) -> Step { - named::bash(&format!( - " - if [ -n \"{crate_name}\" ]; then - cargo perf-test -p {crate_name} -- --json={ref_name}; + named::bash( + r#" + if [ -n "$CRATE_NAME" ]; then + cargo perf-test -p "$CRATE_NAME" -- --json="$REF_NAME"; else - cargo perf-test -p vim -- --json={ref_name}; - fi" - )) + cargo perf-test -p vim -- --json="$REF_NAME"; + fi"#, + ) + .add_env(("REF_NAME", ref_name.to_string())) + .add_env(("CRATE_NAME", crate_name.to_string())) } fn install_hyperfine() -> Step { @@ -44,9 +46,9 @@ pub fn run_perf( } fn compare_runs(head: &WorkflowInput, base: &WorkflowInput) -> Step { - named::bash(&format!( - "cargo perf-compare --save=results.md {base} {head}" - )) + named::bash(r#"cargo perf-compare --save=results.md "$BASE" "$HEAD""#) + .add_env(("BASE", base.to_string())) + .add_env(("HEAD", head.to_string())) } named::job( diff --git a/tooling/xtask/src/tasks/workflows/deploy_collab.rs b/tooling/xtask/src/tasks/workflows/deploy_collab.rs index 58212118c7ba4fa6d44d5f29fac671ca6eb5e662..300680f95b880e9adb14dffd2572d80cb08fd63c 100644 --- a/tooling/xtask/src/tasks/workflows/deploy_collab.rs +++ b/tooling/xtask/src/tasks/workflows/deploy_collab.rs @@ -1,5 +1,5 @@ use gh_workflow::{Container, Event, Port, Push, Run, Step, Use, Workflow}; -use indoc::{formatdoc, indoc}; +use indoc::indoc; use crate::tasks::workflows::runners::{self, Platform}; use crate::tasks::workflows::steps::{ @@ -115,9 +115,10 @@ fn deploy(deps: &[&NamedJob]) -> NamedJob { } fn sign_into_kubernetes() -> Step { - named::bash(formatdoc! {r#" - doctl kubernetes cluster kubeconfig save --expiry-seconds 600 {cluster_name} - "#, cluster_name = vars::CLUSTER_NAME}) + named::bash( + r#"doctl kubernetes cluster kubeconfig save --expiry-seconds 600 "$CLUSTER_NAME""#, + ) + .add_env(("CLUSTER_NAME", vars::CLUSTER_NAME)) } fn start_rollout() -> Step { @@ -139,7 +140,7 @@ fn deploy(deps: &[&NamedJob]) -> NamedJob { echo "Deploying collab:$GITHUB_SHA to $ZED_KUBE_NAMESPACE" source script/lib/deploy-helpers.sh - export_vars_for_environment $ZED_KUBE_NAMESPACE + export_vars_for_environment "$ZED_KUBE_NAMESPACE" ZED_DO_CERTIFICATE_ID="$(doctl compute certificate list --format ID --no-header)" export ZED_DO_CERTIFICATE_ID @@ -149,14 +150,14 @@ fn deploy(deps: &[&NamedJob]) -> NamedJob { export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT export DATABASE_MAX_CONNECTIONS=850 envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f - - kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch + kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}" export ZED_SERVICE_NAME=api export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_API_LOAD_BALANCER_SIZE_UNIT export DATABASE_MAX_CONNECTIONS=60 envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f - - kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch + kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}" "#}) } diff --git a/tooling/xtask/src/tasks/workflows/extension_bump.rs b/tooling/xtask/src/tasks/workflows/extension_bump.rs index 746b842f18dfcc8805be9285facefdfa52085b84..8c31de202ee7ac81b5f5e95fb26ec89452fd077c 100644 --- a/tooling/xtask/src/tasks/workflows/extension_bump.rs +++ b/tooling/xtask/src/tasks/workflows/extension_bump.rs @@ -150,7 +150,7 @@ pub(crate) fn compare_versions() -> (Step, StepOutput, StepOutput) { r#" CURRENT_VERSION="$({VERSION_CHECK})" - if [[ "${{{{ github.event_name }}}}" == "pull_request" ]]; then + if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then PR_FORK_POINT="$(git merge-base origin/main HEAD)" git checkout "$PR_FORK_POINT" elif BRANCH_PARENT_SHA="$(git merge-base origin/main origin/zed-zippy-autobump)"; then @@ -258,8 +258,6 @@ fn install_bump_2_version() -> Step { fn bump_version(current_version: &JobOutput, bump_type: &WorkflowInput) -> (Step, StepOutput) { let step = named::bash(formatdoc! {r#" - OLD_VERSION="{current_version}" - BUMP_FILES=("extension.toml") if [[ -f "Cargo.toml" ]]; then BUMP_FILES+=("Cargo.toml") @@ -269,7 +267,7 @@ fn bump_version(current_version: &JobOutput, bump_type: &WorkflowInput) -> (Step --search "version = \"{{current_version}}"\" \ --replace "version = \"{{new_version}}"\" \ --current-version "$OLD_VERSION" \ - --no-configured-files {bump_type} "${{BUMP_FILES[@]}}" + --no-configured-files "$BUMP_TYPE" "${{BUMP_FILES[@]}}" if [[ -f "Cargo.toml" ]]; then cargo update --workspace @@ -280,7 +278,9 @@ fn bump_version(current_version: &JobOutput, bump_type: &WorkflowInput) -> (Step echo "new_version=${{NEW_VERSION}}" >> "$GITHUB_OUTPUT" "# }) - .id("bump-version"); + .id("bump-version") + .add_env(("OLD_VERSION", current_version.to_string())) + .add_env(("BUMP_TYPE", bump_type.to_string())); let new_version = StepOutput::new(&step, "new_version"); (step, new_version) diff --git a/tooling/xtask/src/tasks/workflows/extension_tests.rs b/tooling/xtask/src/tasks/workflows/extension_tests.rs index de4f1dd94267a55dcc3e1555c1a5673ff813ad26..09f0cadf1c8731f8eed4ef1197a7edd05e0d1558 100644 --- a/tooling/xtask/src/tasks/workflows/extension_tests.rs +++ b/tooling/xtask/src/tasks/workflows/extension_tests.rs @@ -1,5 +1,5 @@ use gh_workflow::*; -use indoc::{formatdoc, indoc}; +use indoc::indoc; use crate::tasks::workflows::{ extension_bump::compare_versions, @@ -142,12 +142,14 @@ pub fn check() -> Step { } fn verify_version_did_not_change(version_changed: StepOutput) -> Step { - named::bash(formatdoc! {r#" - if [[ {version_changed} == "true" && "${{{{ github.event_name }}}}" == "pull_request" && "${{{{ github.event.pull_request.user.login }}}}" != "zed-zippy[bot]" ]] ; then + named::bash(indoc! {r#" + if [[ "$VERSION_CHANGED" == "true" && "$GITHUB_EVENT_NAME" == "pull_request" && "$PR_USER_LOGIN" != "zed-zippy[bot]" ]] ; then echo "Version change detected in your change!" echo "Version changes happen in separate PRs and will be performed by the zed-zippy bot" exit 42 fi "# }) + .add_env(("VERSION_CHANGED", version_changed.to_string())) + .add_env(("PR_USER_LOGIN", "${{ github.event.pull_request.user.login }}")) } diff --git a/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs b/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs index 2ba6069c273e8a3e9a27885595d2ad5380748cdd..6f03ad1521850fb24c5bad7265ebf913228c5077 100644 --- a/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs +++ b/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs @@ -105,10 +105,8 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { } fn get_removed_files(prev_commit: &StepOutput) -> (Step, StepOutput) { - let step = named::bash(formatdoc! {r#" - PREV_COMMIT="{prev_commit}" - - if [ "${{{{ matrix.repo }}}}" = "workflows" ]; then + let step = named::bash(indoc::indoc! {r#" + if [ "$MATRIX_REPO" = "workflows" ]; then WORKFLOW_DIR="extensions/workflows" else WORKFLOW_DIR="extensions/workflows/shared" @@ -119,8 +117,8 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { # Get deleted files (status D) and renamed files (status R - old name needs removal) # Using -M to detect renames, then extracting files that are gone from their original location REMOVED_FILES=$(git diff --name-status -M "$PREV_COMMIT" HEAD -- "$WORKFLOW_DIR" | \ - awk '/^D/ {{ print $2 }} /^R/ {{ print $2 }}' | \ - xargs -I{{}} basename {{}} 2>/dev/null | \ + awk '/^D/ { print $2 } /^R/ { print $2 }' | \ + xargs -I{} basename {} 2>/dev/null | \ tr '\n' ' ' || echo "") REMOVED_FILES=$(echo "$REMOVED_FILES" | xargs) @@ -129,7 +127,9 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { echo "removed_files=$REMOVED_FILES" >> "$GITHUB_OUTPUT" "#}) .id("calc-changes") - .working_directory("zed"); + .working_directory("zed") + .add_env(("PREV_COMMIT", prev_commit.to_string())) + .add_env(("MATRIX_REPO", "${{ matrix.repo }}")); let removed_files = StepOutput::new(&step, "removed_files"); @@ -137,9 +137,7 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { } fn sync_workflow_files(removed_files: &StepOutput) -> Step { - named::bash(formatdoc! {r#" - REMOVED_FILES="{removed_files}" - + named::bash(indoc::indoc! {r#" mkdir -p extension/.github/workflows cd extension/.github/workflows @@ -153,12 +151,14 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { cd - > /dev/null - if [ "${{{{ matrix.repo }}}}" = "workflows" ]; then + if [ "$MATRIX_REPO" = "workflows" ]; then cp zed/extensions/workflows/*.yml extension/.github/workflows/ else cp zed/extensions/workflows/shared/*.yml extension/.github/workflows/ fi "#}) + .add_env(("REMOVED_FILES", removed_files.to_string())) + .add_env(("MATRIX_REPO", "${{ matrix.repo }}")) } fn get_short_sha() -> (Step, StepOutput) { @@ -205,13 +205,16 @@ fn rollout_workflows_to_extension(fetch_repos_job: &NamedJob) -> NamedJob { fn enable_auto_merge(token: &StepOutput) -> Step { named::bash(indoc::indoc! {r#" - PR_NUMBER="${{ steps.create-pr.outputs.pull-request-number }}" if [ -n "$PR_NUMBER" ]; then cd extension gh pr merge "$PR_NUMBER" --auto --squash fi "#}) .add_env(("GH_TOKEN", token.to_string())) + .add_env(( + "PR_NUMBER", + "${{ steps.create-pr.outputs.pull-request-number }}", + )) } let (authenticate, token) = generate_token( diff --git a/tooling/xtask/src/tasks/workflows/publish_extension_cli.rs b/tooling/xtask/src/tasks/workflows/publish_extension_cli.rs index 549b0fdfcfbb8f44b24ac849e2fe3c13bf5acdb0..2269201a2de383bc5ae7147d9e1d08105c540d15 100644 --- a/tooling/xtask/src/tasks/workflows/publish_extension_cli.rs +++ b/tooling/xtask/src/tasks/workflows/publish_extension_cli.rs @@ -28,7 +28,7 @@ fn publish_job() -> NamedJob { } fn upload_binary() -> Step { - named::bash("script/upload-extension-cli ${{ github.sha }}") + named::bash(r#"script/upload-extension-cli "$GITHUB_SHA""#) .add_env(( "DIGITALOCEAN_SPACES_ACCESS_KEY", vars::DIGITALOCEAN_SPACES_ACCESS_KEY, @@ -60,7 +60,7 @@ fn update_sha_in_zed(publish_job: &NamedJob) -> NamedJob { fn replace_sha() -> Step { named::bash(indoc! {r#" - sed -i "s/ZED_EXTENSION_CLI_SHA: &str = \"[a-f0-9]*\"/ZED_EXTENSION_CLI_SHA: \&str = \"${{ github.sha }}\"/" \ + sed -i "s/ZED_EXTENSION_CLI_SHA: &str = \"[a-f0-9]*\"/ZED_EXTENSION_CLI_SHA: \&str = \"$GITHUB_SHA\"/" \ tooling/xtask/src/tasks/workflows/extension_tests.rs "#}) } @@ -139,7 +139,7 @@ fn update_sha_in_extensions(publish_job: &NamedJob) -> NamedJob { fn replace_sha() -> Step { named::bash(indoc! {r#" - sed -i "s/ZED_EXTENSION_CLI_SHA: [a-f0-9]*/ZED_EXTENSION_CLI_SHA: ${{ github.sha }}/" \ + sed -i "s/ZED_EXTENSION_CLI_SHA: [a-f0-9]*/ZED_EXTENSION_CLI_SHA: $GITHUB_SHA/" \ .github/workflows/ci.yml "#}) } @@ -191,7 +191,7 @@ fn create_pull_request_extensions( fn get_short_sha() -> (Step, StepOutput) { let step = named::bash(indoc::indoc! {r#" - echo "sha_short=$(echo "${{ github.sha }}" | cut -c1-7)" >> "$GITHUB_OUTPUT" + echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT" "#}) .id("short-sha"); diff --git a/tooling/xtask/src/tasks/workflows/release.rs b/tooling/xtask/src/tasks/workflows/release.rs index 8241fc58f0821b950e32ee9b1a42473975ec008d..2963bbec24301b85b345461a6ea532a9ac3421c5 100644 --- a/tooling/xtask/src/tasks/workflows/release.rs +++ b/tooling/xtask/src/tasks/workflows/release.rs @@ -272,18 +272,55 @@ pub(crate) fn push_release_update_notification( test_jobs: &[&NamedJob], bundle_jobs: &ReleaseBundleJobs, ) -> NamedJob { - let all_job_names = test_jobs - .into_iter() + fn env_name(name: &str) -> String { + format!("RESULT_{}", name.to_uppercase()) + } + + let all_job_names: Vec<&str> = test_jobs + .iter() .map(|j| j.name.as_ref()) - .chain(bundle_jobs.jobs().into_iter().map(|j| j.name.as_ref())); + .chain(bundle_jobs.jobs().into_iter().map(|j| j.name.as_ref())) + .collect(); + + let env_entries = [ + ( + "DRAFT_RESULT".into(), + format!("${{{{ needs.{}.result }}}}", create_draft_release_job.name), + ), + ( + "UPLOAD_RESULT".into(), + format!("${{{{ needs.{}.result }}}}", upload_assets_job.name), + ), + ( + "VALIDATE_RESULT".into(), + format!("${{{{ needs.{}.result }}}}", validate_assets_job.name), + ), + ( + "AUTO_RELEASE_RESULT".into(), + format!("${{{{ needs.{}.result }}}}", auto_release_preview.name), + ), + ("RUN_URL".into(), CURRENT_ACTION_RUN_URL.to_string()), + ] + .into_iter() + .chain( + all_job_names + .iter() + .map(|name| (env_name(name), format!("${{{{ needs.{name}.result }}}}"))), + ); + + let failure_checks = all_job_names + .iter() + .map(|name| { + format!( + "if [ \"${env_name}\" == \"failure\" ];then FAILED_JOBS=\"$FAILED_JOBS {name}\"; fi", + env_name = env_name(name) + ) + }) + .collect::>() + .join("\n "); let notification_script = formatdoc! {r#" - DRAFT_RESULT="${{{{ needs.{draft_job}.result }}}}" - UPLOAD_RESULT="${{{{ needs.{upload_job}.result }}}}" - VALIDATE_RESULT="${{{{ needs.{validate_job}.result }}}}" - AUTO_RELEASE_RESULT="${{{{ needs.{auto_release_job}.result }}}}" TAG="$GITHUB_REF_NAME" - RUN_URL="{run_url}" if [ "$DRAFT_RESULT" == "failure" ]; then echo "❌ Draft release creation failed for $TAG: $RUN_URL" @@ -319,19 +356,6 @@ pub(crate) fn push_release_update_notification( fi fi "#, - draft_job = create_draft_release_job.name, - upload_job = upload_assets_job.name, - validate_job = validate_assets_job.name, - auto_release_job = auto_release_preview.name, - run_url = CURRENT_ACTION_RUN_URL, - failure_checks = all_job_names - .into_iter() - .map(|name: &str| format!( - "if [ \"${{{{ needs.{name}.result }}}}\" == \"failure\" ];\ - then FAILED_JOBS=\"$FAILED_JOBS {name}\"; fi" - )) - .collect::>() - .join("\n "), }; let mut all_deps: Vec<&NamedJob> = vec![ @@ -347,7 +371,10 @@ pub(crate) fn push_release_update_notification( .runs_on(runners::LINUX_SMALL) .cond(Expression::new("always()")); - for step in notify_slack(MessageType::Evaluated(notification_script)) { + for step in notify_slack(MessageType::Evaluated { + script: notification_script, + env: env_entries.collect(), + }) { job = job.add_step(step); } named::job(job) @@ -368,14 +395,17 @@ pub(crate) fn notify_on_failure(deps: &[&NamedJob]) -> NamedJob { pub(crate) enum MessageType { Static(String), - Evaluated(String), + Evaluated { + script: String, + env: Vec<(String, String)>, + }, } fn notify_slack(message: MessageType) -> Vec> { match message { MessageType::Static(message) => vec![send_slack_message(message)], - MessageType::Evaluated(expression) => { - let (generate_step, generated_message) = generate_slack_message(expression); + MessageType::Evaluated { script, env } => { + let (generate_step, generated_message) = generate_slack_message(script, env); vec![ generate_step, @@ -385,26 +415,32 @@ fn notify_slack(message: MessageType) -> Vec> { } } -fn generate_slack_message(expression: String) -> (Step, StepOutput) { +fn generate_slack_message( + expression: String, + env: Vec<(String, String)>, +) -> (Step, StepOutput) { let script = formatdoc! {r#" MESSAGE=$({expression}) echo "message=$MESSAGE" >> "$GITHUB_OUTPUT" "# }; - let generate_step = named::bash(&script) + let mut generate_step = named::bash(&script) .id("generate-webhook-message") .add_env(("GH_TOKEN", Context::github().token())); + for (name, value) in env { + generate_step = generate_step.add_env((name, value)); + } + let output = StepOutput::new(&generate_step, "message"); (generate_step, output) } fn send_slack_message(message: String) -> Step { - let script = formatdoc! {r#" - curl -X POST -H 'Content-type: application/json'\ - --data '{{"text":"{message}"}}' "$SLACK_WEBHOOK" - "# - }; - named::bash(&script).add_env(("SLACK_WEBHOOK", vars::SLACK_WEBHOOK_WORKFLOW_FAILURES)) + named::bash( + r#"curl -X POST -H 'Content-type: application/json' --data "$(jq -n --arg text "$SLACK_MESSAGE" '{"text": $text}')" "$SLACK_WEBHOOK""# + ) + .add_env(("SLACK_WEBHOOK", vars::SLACK_WEBHOOK_WORKFLOW_FAILURES)) + .add_env(("SLACK_MESSAGE", message)) } diff --git a/tooling/xtask/src/tasks/workflows/run_tests.rs b/tooling/xtask/src/tasks/workflows/run_tests.rs index d617dda5af0ad51d0e86cfeeb69a035a53c07663..38ba1bd32945f9ba8ee1e08ebc994a1132fb07f2 100644 --- a/tooling/xtask/src/tasks/workflows/run_tests.rs +++ b/tooling/xtask/src/tasks/workflows/run_tests.rs @@ -6,7 +6,10 @@ use indexmap::IndexMap; use indoc::formatdoc; use crate::tasks::workflows::{ - steps::{CommonJobConditions, repository_owner_guard_expression, use_clang}, + steps::{ + CommonJobConditions, cache_rust_dependencies_namespace, repository_owner_guard_expression, + use_clang, + }, vars::{self, PathCondition}, }; @@ -116,7 +119,7 @@ fn orchestrate_impl(rules: &[&PathCondition], include_package_filter: bool) -> N git fetch origin "$GITHUB_BASE_REF" --depth=350 COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)" fi - CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" ${{ github.sha }})" + CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")" check_pattern() { local output_name="$1" @@ -240,15 +243,20 @@ pub fn tests_pass(jobs: &[NamedJob]) -> NamedJob { "#}); + let env_entries: Vec<_> = jobs + .iter() + .map(|job| { + let env_name = format!("RESULT_{}", job.name.to_uppercase()); + let env_value = format!("${{{{ needs.{}.result }}}}", job.name); + (env_name, env_value) + }) + .collect(); + script.push_str( &jobs .iter() - .map(|job| { - format!( - "check_result \"{}\" \"${{{{ needs.{}.result }}}}\"", - job.name, job.name - ) - }) + .zip(env_entries.iter()) + .map(|(job, (env_name, _))| format!("check_result \"{}\" \"${}\"", job.name, env_name)) .collect::>() .join("\n"), ); @@ -263,7 +271,13 @@ pub fn tests_pass(jobs: &[NamedJob]) -> NamedJob { .collect::>(), ) .cond(repository_owner_guard_expression(true)) - .add_step(named::bash(&script)); + .add_step( + env_entries + .into_iter() + .fold(named::bash(&script), |step, env_item| { + step.add_env(env_item) + }), + ); named::job(job) } @@ -646,9 +660,10 @@ pub(crate) fn check_scripts() -> NamedJob { } fn run_actionlint() -> Step { - named::bash(indoc::indoc! {r#" - ${{ steps.get_actionlint.outputs.executable }} -color - "#}) + named::bash(r#""$ACTIONLINT_BIN" -color"#).add_env(( + "ACTIONLINT_BIN", + "${{ steps.get_actionlint.outputs.executable }}", + )) } fn run_shellcheck() -> Step { @@ -673,6 +688,7 @@ pub(crate) fn check_scripts() -> NamedJob { .add_step(run_shellcheck()) .add_step(download_actionlint().id("get_actionlint")) .add_step(run_actionlint()) + .add_step(cache_rust_dependencies_namespace()) .add_step(check_xtask_workflows()), ) } diff --git a/tooling/xtask/src/tasks/workflows/steps.rs b/tooling/xtask/src/tasks/workflows/steps.rs index 9e54452424dba36d64a209c71b281e3b72eaafc8..4d17be81322277d0093de5d547bf4f0849e38dc3 100644 --- a/tooling/xtask/src/tasks/workflows/steps.rs +++ b/tooling/xtask/src/tasks/workflows/steps.rs @@ -503,9 +503,8 @@ pub mod named { } pub fn git_checkout(ref_name: &dyn std::fmt::Display) -> Step { - named::bash(&format!( - "git fetch origin {ref_name} && git checkout {ref_name}" - )) + named::bash(r#"git fetch origin "$REF_NAME" && git checkout "$REF_NAME""#) + .add_env(("REF_NAME", ref_name.to_string())) } pub fn authenticate_as_zippy() -> (Step, StepOutput) {