diff --git a/.github/workflows/extension_bump.yml b/.github/workflows/extension_bump.yml index 779116ec5b2ea4c766212ea2b61993eb82693992..4bdd340bc6893d2ebbb64958403a7e8706f7c0ac 100644 --- a/.github/workflows/extension_bump.yml +++ b/.github/workflows/extension_bump.yml @@ -13,6 +13,10 @@ on: description: bump-type type: string default: patch + force-bump: + description: force-bump + required: true + type: boolean secrets: app-id: description: The app ID used to create the PR @@ -56,17 +60,24 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: clean: false - fetch-depth: 10 + fetch-depth: 0 - id: compare-versions-check name: extension_bump::compare_versions run: | CURRENT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)" + PR_PARENT_SHA="${{ github.event.pull_request.head.sha }}" - git checkout "$(git log -1 --format=%H)"~1 + if [[ -n "$PR_PARENT_SHA" ]]; then + git checkout "$PR_PARENT_SHA" + elif BRANCH_PARENT_SHA="$(git merge-base origin/main origin/zed-zippy-autobump)"; then + git checkout "$BRANCH_PARENT_SHA" + else + git checkout "$(git log -1 --format=%H)"~1 + fi - PREV_COMMIT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)" + PARENT_COMMIT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)" - [[ "$CURRENT_VERSION" == "$PREV_COMMIT_VERSION" ]] && \ + [[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \ echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \ echo "needs_bump=false" >> "$GITHUB_OUTPUT" @@ -80,7 +91,9 @@ jobs: needs: - check_extension - check_bump_needed - if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && needs.check_bump_needed.outputs.needs_bump == 'true' + if: |- + (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && + (inputs.force-bump == 'true' || needs.check_bump_needed.outputs.needs_bump == 'true') runs-on: namespace-profile-8x16-ubuntu-2204 steps: - id: generate-token @@ -99,7 +112,7 @@ jobs: - id: bump-version name: extension_bump::bump_version run: | - OLD_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)" + OLD_VERSION="${{ needs.check_bump_needed.outputs.current_version }}" cat < .bumpversion.cfg [bumpversion] @@ -117,7 +130,6 @@ jobs: rm .bumpversion.cfg - echo "old_version=${OLD_VERSION}" >> "$GITHUB_OUTPUT" echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT" shell: bash -euxo pipefail {0} - name: extension_bump::create_pull_request @@ -126,7 +138,7 @@ jobs: title: Bump version to ${{ steps.bump-version.outputs.new_version }} body: This PR bumps the version of this extension to v${{ steps.bump-version.outputs.new_version }} commit-message: Bump version to v${{ steps.bump-version.outputs.new_version }} - branch: bump-from-${{ steps.bump-version.outputs.old_version }} + branch: zed-zippy-autobump committer: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com> base: main delete-branch: true @@ -137,7 +149,7 @@ jobs: needs: - check_extension - check_bump_needed - if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && needs.check_bump_needed.outputs.needs_bump == 'false' + if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.check_bump_needed.outputs.needs_bump == 'false' runs-on: namespace-profile-8x16-ubuntu-2204 steps: - id: generate-token diff --git a/.github/workflows/extension_release.yml b/.github/workflows/extension_release.yml new file mode 100644 index 0000000000000000000000000000000000000000..263e6465a749e401747e32c279b972791c54f960 --- /dev/null +++ b/.github/workflows/extension_release.yml @@ -0,0 +1,41 @@ +# Generated from xtask::workflows::extension_release +# Rebuild with `cargo xtask workflows`. +name: extension_release +on: + workflow_call: + secrets: + app-id: + description: The app ID used to create the PR + required: true + app-secret: + description: The app secret for the corresponding app ID + required: true +jobs: + create_release: + if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') + runs-on: namespace-profile-8x16-ubuntu-2204 + steps: + - id: generate-token + name: extension_bump::generate_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.app-id }} + private-key: ${{ secrets.app-secret }} + - name: steps::checkout_repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + clean: false + - id: get-extension-id + name: extension_release::get_extension_id + run: | + EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)" + + echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT" + shell: bash -euxo pipefail {0} + - name: extension_release::release_action + uses: huacnlee/zed-extension-action@v2 + with: + extension-name: ${{ steps.get-extension-id.outputs.extension_id }} + push-to: zed-industries/extensions + env: + COMMITTER_TOKEN: ${{ steps.generate-token.outputs.token }} diff --git a/.github/workflows/run_bundling.yml b/.github/workflows/run_bundling.yml index ddedd38ebedb647f07e162286365f4e6b95f45a2..f56e56ac7f139926085f33d6e97d3dea6e03a4bb 100644 --- a/.github/workflows/run_bundling.yml +++ b/.github/workflows/run_bundling.yml @@ -13,7 +13,7 @@ jobs: bundle_linux_aarch64: if: |- (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || - (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4 env: CARGO_INCREMENTAL: 0 @@ -56,7 +56,7 @@ jobs: bundle_linux_x86_64: if: |- (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || - (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) runs-on: namespace-profile-32x64-ubuntu-2004 env: CARGO_INCREMENTAL: 0 @@ -99,7 +99,7 @@ jobs: bundle_mac_aarch64: if: |- (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || - (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) runs-on: self-mini-macos env: CARGO_INCREMENTAL: 0 @@ -145,7 +145,7 @@ jobs: bundle_mac_x86_64: if: |- (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || - (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) runs-on: self-mini-macos env: CARGO_INCREMENTAL: 0 @@ -191,7 +191,7 @@ jobs: bundle_windows_aarch64: if: |- (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || - (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) runs-on: self-32vcpu-windows-2022 env: CARGO_INCREMENTAL: 0 @@ -229,7 +229,7 @@ jobs: bundle_windows_x86_64: if: |- (github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || - (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling')) runs-on: self-32vcpu-windows-2022 env: CARGO_INCREMENTAL: 0 diff --git a/Cargo.lock b/Cargo.lock index e398d20beaa0a8f7f381b4389bc5d17f310b12f0..1d891e7a066d0eb6ec3c79e65f291e0bdf93961f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6969,7 +6969,7 @@ dependencies = [ [[package]] name = "gh-workflow" version = "0.8.0" -source = "git+https://github.com/zed-industries/gh-workflow?rev=3eaa84abca0778eb54272f45a312cb24f9a0b435#3eaa84abca0778eb54272f45a312cb24f9a0b435" +source = "git+https://github.com/zed-industries/gh-workflow?rev=e5f883040530b4df36437f140084ee5cc7c1c9be#e5f883040530b4df36437f140084ee5cc7c1c9be" dependencies = [ "async-trait", "derive_more 2.0.1", @@ -6986,7 +6986,7 @@ dependencies = [ [[package]] name = "gh-workflow-macros" version = "0.8.0" -source = "git+https://github.com/zed-industries/gh-workflow?rev=3eaa84abca0778eb54272f45a312cb24f9a0b435#3eaa84abca0778eb54272f45a312cb24f9a0b435" +source = "git+https://github.com/zed-industries/gh-workflow?rev=e5f883040530b4df36437f140084ee5cc7c1c9be#e5f883040530b4df36437f140084ee5cc7c1c9be" dependencies = [ "heck 0.5.0", "quote", diff --git a/Cargo.toml b/Cargo.toml index 05ea7bceb818e33f8b550269f00c305ce6d7be0b..8fe4dbcaadc8413ee915ee6c2b12065ef98e8430 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -508,7 +508,7 @@ fork = "0.4.0" futures = "0.3" futures-batch = "0.6.1" futures-lite = "1.13" -gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "3eaa84abca0778eb54272f45a312cb24f9a0b435" } +gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "e5f883040530b4df36437f140084ee5cc7c1c9be" } git2 = { version = "0.20.1", default-features = false } globset = "0.4" handlebars = "4.3" diff --git a/extensions/workflows/bump_version.yml b/extensions/workflows/bump_version.yml new file mode 100644 index 0000000000000000000000000000000000000000..a272782a73c69a04382a5818f0c63314abaffe06 --- /dev/null +++ b/extensions/workflows/bump_version.yml @@ -0,0 +1,48 @@ +# Generated from xtask::workflows:: within the Zed repository.extensions::bump_version +# Rebuild with `cargo xtask workflows`. +name: extensions::bump_version +on: + pull_request: + types: + - labeled + push: + branches: + - main +jobs: + determine_bump_type: + runs-on: namespace-profile-16x32-ubuntu-2204 + steps: + - id: get-bump-type + name: extensions::bump_version::get_bump_type + run: | + if [ "$HAS_MAJOR_LABEL" = "true" ]; then + bump_type="major" + elif [ "$HAS_MINOR_LABEL" = "true" ]; then + bump_type="minor" + else + bump_type="patch" + fi + echo "bump_type=$bump_type" >> $GITHUB_OUTPUT + shell: bash -euxo pipefail {0} + env: + HAS_MAJOR_LABEL: |- + ${{ (github.event.action == 'labeled' && github.event.label.name == 'major') || + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'major')) }} + HAS_MINOR_LABEL: |- + ${{ (github.event.action == 'labeled' && github.event.label.name == 'minor') || + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'minor')) }} + outputs: + bump_type: ${{ steps.get-bump-type.outputs.bump_type }} + call_bump_version: + needs: + - determine_bump_type + if: |- + (github.event.action == 'labeled' && needs.determine_bump_type.outputs.bump_type != 'patch') || + github.event_name == 'push' + uses: zed-industries/zed/.github/workflows/extension_bump.yml@main + secrets: + app-id: ${{ secrets.ZED_ZIPPY_APP_ID }} + app-secret: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }} + with: + bump-type: ${{ needs.determine_bump_type.outputs.bump_type }} + force-bump: true diff --git a/extensions/workflows/release_version.yml b/extensions/workflows/release_version.yml new file mode 100644 index 0000000000000000000000000000000000000000..8975ad80db5d8b6f558017a895f3b85691465a21 --- /dev/null +++ b/extensions/workflows/release_version.yml @@ -0,0 +1,13 @@ +# Generated from xtask::workflows:: within the Zed repository.extensions::release_version +# Rebuild with `cargo xtask workflows`. +name: extensions::release_version +on: + push: + tags: + - v** +jobs: + call_release_version: + uses: zed-industries/zed/.github/workflows/extension_release.yml@main + secrets: + app-id: ${{ secrets.ZED_ZIPPY_APP_ID }} + app-secret: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }} diff --git a/extensions/workflows/run_tests.yml b/extensions/workflows/run_tests.yml new file mode 100644 index 0000000000000000000000000000000000000000..01834bb86a834ffa973b42d5ff5f17991aa02229 --- /dev/null +++ b/extensions/workflows/run_tests.yml @@ -0,0 +1,13 @@ +# Generated from xtask::workflows:: within the Zed repository.extensions::run_tests +# Rebuild with `cargo xtask workflows`. +name: extensions::run_tests +on: + pull_request: + branches: + - '**' +jobs: + call_extension_tests: + uses: zed-industries/zed/.github/workflows/extension_tests.yml@main +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} + cancel-in-progress: true diff --git a/tooling/xtask/src/tasks/workflows.rs b/tooling/xtask/src/tasks/workflows.rs index 334bc71e46fc5d7d99a9fab238dc60b874a1093d..82fc293b9a7858438fce4f1f78dd6407870dba75 100644 --- a/tooling/xtask/src/tasks/workflows.rs +++ b/tooling/xtask/src/tasks/workflows.rs @@ -1,14 +1,17 @@ use anyhow::{Context, Result}; use clap::Parser; +use gh_workflow::Workflow; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; mod after_release; mod cherry_pick; mod compare_perf; mod danger; mod extension_bump; +mod extension_release; mod extension_tests; +mod extensions; mod nix_build; mod release_nightly; mod run_bundling; @@ -23,44 +26,112 @@ mod vars; #[derive(Parser)] pub struct GenerateWorkflowArgs {} +struct WorkflowFile { + source: fn() -> Workflow, + r#type: WorkflowType, +} + +impl WorkflowFile { + fn zed(f: fn() -> Workflow) -> WorkflowFile { + WorkflowFile { + source: f, + r#type: WorkflowType::Zed, + } + } + fn extension(f: fn() -> Workflow) -> WorkflowFile { + WorkflowFile { + source: f, + r#type: WorkflowType::Extensions, + } + } + + fn generate_file(&self) -> Result<()> { + let workflow = (self.source)(); + let workflow_folder = self.r#type.folder_path(); + let workflow_name = workflow + .name + .as_ref() + .expect("Workflow must have a name at this point"); + let filename = format!( + "{}.yml", + workflow_name.rsplit("::").next().unwrap_or(workflow_name) + ); + + let workflow_path = workflow_folder.join(filename); + + let content = workflow + .to_string() + .map_err(|e| anyhow::anyhow!("{:?}: {:?}", workflow_path, e))?; + + let disclaimer = self.r#type.disclaimer(workflow_name); + + let content = [disclaimer, content].join("\n"); + fs::write(&workflow_path, content).map_err(Into::into) + } +} + +enum WorkflowType { + Zed, + Extensions, +} + +impl WorkflowType { + fn disclaimer(&self, workflow_name: &str) -> String { + format!( + concat!( + "# Generated from xtask::workflows::{}{}\n", + "# Rebuild with `cargo xtask workflows`.", + ), + matches!(self, WorkflowType::Extensions) + .then_some(" within the Zed repository.") + .unwrap_or_default(), + workflow_name + ) + } + + fn folder_path(&self) -> PathBuf { + match self { + WorkflowType::Zed => PathBuf::from(".github/workflows"), + WorkflowType::Extensions => PathBuf::from("extensions/workflows"), + } + } +} + pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> { if !Path::new("crates/zed/").is_dir() { anyhow::bail!("xtask workflows must be ran from the project root"); } - let dir = Path::new(".github/workflows"); - - let workflows = vec![ - ("danger.yml", danger::danger()), - ("run_bundling.yml", run_bundling::run_bundling()), - ("release_nightly.yml", release_nightly::release_nightly()), - ("run_tests.yml", run_tests::run_tests()), - ("release.yml", release::release()), - ("cherry_pick.yml", cherry_pick::cherry_pick()), - ("compare_perf.yml", compare_perf::compare_perf()), - ("run_unit_evals.yml", run_agent_evals::run_unit_evals()), - ( - "run_cron_unit_evals.yml", - run_agent_evals::run_cron_unit_evals(), - ), - ("run_agent_evals.yml", run_agent_evals::run_agent_evals()), - ("after_release.yml", after_release::after_release()), - ("extension_tests.yml", extension_tests::extension_tests()), - ("extension_bump.yml", extension_bump::extension_bump()), + let workflow_dir = Path::new(".github/workflows"); + let extension_workflow_dir = Path::new("extensions/workflows"); + + let workflows = [ + WorkflowFile::zed(danger::danger), + WorkflowFile::zed(run_bundling::run_bundling), + WorkflowFile::zed(release_nightly::release_nightly), + WorkflowFile::zed(run_tests::run_tests), + WorkflowFile::zed(release::release), + WorkflowFile::zed(cherry_pick::cherry_pick), + WorkflowFile::zed(compare_perf::compare_perf), + WorkflowFile::zed(run_agent_evals::run_unit_evals), + WorkflowFile::zed(run_agent_evals::run_cron_unit_evals), + WorkflowFile::zed(run_agent_evals::run_agent_evals), + WorkflowFile::zed(after_release::after_release), + WorkflowFile::zed(extension_tests::extension_tests), + WorkflowFile::zed(extension_bump::extension_bump), + WorkflowFile::zed(extension_release::extension_release), + /* workflows used for CI/CD in extension repositories */ + WorkflowFile::extension(extensions::run_tests::run_tests), + WorkflowFile::extension(extensions::bump_version::bump_version), + WorkflowFile::extension(extensions::release_version::release_version), ]; - fs::create_dir_all(dir) - .with_context(|| format!("Failed to create directory: {}", dir.display()))?; - for (filename, workflow) in workflows { - let content = workflow - .to_string() - .map_err(|e| anyhow::anyhow!("{}: {:?}", filename, e))?; - let content = format!( - "# Generated from xtask::workflows::{}\n# Rebuild with `cargo xtask workflows`.\n{}", - workflow.name.unwrap(), - content - ); - let file_path = dir.join(filename); - fs::write(&file_path, content)?; + for directory in [&workflow_dir, &extension_workflow_dir] { + fs::create_dir_all(directory) + .with_context(|| format!("Failed to create directory: {}", directory.display()))?; + } + + for workflow_file in workflows { + workflow_file.generate_file()?; } Ok(()) diff --git a/tooling/xtask/src/tasks/workflows/extension_bump.rs b/tooling/xtask/src/tasks/workflows/extension_bump.rs index 85e7dbeceed0d05f60f04dbf055553f71228ce54..41c90aec121a9e8737b975238266a26abed71a61 100644 --- a/tooling/xtask/src/tasks/workflows/extension_bump.rs +++ b/tooling/xtask/src/tasks/workflows/extension_bump.rs @@ -2,6 +2,7 @@ use gh_workflow::*; use indoc::indoc; use crate::tasks::workflows::{ + extension_release::extension_workflow_secrets, extension_tests::{self}, runners, steps::{self, CommonJobConditions, DEFAULT_REPOSITORY_OWNER_GUARD, NamedJob, named}, @@ -25,10 +26,11 @@ const VERSION_CHECK: &str = r#"sed -n 's/version = \"\(.*\)\"/\1/p' < extension. // This is used by various extensions repos in the zed-extensions org to bump extension versions. pub(crate) fn extension_bump() -> Workflow { let bump_type = WorkflowInput::string("bump-type", Some("patch".to_owned())); + // TODO: Ideally, this would have a default of `false`, but this is currently not + // supported in gh-workflows + let force_bump = WorkflowInput::bool("force-bump", None); - let app_id = WorkflowSecret::new("app-id", "The app ID used to create the PR"); - let app_secret = - WorkflowSecret::new("app-secret", "The app secret for the corresponding app ID"); + let (app_id, app_secret) = extension_workflow_secrets(); let test_extension = extension_tests::check_extension(); let (check_bump_needed, needs_bump, current_version) = check_bump_needed(); @@ -38,8 +40,15 @@ pub(crate) fn extension_bump() -> Workflow { let dependencies = [&test_extension, &check_bump_needed]; - let bump_version = - bump_extension_version(&dependencies, &bump_type, &needs_bump, &app_id, &app_secret); + let bump_version = bump_extension_version( + &dependencies, + ¤t_version, + &bump_type, + &needs_bump, + &force_bump, + &app_id, + &app_secret, + ); let create_label = create_version_label( &dependencies, &needs_bump, @@ -53,6 +62,7 @@ pub(crate) fn extension_bump() -> Workflow { Event::default().workflow_call( WorkflowCall::default() .add_input(bump_type.name, bump_type.call_input()) + .add_input(force_bump.name, force_bump.call_input()) .secrets([ (app_id.name.to_owned(), app_id.secret_configuration()), ( @@ -90,7 +100,7 @@ fn check_bump_needed() -> (NamedJob, StepOutput, StepOutput) { ]) .runs_on(runners::LINUX_SMALL) .timeout_minutes(1u32) - .add_step(steps::checkout_repo().add_with(("fetch-depth", 10))) + .add_step(steps::checkout_repo().add_with(("fetch-depth", 0))) .add_step(compare_versions); (named::job(job), version_changed, current_version) @@ -106,7 +116,7 @@ fn create_version_label( let (generate_token, generated_token) = generate_token(app_id, app_secret); let job = steps::dependant_job(dependencies) .cond(Expression::new(format!( - "{DEFAULT_REPOSITORY_OWNER_GUARD} && {} == 'false'", + "{DEFAULT_REPOSITORY_OWNER_GUARD} && github.event_name == 'push' && github.ref == 'refs/heads/main' && {} == 'false'", needs_bump.expr(), ))) .runs_on(runners::LINUX_LARGE) @@ -143,14 +153,21 @@ fn create_version_tag(current_version: &JobOutput, generated_token: StepOutput) fn compare_versions() -> (Step, StepOutput, StepOutput) { let check_needs_bump = named::bash(format!( indoc! { - r#" + r#" CURRENT_VERSION="$({})" + PR_PARENT_SHA="${{{{ github.event.pull_request.head.sha }}}}" - git checkout "$(git log -1 --format=%H)"~1 + if [[ -n "$PR_PARENT_SHA" ]]; then + git checkout "$PR_PARENT_SHA" + elif BRANCH_PARENT_SHA="$(git merge-base origin/main origin/zed-zippy-autobump)"; then + git checkout "$BRANCH_PARENT_SHA" + else + git checkout "$(git log -1 --format=%H)"~1 + fi - PREV_COMMIT_VERSION="$({})" + PARENT_COMMIT_VERSION="$({})" - [[ "$CURRENT_VERSION" == "$PREV_COMMIT_VERSION" ]] && \ + [[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \ echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \ echo "needs_bump=false" >> "$GITHUB_OUTPUT" @@ -169,17 +186,20 @@ fn compare_versions() -> (Step, StepOutput, StepOutput) { fn bump_extension_version( dependencies: &[&NamedJob], + current_version: &JobOutput, bump_type: &WorkflowInput, needs_bump: &JobOutput, + force_bump: &WorkflowInput, app_id: &WorkflowSecret, app_secret: &WorkflowSecret, ) -> NamedJob { let (generate_token, generated_token) = generate_token(app_id, app_secret); - let (bump_version, old_version, new_version) = bump_version(bump_type); + let (bump_version, new_version) = bump_version(current_version, bump_type); let job = steps::dependant_job(dependencies) .cond(Expression::new(format!( - "{DEFAULT_REPOSITORY_OWNER_GUARD} && {} == 'true'", + "{DEFAULT_REPOSITORY_OWNER_GUARD} &&\n({} == 'true' || {} == 'true')", + force_bump.expr(), needs_bump.expr(), ))) .runs_on(runners::LINUX_LARGE) @@ -188,16 +208,15 @@ fn bump_extension_version( .add_step(steps::checkout_repo()) .add_step(install_bump_2_version()) .add_step(bump_version) - .add_step(create_pull_request( - old_version, - new_version, - generated_token, - )); + .add_step(create_pull_request(new_version, generated_token)); named::job(job) } -fn generate_token(app_id: &WorkflowSecret, app_secret: &WorkflowSecret) -> (Step, StepOutput) { +pub(crate) fn generate_token( + app_id: &WorkflowSecret, + app_secret: &WorkflowSecret, +) -> (Step, StepOutput) { let step = named::uses("actions", "create-github-app-token", "v2") .id("generate-token") .add_with( @@ -215,10 +234,10 @@ fn install_bump_2_version() -> Step { named::run(runners::Platform::Linux, "pip install bump2version") } -fn bump_version(bump_type: &WorkflowInput) -> (Step, StepOutput, StepOutput) { +fn bump_version(current_version: &JobOutput, bump_type: &WorkflowInput) -> (Step, StepOutput) { let step = named::bash(format!( indoc! {r#" - OLD_VERSION="$({})" + OLD_VERSION="{}" cat < .bumpversion.cfg {} @@ -230,24 +249,18 @@ fn bump_version(bump_type: &WorkflowInput) -> (Step, StepOutput, StepOutput rm .bumpversion.cfg - echo "old_version=${{OLD_VERSION}}" >> "$GITHUB_OUTPUT" echo "new_version=${{NEW_VERSION}}" >> "$GITHUB_OUTPUT" "# }, - VERSION_CHECK, BUMPVERSION_CONFIG, bump_type, VERSION_CHECK + current_version, BUMPVERSION_CONFIG, bump_type, VERSION_CHECK )) .id("bump-version"); - let old_version = StepOutput::new(&step, "old_version"); let new_version = StepOutput::new(&step, "new_version"); - (step, old_version, new_version) + (step, new_version) } -fn create_pull_request( - old_version: StepOutput, - new_version: StepOutput, - generated_token: StepOutput, -) -> Step { +fn create_pull_request(new_version: StepOutput, generated_token: StepOutput) -> Step { let formatted_version = format!("v{}", new_version); named::uses("peter-evans", "create-pull-request", "v7").with( @@ -264,7 +277,7 @@ fn create_pull_request( "commit-message", format!("Bump version to {}", formatted_version), ) - .add("branch", format!("bump-from-{}", old_version)) + .add("branch", "zed-zippy-autobump") .add( "committer", "zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>", diff --git a/tooling/xtask/src/tasks/workflows/extension_release.rs b/tooling/xtask/src/tasks/workflows/extension_release.rs new file mode 100644 index 0000000000000000000000000000000000000000..fa064549184e675ae80b429f097448238b847935 --- /dev/null +++ b/tooling/xtask/src/tasks/workflows/extension_release.rs @@ -0,0 +1,70 @@ +use gh_workflow::{Event, Job, Run, Step, Use, Workflow, WorkflowCall}; +use indoc::indoc; + +use crate::tasks::workflows::{ + extension_bump::generate_token, + runners, + steps::{CommonJobConditions, NamedJob, checkout_repo, named}, + vars::{StepOutput, WorkflowSecret}, +}; + +pub(crate) fn extension_release() -> Workflow { + let (app_id, app_secret) = extension_workflow_secrets(); + + let create_release = create_release(&app_id, &app_secret); + named::workflow() + .on( + Event::default().workflow_call(WorkflowCall::default().secrets([ + (app_id.name.to_owned(), app_id.secret_configuration()), + ( + app_secret.name.to_owned(), + app_secret.secret_configuration(), + ), + ])), + ) + .add_job(create_release.name, create_release.job) +} + +fn create_release(app_id: &WorkflowSecret, app_secret: &WorkflowSecret) -> NamedJob { + let (generate_token, generated_token) = generate_token(&app_id, &app_secret); + let (get_extension_id, extension_id) = get_extension_id(); + + let job = Job::default() + .with_repository_owner_guard() + .runs_on(runners::LINUX_LARGE) + .add_step(generate_token) + .add_step(checkout_repo()) + .add_step(get_extension_id) + .add_step(release_action(extension_id, generated_token)); + + named::job(job) +} + +fn get_extension_id() -> (Step, StepOutput) { + let step = named::bash(indoc! { + r#" + EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)" + + echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT" + "#}) + .id("get-extension-id"); + + let extension_id = StepOutput::new(&step, "extension_id"); + + (step, extension_id) +} + +fn release_action(extension_id: StepOutput, generated_token: StepOutput) -> Step { + named::uses("huacnlee", "zed-extension-action", "v2") + .add_with(("extension-name", extension_id.to_string())) + .add_with(("push-to", "zed-industries/extensions")) + .add_env(("COMMITTER_TOKEN", generated_token.to_string())) +} + +pub(crate) fn extension_workflow_secrets() -> (WorkflowSecret, WorkflowSecret) { + let app_id = WorkflowSecret::new("app-id", "The app ID used to create the PR"); + let app_secret = + WorkflowSecret::new("app-secret", "The app secret for the corresponding app ID"); + + (app_id, app_secret) +} diff --git a/tooling/xtask/src/tasks/workflows/extensions/bump_version.rs b/tooling/xtask/src/tasks/workflows/extensions/bump_version.rs new file mode 100644 index 0000000000000000000000000000000000000000..58ab69c47ff7315a6835a0da1e930c97223a1c70 --- /dev/null +++ b/tooling/xtask/src/tasks/workflows/extensions/bump_version.rs @@ -0,0 +1,99 @@ +use gh_workflow::{ + Event, Expression, Input, Job, PullRequest, PullRequestType, Push, Run, Step, UsesJob, Workflow, +}; +use indexmap::IndexMap; +use indoc::indoc; + +use crate::tasks::workflows::{ + runners, + steps::{NamedJob, named}, + vars::{self, JobOutput, StepOutput}, +}; + +pub(crate) fn bump_version() -> Workflow { + let (determine_bump_type, bump_type) = determine_bump_type(); + let bump_type = bump_type.as_job_output(&determine_bump_type); + + let call_bump_version = call_bump_version(&determine_bump_type, bump_type); + + named::workflow() + .on(Event::default() + .push(Push::default().add_branch("main")) + .pull_request(PullRequest::default().add_type(PullRequestType::Labeled))) + .add_job(determine_bump_type.name, determine_bump_type.job) + .add_job(call_bump_version.name, call_bump_version.job) +} + +pub(crate) fn call_bump_version( + depending_job: &NamedJob, + bump_type: JobOutput, +) -> NamedJob { + let job = Job::default() + .cond(Expression::new(format!( + indoc! { + "(github.event.action == 'labeled' && {} != 'patch') || + github.event_name == 'push'" + }, + bump_type.expr() + ))) + .uses( + "zed-industries", + "zed", + ".github/workflows/extension_bump.yml", + "main", + ) + .add_need(depending_job.name.clone()) + .with( + Input::default() + .add("bump-type", bump_type.to_string()) + .add("force-bump", true), + ) + .secrets(IndexMap::from([ + ("app-id".to_owned(), vars::ZED_ZIPPY_APP_ID.to_owned()), + ( + "app-secret".to_owned(), + vars::ZED_ZIPPY_APP_PRIVATE_KEY.to_owned(), + ), + ])); + + named::job(job) +} + +fn determine_bump_type() -> (NamedJob, StepOutput) { + let (get_bump_type, output) = get_bump_type(); + let job = Job::default() + .runs_on(runners::LINUX_DEFAULT) + .add_step(get_bump_type) + .outputs([(output.name.to_owned(), output.to_string())]); + (named::job(job), output) +} + +fn get_bump_type() -> (Step, StepOutput) { + let step = named::bash( + indoc! {r#" + if [ "$HAS_MAJOR_LABEL" = "true" ]; then + bump_type="major" + elif [ "$HAS_MINOR_LABEL" = "true" ]; then + bump_type="minor" + else + bump_type="patch" + fi + echo "bump_type=$bump_type" >> $GITHUB_OUTPUT + "#}, + ) + .add_env(("HAS_MAJOR_LABEL", + indoc!{ + "${{ (github.event.action == 'labeled' && github.event.label.name == 'major') || + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'major')) }}" + })) + .add_env(("HAS_MINOR_LABEL", + indoc!{ + "${{ (github.event.action == 'labeled' && github.event.label.name == 'minor') || + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'minor')) }}" + })) + .id("get-bump-type"); + + let step_output = StepOutput::new(&step, "bump_type"); + + (step, step_output) +} diff --git a/tooling/xtask/src/tasks/workflows/extensions/mod.rs b/tooling/xtask/src/tasks/workflows/extensions/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..d26100f09d6a0559e3548e9303a7ef05c93c8f33 --- /dev/null +++ b/tooling/xtask/src/tasks/workflows/extensions/mod.rs @@ -0,0 +1,24 @@ +use gh_workflow::{Job, UsesJob}; +use indexmap::IndexMap; + +use crate::tasks::workflows::vars; + +pub(crate) mod bump_version; +pub(crate) mod release_version; +pub(crate) mod run_tests; + +pub(crate) trait WithAppSecrets: Sized { + fn with_app_secrets(self) -> Self; +} + +impl WithAppSecrets for Job { + fn with_app_secrets(self) -> Self { + self.secrets(IndexMap::from([ + ("app-id".to_owned(), vars::ZED_ZIPPY_APP_ID.to_owned()), + ( + "app-secret".to_owned(), + vars::ZED_ZIPPY_APP_PRIVATE_KEY.to_owned(), + ), + ])) + } +} diff --git a/tooling/xtask/src/tasks/workflows/extensions/release_version.rs b/tooling/xtask/src/tasks/workflows/extensions/release_version.rs new file mode 100644 index 0000000000000000000000000000000000000000..ebeb6959a97d672abeaa151c124e48008ddf9e05 --- /dev/null +++ b/tooling/xtask/src/tasks/workflows/extensions/release_version.rs @@ -0,0 +1,26 @@ +use gh_workflow::{Event, Job, Push, UsesJob, Workflow}; + +use crate::tasks::workflows::{ + extensions::WithAppSecrets, + steps::{NamedJob, named}, +}; + +pub(crate) fn release_version() -> Workflow { + let create_release = call_release_version(); + named::workflow() + .on(Event::default().push(Push::default().add_tag("v**"))) + .add_job(create_release.name, create_release.job) +} + +pub(crate) fn call_release_version() -> NamedJob { + let job = Job::default() + .uses( + "zed-industries", + "zed", + ".github/workflows/extension_release.yml", + "main", + ) + .with_app_secrets(); + + named::job(job) +} diff --git a/tooling/xtask/src/tasks/workflows/extensions/run_tests.rs b/tooling/xtask/src/tasks/workflows/extensions/run_tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..ffcaecf3919a0c5b3948ef3004a3886939c95cf4 --- /dev/null +++ b/tooling/xtask/src/tasks/workflows/extensions/run_tests.rs @@ -0,0 +1,25 @@ +use gh_workflow::{Event, Job, PullRequest, UsesJob, Workflow}; + +use crate::tasks::workflows::{ + steps::{NamedJob, named}, + vars::one_workflow_per_non_main_branch, +}; + +pub(crate) fn run_tests() -> Workflow { + let call_extension_tests = call_extension_tests(); + named::workflow() + .on(Event::default().pull_request(PullRequest::default().add_branch("**"))) + .concurrency(one_workflow_per_non_main_branch()) + .add_job(call_extension_tests.name, call_extension_tests.job) +} + +pub(crate) fn call_extension_tests() -> NamedJob { + let job = Job::default().uses( + "zed-industries", + "zed", + ".github/workflows/extension_tests.yml", + "main", + ); + + named::job(job) +} diff --git a/tooling/xtask/src/tasks/workflows/run_bundling.rs b/tooling/xtask/src/tasks/workflows/run_bundling.rs index f01ca4aaa26e8d3e731fdac4f1d77b4b10a39174..a0793ffb68913ae381f3d70de22f8e6c302bb92a 100644 --- a/tooling/xtask/src/tasks/workflows/run_bundling.rs +++ b/tooling/xtask/src/tasks/workflows/run_bundling.rs @@ -9,6 +9,7 @@ use crate::tasks::workflows::{ use super::{runners, steps}; use gh_workflow::*; +use indoc::indoc; pub fn run_bundling() -> Workflow { let bundle = ReleaseBundleJobs { @@ -42,10 +43,11 @@ pub fn run_bundling() -> Workflow { fn bundle_job(deps: &[&NamedJob]) -> Job { dependant_job(deps) .when(deps.len() == 0, |job| - job.cond(Expression::new( - "(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || - (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))", - ))) + job.cond(Expression::new( + indoc! { + r#"(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') || + (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))"#, + }))) .timeout_minutes(60u32) } diff --git a/tooling/xtask/src/tasks/workflows/steps.rs b/tooling/xtask/src/tasks/workflows/steps.rs index e20dafe18a660a0067708cc1e9d15d59572e5f53..62f71bbcb5129117a4d2d57e9858c48393a73243 100644 --- a/tooling/xtask/src/tasks/workflows/steps.rs +++ b/tooling/xtask/src/tasks/workflows/steps.rs @@ -128,9 +128,9 @@ pub fn script(name: &str) -> Step { } } -pub struct NamedJob { +pub struct NamedJob { pub name: String, - pub job: Job, + pub job: Job, } // impl NamedJob { @@ -282,15 +282,19 @@ pub mod named { Workflow::default().name( named::function_name(1) .split("::") - .next() - .unwrap() - .to_owned(), + .collect::>() + .into_iter() + .rev() + .skip(1) + .rev() + .collect::>() + .join("::"), ) } /// Returns a Job with the same name as the enclosing function. /// (note job names may not contain `::`) - pub fn job(job: Job) -> NamedJob { + pub fn job(job: Job) -> NamedJob { NamedJob { name: function_name(1).split("::").last().unwrap().to_owned(), job, diff --git a/tooling/xtask/src/tasks/workflows/vars.rs b/tooling/xtask/src/tasks/workflows/vars.rs index 8dae64a1ea10ca891d23cabb989f5073ddd1755d..b5a3085a4955db75ca8abfc395e8755a235451ff 100644 --- a/tooling/xtask/src/tasks/workflows/vars.rs +++ b/tooling/xtask/src/tasks/workflows/vars.rs @@ -219,6 +219,14 @@ impl WorkflowInput { } } + pub fn bool(name: &'static str, default: Option) -> Self { + Self { + input_type: "boolean", + name, + default: default.as_ref().map(ToString::to_string), + } + } + pub fn input(&self) -> WorkflowDispatchInput { WorkflowDispatchInput { description: self.name.to_owned(), @@ -236,11 +244,15 @@ impl WorkflowInput { default: self.default.clone(), } } + + pub(crate) fn expr(&self) -> String { + format!("inputs.{}", self.name) + } } impl std::fmt::Display for WorkflowInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "${{{{ inputs.{} }}}}", self.name) + write!(f, "${{{{ {} }}}}", self.expr()) } }