diff --git a/.changes/1053.json b/.changes/1053.json new file mode 100644 index 000000000..7d9fac40a --- /dev/null +++ b/.changes/1053.json @@ -0,0 +1,5 @@ +{ + "description": "add automatic tagging of new releases.", + "issues": [1050], + "type": "internal" +} diff --git a/.github/actions/cargo-install-upload-artifacts/action.yml b/.github/actions/cargo-install-upload-artifacts/action.yml index 69cf2721c..79d780e9f 100644 --- a/.github/actions/cargo-install-upload-artifacts/action.yml +++ b/.github/actions/cargo-install-upload-artifacts/action.yml @@ -27,7 +27,7 @@ runs: echo "::set-output name=out-dir::${out_dir}" echo "::set-output name=artifacts-dir::${artifacts_dir}" shell: bash - - run: rm -rf .git + - run: mv .git .git.bak shell: bash - name: Build with all features run: @@ -82,3 +82,5 @@ runs: name: ${{ steps.archive.outputs.name }} path: ${{ steps.archive.outputs.path }} if-no-files-found: error + - run: mv .git.bak .git + shell: bash \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 974a82102..4d5adfa6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ on: pull_request: push: - branches: [main, staging, trying] + branches: [main, staging, trying, v*.*.*] tags: - "v*.*.*" @@ -351,7 +351,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 # fetch tags for publish + ssh-key: "${{ secrets.COMMIT_KEY }}" # use deploy key to trigger workflow - uses: ./.github/actions/setup-rust + - run: cargo xtask ci-job release + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - uses: ./.github/actions/cargo-publish with: cargo-registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 66150456a..02d603948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cross" -version = "0.2.4" +version = "0.2.5" dependencies = [ "atty", "clap", diff --git a/Cargo.toml b/Cargo.toml index 47d71f1cb..afb1d47f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ keywords = ["cross", "compilation", "testing", "tool"] license = "MIT OR Apache-2.0" name = "cross" repository = "https://github.com/cross-rs/cross" -version = "0.2.4" +version = "0.2.5" edition = "2021" include = [ "src/**/*", diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 012be668d..5ed75bc0c 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -1,3 +1,4 @@ +mod release; mod target_matrix; use crate::util::gha_output; @@ -32,6 +33,7 @@ pub enum CiJob { #[clap(long, env = "COMMIT_AUTHOR")] author: String, }, + Release(release::Release), } pub fn ci(args: CiJob, metadata: CargoMetadata) -> cross::Result<()> { @@ -117,6 +119,9 @@ pub fn ci(args: CiJob, metadata: CargoMetadata) -> cross::Result<()> { CiJob::TargetMatrix { message, author } => { target_matrix::run(message, author)?; } + CiJob::Release(r) => { + r.run(&mut cross::shell::Verbosity::Verbose(1).into())?; + } } Ok(()) } diff --git a/xtask/src/ci/release.rs b/xtask/src/ci/release.rs new file mode 100644 index 000000000..aaf293a7b --- /dev/null +++ b/xtask/src/ci/release.rs @@ -0,0 +1,140 @@ +// TODO: More details/ensure accuracy +//! +//! # The `publish` job + `build` +//! +//! The `publish` job is triggered in five cases. +//! All of these need the event to be a push (since the `build` job is a need for `publish`, +//! and that need is gated on `if: github.event_name == 'push'` +//! +//! 1. on the default_branch, or `main` in our case +//! 2. on the `staging` branch +//! 3. on the `try` branch +//! 4. on branches matching `v*.*.*` +//! 5. on tags matching `v*.*.*` +//! +//! ## In `default_branch`/`main` +//! +//! In the case of `main`, the workflow does the following. +//! +//! 1. `build` builds and publishes images for these targets with the tag `main` and `edge`. It also assembles binaries for artifacting +//! 2. If all ok, the `publish` job triggers +//! 3. this calls `cargo xtask ci-job release` which +//! 1. inspects the package version +//! 2. if the version does not exist as a tag, create a new tag for that version and push it. +//! this tag will trigger the `CI` workflow again, but with `ref_type == "tag"` +//! if the version does exist, exit quietly. +//! 4. `publish` now calls `cargo-publish` which creates a new release with draft tag `Unreleased`, attaches binaries from step 1, and does a `cargo publish --dry-run`, this tag uses the standard github token for workflows, and should not be able to trigger any other workflows. +//! +//! ## In `staging` / `try` branch +//! +//! In `staging` or `try`, we need to make sure that nothing goes out. +//! This includes tags, releases and `cargo publish` +//! +//! 1. `build` builds (but does not publish) images for these targets with the tag `try`/`staging`. It also assembles binaries for artifacting +//! 2. If all ok, the `publish` job triggers +//! 4. this calls `cargo xtask ci-job release` which +//! 1. inspects the package version +//! 2. if the version does not exist as a tag, "dry-run" creating the tag and push it. +//! if the version does exist, exit quietly. +//! 5. `publish` now calls `cargo-publish` which does a `cargo publish --dry-run` +//! +//! ## On branches matching `v*.*.*` +//! +//! 1. `build` builds (but does not publish) images for these targets with the tag `vx.y.z` and `edge`. It also assembles binaries for artifacting +//! 2. If all ok, the `publish` job triggers +//! 3. this calls `cargo xtask ci-job release` which +//! 1. inspects the package version +//! 2. since the `ref_type == "branch"`, if the version does not exist as a tag, +//! create a new tag for that version and push it. +//! this tag will trigger the `CI` workflow again, but with `ref_type == "tag"` +//! if the version does exist, exit quietly. +//! 4. `publish` now calls `cargo-publish` which does nothing +//! +//! ## On tags matching `v*.*.*` +//! +//! In this case, we need to make sure that the created release does not trigger a workflow. +//! +//! 1. `build` builds and publishes images for these targets with the tag `vx.y.z`. It also assembles binaries for artifacting +//! 2. If all ok, the `publish` job triggers +//! 4. this calls `cargo xtask ci-job release` which +//! 1. inspects the package version +//! 2. since the `ref_type == "tag"`, the program exits quietly. +//! 5. `publish` now calls `cargo-publish` which creates a new release with tag `vx.y.z`, attaches binaries from step 1, and does a `cargo publish`, this release tag uses the standard github token for workflows, and should not be able to trigger any other workflows. +use clap::Args; +use cross::{shell::MessageInfo, CommandExt}; + +#[derive(Debug, Args)] +pub struct Release { + #[clap(long, default_value = "main", env = "DEFAULT_BRANCH")] + default_branch: String, + #[clap(long, hide = true, env = "GITHUB_REF_TYPE")] + pub ref_type: Option, + #[clap(long, hide = true, env = "GITHUB_REF_NAME")] + ref_name: Option, +} + +impl Release { + pub fn run(&self, msg_info: &mut MessageInfo) -> Result<(), color_eyre::Report> { + if self.ref_type.as_deref() == Some("branch") { + self.tag(msg_info)?; + } + Ok(()) + } + + pub fn tag(&self, msg_info: &mut MessageInfo) -> Result<(), color_eyre::Report> { + color_eyre::eyre::ensure!( + self.ref_type.as_deref() == Some("branch"), + "tag() should only be called on a branch" + ); + let current_branch = self.ref_name.as_deref().unwrap(); + let version = pkgid()?.rsplit_once('#').unwrap().1.trim().to_string(); + let tag = format!("v{version}"); + + let has_tag = std::process::Command::new("git") + .args(["tag", "--list"]) + .run_and_get_stdout(msg_info)? + .lines() + .any(|it| it.trim() == tag); + if !has_tag { + let dry_run = std::env::var("CI").is_err() + || (current_branch != self.default_branch + && !wildmatch::WildMatch::new("v*.*.*").matches(current_branch)); + + eprint!("Taging!"); + let mut tagging = std::process::Command::new("git"); + tagging.args(["tag", &tag]); + let mut push = std::process::Command::new("git"); + push.args(["push", "--tags"]); + if dry_run { + eprintln!(" (dry run)"); + tagging.print(msg_info)?; + push.print(msg_info)?; + } else { + eprintln!(); + tagging.run(msg_info, false)?; + push.run(msg_info, false)?; + } + } + Ok(()) + } +} + +#[track_caller] +fn pkgid() -> Result { + cross::cargo_command() + .arg("pkgid") + .current_dir(crate::util::get_cargo_workspace()) + .run_and_get_stdout(&mut cross::shell::Verbosity::Verbose(1).into()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn assert_pkgid_hashtag() { + let pkgid = pkgid().unwrap(); + assert!(!pkgid.contains('@')); + assert!(pkgid.contains("cross")); + } +}