More permissions for autofix (#45170)

Conrad Irwin created

Release Notes:

- N/A

Change summary

.github/workflows/release.yml                  | 17 ++---
.github/workflows/release_nightly.yml          |  3 
.github/workflows/run_tests.yml                | 41 ++++++++-----
tooling/xtask/src/tasks/workflows/run_tests.rs | 58 +++++++++++++++++--
tooling/xtask/src/tasks/workflows/steps.rs     | 16 +----
5 files changed, 92 insertions(+), 43 deletions(-)

Detailed changes

.github/workflows/release.yml 🔗

@@ -26,7 +26,8 @@ jobs:
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
         node-version: '20'
-    - name: steps::clippy
+    - id: clippy
+      name: steps::clippy
       run: ./script/clippy
       shell: bash -euxo pipefail {0}
     - name: steps::clear_target_dir_if_large
@@ -71,15 +72,10 @@ jobs:
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
         node-version: '20'
-    - name: steps::clippy
+    - id: clippy
+      name: steps::clippy
       run: ./script/clippy
       shell: bash -euxo pipefail {0}
-    - name: steps::trigger_autofix
-      if: failure() && github.event_name == 'pull_request' && github.actor != 'zed-zippy[bot]'
-      run: gh workflow run autofix_pr.yml -f pr_number=${{ github.event.pull_request.number }} -f run_clippy=true
-      shell: bash -euxo pipefail {0}
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
     - name: steps::cargo_install_nextest
       uses: taiki-e/install-action@nextest
     - name: steps::clear_target_dir_if_large
@@ -93,6 +89,8 @@ jobs:
       run: |
         rm -rf ./../.cargo
       shell: bash -euxo pipefail {0}
+    outputs:
+      clippy_failed: ${{ steps.clippy.outcome == 'failure' }}
     timeout-minutes: 60
   run_tests_windows:
     if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
@@ -111,7 +109,8 @@ jobs:
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
         node-version: '20'
-    - name: steps::clippy
+    - id: clippy
+      name: steps::clippy
       run: ./script/clippy.ps1
       shell: pwsh
     - name: steps::clear_target_dir_if_large

.github/workflows/release_nightly.yml 🔗

@@ -44,7 +44,8 @@ jobs:
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
         node-version: '20'
-    - name: steps::clippy
+    - id: clippy
+      name: steps::clippy
       run: ./script/clippy.ps1
       shell: pwsh
     - name: steps::clear_target_dir_if_large

.github/workflows/run_tests.yml 🔗

@@ -80,12 +80,6 @@ jobs:
     - name: steps::cargo_fmt
       run: cargo fmt --all -- --check
       shell: bash -euxo pipefail {0}
-    - name: steps::trigger_autofix
-      if: failure() && github.event_name == 'pull_request' && github.actor != 'zed-zippy[bot]'
-      run: gh workflow run autofix_pr.yml -f pr_number=${{ github.event.pull_request.number }} -f run_clippy=false
-      shell: bash -euxo pipefail {0}
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
     - name: ./script/check-todos
       run: ./script/check-todos
       shell: bash -euxo pipefail {0}
@@ -116,7 +110,8 @@ jobs:
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
         node-version: '20'
-    - name: steps::clippy
+    - id: clippy
+      name: steps::clippy
       run: ./script/clippy.ps1
       shell: pwsh
     - name: steps::clear_target_dir_if_large
@@ -163,15 +158,10 @@ jobs:
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
         node-version: '20'
-    - name: steps::clippy
+    - id: clippy
+      name: steps::clippy
       run: ./script/clippy
       shell: bash -euxo pipefail {0}
-    - name: steps::trigger_autofix
-      if: failure() && github.event_name == 'pull_request' && github.actor != 'zed-zippy[bot]'
-      run: gh workflow run autofix_pr.yml -f pr_number=${{ github.event.pull_request.number }} -f run_clippy=true
-      shell: bash -euxo pipefail {0}
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
     - name: steps::cargo_install_nextest
       uses: taiki-e/install-action@nextest
     - name: steps::clear_target_dir_if_large
@@ -185,6 +175,8 @@ jobs:
       run: |
         rm -rf ./../.cargo
       shell: bash -euxo pipefail {0}
+    outputs:
+      clippy_failed: ${{ steps.clippy.outcome == 'failure' }}
     timeout-minutes: 60
   run_tests_mac:
     needs:
@@ -205,7 +197,8 @@ jobs:
       uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
       with:
         node-version: '20'
-    - name: steps::clippy
+    - id: clippy
+      name: steps::clippy
       run: ./script/clippy
       shell: bash -euxo pipefail {0}
     - name: steps::clear_target_dir_if_large
@@ -585,6 +578,24 @@ jobs:
 
         exit $EXIT_CODE
       shell: bash -euxo pipefail {0}
+  call_autofix:
+    needs:
+    - check_style
+    - run_tests_linux
+    if: (needs.check_style.result == 'failure' || needs.run_tests_linux.outputs.clippy_failed == 'true') && github.event_name == 'pull_request' && github.actor != 'zed-zippy[bot]'
+    runs-on: namespace-profile-2x4-ubuntu-2404
+    steps:
+    - id: get-app-token
+      name: steps::authenticate_as_zippy
+      uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
+      with:
+        app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
+        private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
+    - name: run_tests::call_autofix::dispatch_autofix
+      run: gh workflow run autofix_pr.yml -f pr_number=${{ github.event.pull_request.number }} -f run_clippy=${{ needs.run_tests_linux.outputs.clippy_failed == 'true' }}
+      shell: bash -euxo pipefail {0}
+      env:
+        GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
 concurrency:
   group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
   cancel-in-progress: true

tooling/xtask/src/tasks/workflows/run_tests.rs 🔗

@@ -45,11 +45,15 @@ pub(crate) fn run_tests() -> Workflow {
         &should_run_tests,
     ]);
 
+    let check_style = check_style();
+    let run_tests_linux = run_platform_tests(Platform::Linux);
+    let call_autofix = call_autofix(&check_style, &run_tests_linux);
+
     let mut jobs = vec![
         orchestrate,
-        check_style(),
+        check_style,
         should_run_tests.guard(run_platform_tests(Platform::Windows)),
-        should_run_tests.guard(run_platform_tests(Platform::Linux)),
+        should_run_tests.guard(run_tests_linux),
         should_run_tests.guard(run_platform_tests(Platform::Mac)),
         should_run_tests.guard(doctests()),
         should_run_tests.guard(check_workspace_binaries()),
@@ -106,6 +110,7 @@ pub(crate) fn run_tests() -> Workflow {
             workflow
         })
         .add_job(tests_pass.name, tests_pass.job)
+        .add_job(call_autofix.name, call_autofix.job)
 }
 
 // Generates a bash script that checks changed files against regex patterns
@@ -238,13 +243,44 @@ fn check_style() -> NamedJob {
             .add_step(steps::setup_pnpm())
             .add_step(steps::script("./script/prettier"))
             .add_step(steps::cargo_fmt())
-            .add_step(steps::trigger_autofix(false))
             .add_step(steps::script("./script/check-todos"))
             .add_step(steps::script("./script/check-keymaps"))
             .add_step(check_for_typos()),
     )
 }
 
+fn call_autofix(check_style: &NamedJob, run_tests_linux: &NamedJob) -> NamedJob {
+    fn dispatch_autofix(run_tests_linux_name: &str) -> Step<Run> {
+        let clippy_failed_expr = format!(
+            "needs.{}.outputs.{} == 'true'",
+            run_tests_linux_name, CLIPPY_FAILED_OUTPUT
+        );
+        named::bash(format!(
+            "gh workflow run autofix_pr.yml -f pr_number=${{{{ github.event.pull_request.number }}}} -f run_clippy=${{{{ {} }}}}",
+            clippy_failed_expr
+        ))
+        .add_env(("GITHUB_TOKEN", "${{ steps.get-app-token.outputs.token }}"))
+    }
+
+    let clippy_failed_expr = format!(
+        "needs.{}.outputs.{} == 'true'",
+        run_tests_linux.name, CLIPPY_FAILED_OUTPUT
+    );
+    let (authenticate, _token) = steps::authenticate_as_zippy();
+
+    let job = Job::default()
+        .runs_on(runners::LINUX_SMALL)
+        .cond(Expression::new(format!(
+            "(needs.{}.result == 'failure' || {}) && github.event_name == 'pull_request' && github.actor != 'zed-zippy[bot]'",
+            check_style.name, clippy_failed_expr
+        )))
+        .needs(vec![check_style.name.clone(), run_tests_linux.name.clone()])
+        .add_step(authenticate)
+        .add_step(dispatch_autofix(&run_tests_linux.name));
+
+    named::job(job)
+}
+
 fn check_dependencies() -> NamedJob {
     fn install_cargo_machete() -> Step<Use> {
         named::uses(
@@ -305,6 +341,8 @@ fn check_workspace_binaries() -> NamedJob {
     )
 }
 
+pub const CLIPPY_FAILED_OUTPUT: &str = "clippy_failed";
+
 pub(crate) fn run_platform_tests(platform: Platform) -> NamedJob {
     let runner = match platform {
         Platform::Windows => runners::WINDOWS_DEFAULT,
@@ -327,12 +365,20 @@ pub(crate) fn run_platform_tests(platform: Platform) -> NamedJob {
             .add_step(steps::setup_node())
             .add_step(steps::clippy(platform))
             .when(platform == Platform::Linux, |job| {
-                job.add_step(steps::trigger_autofix(true))
-                    .add_step(steps::cargo_install_nextest())
+                job.add_step(steps::cargo_install_nextest())
             })
             .add_step(steps::clear_target_dir_if_large(platform))
             .add_step(steps::cargo_nextest(platform))
-            .add_step(steps::cleanup_cargo_config(platform)),
+            .add_step(steps::cleanup_cargo_config(platform))
+            .when(platform == Platform::Linux, |job| {
+                job.outputs([(
+                    CLIPPY_FAILED_OUTPUT.to_owned(),
+                    format!(
+                        "${{{{ steps.{}.outcome == 'failure' }}}}",
+                        steps::CLIPPY_STEP_ID
+                    ),
+                )])
+            }),
     }
 }
 

tooling/xtask/src/tasks/workflows/steps.rs 🔗

@@ -101,10 +101,12 @@ pub fn clear_target_dir_if_large(platform: Platform) -> Step<Run> {
     }
 }
 
+pub const CLIPPY_STEP_ID: &str = "clippy";
+
 pub fn clippy(platform: Platform) -> Step<Run> {
     match platform {
-        Platform::Windows => named::pwsh("./script/clippy.ps1"),
-        _ => named::bash("./script/clippy"),
+        Platform::Windows => named::pwsh("./script/clippy.ps1").id(CLIPPY_STEP_ID),
+        _ => named::bash("./script/clippy").id(CLIPPY_STEP_ID),
     }
 }
 
@@ -345,16 +347,6 @@ pub fn git_checkout(ref_name: &dyn std::fmt::Display) -> Step<Run> {
     ))
 }
 
-pub fn trigger_autofix(run_clippy: bool) -> Step<Run> {
-    named::bash(format!(
-        "gh workflow run autofix_pr.yml -f pr_number=${{{{ github.event.pull_request.number }}}} -f run_clippy={run_clippy}"
-    ))
-    .if_condition(Expression::new(
-        "failure() && github.event_name == 'pull_request' && github.actor != 'zed-zippy[bot]'",
-    ))
-    .add_env(("GITHUB_TOKEN", vars::GITHUB_TOKEN))
-}
-
 pub fn authenticate_as_zippy() -> (Step<Use>, StepOutput) {
     let step = named::uses(
         "actions",