More tweaks to CI pipeline (#41941)

Conrad Irwin created

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

.github/workflows/cherry_pick.yml                 | 16 ++++++
.github/workflows/compare_perf.yml                | 20 +++++++
script/cherry-pick                                | 15 +++--
tooling/xtask/src/tasks/workflows/cherry_pick.rs  | 40 ++++++++++++++--
tooling/xtask/src/tasks/workflows/compare_perf.rs | 33 ++++++++-----
tooling/xtask/src/tasks/workflows/steps.rs        |  2 
tooling/xtask/src/tasks/workflows/vars.rs         | 38 ++++++++++++++--
7 files changed, 128 insertions(+), 36 deletions(-)

Detailed changes

.github/workflows/cherry_pick.yml 🔗

@@ -12,6 +12,10 @@ on:
         description: branch
         required: true
         type: string
+      channel:
+        description: channel
+        required: true
+        type: string
 jobs:
   run_cherry_pick:
     runs-on: namespace-profile-2x4-ubuntu-2404
@@ -20,6 +24,16 @@ jobs:
       uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
       with:
         clean: false
+    - id: get-app-token
+      name: cherry_pick::run_cherry_pick::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: cherry_pick::run_cherry_pick::cherry_pick
-      run: ./scripts/cherry-pick ${{ inputs.branch }} ${{ inputs.commit }}
+      run: ./script/cherry-pick ${{ inputs.branch }} ${{ inputs.commit }} ${{ inputs.channel }}
       shell: bash -euxo pipefail {0}
+      env:
+        GIT_COMMITTER_NAME: Zed Zippy
+        GIT_COMMITTER_EMAIL: hi@zed.dev
+        GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}

.github/workflows/compare_perf.yml 🔗

@@ -12,6 +12,10 @@ on:
         description: base
         required: true
         type: string
+      crate_name:
+        description: crate_name
+        type: string
+        default: ''
 jobs:
   run_perf:
     runs-on: namespace-profile-16x32-ubuntu-2204
@@ -38,13 +42,25 @@ jobs:
       run: git fetch origin ${{ inputs.base }} && git checkout ${{ inputs.base }}
       shell: bash -euxo pipefail {0}
     - name: compare_perf::run_perf::cargo_perf_test
-      run: cargo perf-test -p gpui -- --json=${{ inputs.base }}
+      run: |2-
+
+                    if [ -n "${{ inputs.crate_name }}" ]; then
+                        cargo perf-test -p ${{ inputs.crate_name }} -- --json=${{ inputs.base }};
+                    else
+                        cargo perf-test -p vim -- --json=${{ inputs.base }};
+                    fi
       shell: bash -euxo pipefail {0}
     - name: steps::git_checkout
       run: git fetch origin ${{ inputs.head }} && git checkout ${{ inputs.head }}
       shell: bash -euxo pipefail {0}
     - name: compare_perf::run_perf::cargo_perf_test
-      run: cargo perf-test -p gpui -- --json=${{ inputs.head }}
+      run: |2-
+
+                    if [ -n "${{ inputs.crate_name }}" ]; then
+                        cargo perf-test -p ${{ inputs.crate_name }} -- --json=${{ inputs.head }};
+                    else
+                        cargo perf-test -p vim -- --json=${{ inputs.head }};
+                    fi
       shell: bash -euxo pipefail {0}
     - name: compare_perf::run_perf::compare_runs
       run: cargo perf-compare --save=results.md ${{ inputs.base }} ${{ inputs.head }}

script/cherry-pick 🔗

@@ -1,33 +1,34 @@
 # #!/bin/bash
 set -euxo pipefail
 
-if [ "$#" -ne 2 ]; then
-    echo "Usage: $0 <branch-name> <commit-sha>"
+if [ "$#" -ne 3 ]; then
+    echo "Usage: $0 <branch-name> <commit-sha> <channel>"
     exit 1
 fi
 
 BRANCH_NAME="$1"
 COMMIT_SHA="$2"
+CHANNEL="$3"
 
 SHORT_SHA="${COMMIT_SHA:0:8}"
 NEW_BRANCH="cherry-pick-${BRANCH_NAME}-${SHORT_SHA}"
 git fetch origin
 git checkout "$BRANCH_NAME"
-git checkout -b "$NEW_BRANCH"
+git checkout -B "$NEW_BRANCH"
 
 git cherry-pick "$COMMIT_SHA"
 
-git push origin "$NEW_BRANCH"
+git push origin -f "$NEW_BRANCH"
 COMMIT_TITLE=$(git log -1 --pretty=format:"%s" "$COMMIT_SHA")
 COMMIT_BODY=$(git log -1 --pretty=format:"%b" "$COMMIT_SHA")
 
 # Check if commit title ends with (#number)
 if [[ "$COMMIT_TITLE" =~ \(#([0-9]+)\)$ ]]; then
     PR_NUMBER="${BASH_REMATCH[1]}"
-    PR_BODY="Cherry-pick of #${PR_NUMBER}"$'\n'$'\n'"----"$'\n'"${COMMIT_BODY}"
+    PR_BODY="Cherry-pick of #${PR_NUMBER} to ${CHANNEL}"$'\n'$'\n'"----"$'\n'"${COMMIT_BODY}"
 else
-    PR_BODY="Cherry-pick of ${COMMIT_SHA}"$'\n'$'\n'"----"$'\n'"${COMMIT_BODY}"
+    PR_BODY="Cherry-pick of ${COMMIT_SHA} to ${CHANNEL}"$'\n'$'\n'"----"$'\n'"${COMMIT_BODY}"
 fi
 
 # Create a pull request
-gh pr create --base "$BRANCH_NAME" --head "$NEW_BRANCH" --title "$COMMIT_TITLE (cherry-pick)" --body "$PR_BODY"
+gh pr create --base "$BRANCH_NAME" --head "$NEW_BRANCH" --title "$COMMIT_TITLE (cherry-pick to $CHANNEL)" --body "$PR_BODY"

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

@@ -3,31 +3,57 @@ use gh_workflow::*;
 use crate::tasks::workflows::{
     runners,
     steps::{self, NamedJob, named},
-    vars::Input,
+    vars::{self, Input, StepOutput},
 };
 
 pub fn cherry_pick() -> Workflow {
     let branch = Input::string("branch", None);
     let commit = Input::string("commit", None);
-    let cherry_pick = run_cherry_pick(&branch, &commit);
+    let channel = Input::string("channel", None);
+    let cherry_pick = run_cherry_pick(&branch, &commit, &channel);
     named::workflow()
         .on(Event::default().workflow_dispatch(
             WorkflowDispatch::default()
                 .add_input(commit.name, commit.input())
-                .add_input(branch.name, branch.input()),
+                .add_input(branch.name, branch.input())
+                .add_input(channel.name, channel.input()),
         ))
         .add_job(cherry_pick.name, cherry_pick.job)
 }
 
-fn run_cherry_pick(branch: &Input, commit: &Input) -> NamedJob {
-    fn cherry_pick(branch: &str, commit: &str) -> Step<Run> {
-        named::bash(&format!("./scripts/cherry-pick {branch} {commit}"))
+fn run_cherry_pick(branch: &Input, commit: &Input, channel: &Input) -> NamedJob {
+    fn authenticate_as_zippy() -> (Step<Use>, StepOutput) {
+        let step = named::uses(
+            "actions",
+            "create-github-app-token",
+            "bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1",
+        ) // v2
+        .add_with(("app-id", vars::ZED_ZIPPY_APP_ID))
+        .add_with(("private-key", vars::ZED_ZIPPY_APP_PRIVATE_KEY))
+        .id("get-app-token");
+        let output = StepOutput::new(&step, "token");
+        (step, output)
     }
 
+    fn cherry_pick(
+        branch: &Input,
+        commit: &Input,
+        channel: &Input,
+        token: &StepOutput,
+    ) -> Step<Run> {
+        named::bash(&format!("./script/cherry-pick {branch} {commit} {channel}"))
+            .add_env(("GIT_COMMITTER_NAME", "Zed Zippy"))
+            .add_env(("GIT_COMMITTER_EMAIL", "hi@zed.dev"))
+            .add_env(("GITHUB_TOKEN", token))
+    }
+
+    let (authenticate, token) = authenticate_as_zippy();
+
     named::job(
         Job::default()
             .runs_on(runners::LINUX_SMALL)
             .add_step(steps::checkout_repo())
-            .add_step(cherry_pick(&branch.var(), &commit.var())),
+            .add_step(authenticate)
+            .add_step(cherry_pick(branch, commit, channel, &token)),
     )
 }

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

@@ -11,28 +11,35 @@ use crate::tasks::workflows::{
 pub fn compare_perf() -> Workflow {
     let head = Input::string("head", None);
     let base = Input::string("base", None);
-    let run_perf = run_perf(&base, &head);
+    let crate_name = Input::string("crate_name", Some("".to_owned()));
+    let run_perf = run_perf(&base, &head, &crate_name);
     named::workflow()
         .on(Event::default().workflow_dispatch(
             WorkflowDispatch::default()
                 .add_input(head.name, head.input())
-                .add_input(base.name, base.input()),
+                .add_input(base.name, base.input())
+                .add_input(crate_name.name, crate_name.input()),
         ))
         .add_job(run_perf.name, run_perf.job)
 }
 
-pub fn run_perf(base: &Input, head: &Input) -> NamedJob {
-    fn cargo_perf_test(ref_name: String) -> Step<Run> {
-        // TODO: vim not gpui, and ideally allow args
-        named::bash(&format!("cargo perf-test -p gpui -- --json={ref_name}"))
+pub fn run_perf(base: &Input, head: &Input, crate_name: &Input) -> NamedJob {
+    fn cargo_perf_test(ref_name: &Input, crate_name: &Input) -> Step<Run> {
+        named::bash(&format!(
+            "
+            if [ -n \"{crate_name}\" ]; then
+                cargo perf-test -p {crate_name} -- --json={ref_name};
+            else
+                cargo perf-test -p vim -- --json={ref_name};
+            fi"
+        ))
     }
 
     fn install_hyperfine() -> Step<Run> {
         named::bash("cargo install hyperfine")
     }
 
-    fn compare_runs(head: String, base: String) -> Step<Run> {
-        // TODO: this should really be swapped...
+    fn compare_runs(head: &Input, base: &Input) -> Step<Run> {
         named::bash(&format!(
             "cargo perf-compare --save=results.md {base} {head}"
         ))
@@ -45,11 +52,11 @@ pub fn run_perf(base: &Input, head: &Input) -> NamedJob {
             .add_step(steps::setup_cargo_config(runners::Platform::Linux))
             .map(steps::install_linux_dependencies)
             .add_step(install_hyperfine())
-            .add_step(steps::git_checkout(&base.var()))
-            .add_step(cargo_perf_test(base.var()))
-            .add_step(steps::git_checkout(&head.var()))
-            .add_step(cargo_perf_test(head.var()))
-            .add_step(compare_runs(head.var(), base.var()))
+            .add_step(steps::git_checkout(base))
+            .add_step(cargo_perf_test(base, crate_name))
+            .add_step(steps::git_checkout(head))
+            .add_step(cargo_perf_test(head, crate_name))
+            .add_step(compare_runs(head, base))
             .add_step(upload_artifact("results.md"))
             .add_step(steps::cleanup_cargo_config(runners::Platform::Linux)),
     )

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

@@ -299,7 +299,7 @@ pub(crate) mod named {
     }
 }
 
-pub fn git_checkout(ref_name: &str) -> Step<Run> {
+pub fn git_checkout(ref_name: &dyn std::fmt::Display) -> Step<Run> {
     named::bash(&format!(
         "git fetch origin {ref_name} && git checkout {ref_name}"
     ))

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

@@ -1,6 +1,6 @@
 use std::cell::RefCell;
 
-use gh_workflow::{Concurrency, Env, Expression, WorkflowDispatchInput};
+use gh_workflow::{Concurrency, Env, Expression, Step, WorkflowDispatchInput};
 
 use crate::tasks::workflows::{runners::Platform, steps::NamedJob};
 
@@ -34,6 +34,8 @@ secret!(ZED_CLIENT_CHECKSUM_SEED);
 secret!(ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON);
 secret!(ZED_SENTRY_MINIDUMP_ENDPOINT);
 secret!(SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN);
+secret!(ZED_ZIPPY_APP_ID);
+secret!(ZED_ZIPPY_APP_PRIVATE_KEY);
 
 // todo(ci) make these secrets too...
 var!(AZURE_SIGNING_ACCOUNT_NAME);
@@ -116,6 +118,30 @@ impl PathCondition {
     }
 }
 
+pub(crate) struct StepOutput {
+    name: &'static str,
+    step_id: String,
+}
+
+impl StepOutput {
+    pub fn new<T>(step: &Step<T>, name: &'static str) -> Self {
+        Self {
+            name,
+            step_id: step
+                .value
+                .id
+                .clone()
+                .expect("Steps that produce outputs must have an ID"),
+        }
+    }
+}
+
+impl std::fmt::Display for StepOutput {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "${{{{ steps.{}.outputs.{} }}}}", self.step_id, self.name)
+    }
+}
+
 pub(crate) struct Input {
     pub input_type: &'static str,
     pub name: &'static str,
@@ -131,10 +157,6 @@ impl Input {
         }
     }
 
-    pub fn var(&self) -> String {
-        format!("${{{{ inputs.{} }}}}", self.name)
-    }
-
     pub fn input(&self) -> WorkflowDispatchInput {
         WorkflowDispatchInput {
             description: self.name.to_owned(),
@@ -145,6 +167,12 @@ impl Input {
     }
 }
 
+impl std::fmt::Display for Input {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "${{{{ inputs.{} }}}}", self.name)
+    }
+}
+
 pub mod assets {
     // NOTE: these asset names also exist in the zed.dev codebase.
     pub const MAC_AARCH64: &str = "Zed-aarch64.dmg";