From c38ce585126bda355b72c5e415bd4f980713ef83 Mon Sep 17 00:00:00 2001 From: person93 Date: Tue, 29 Mar 2022 13:17:34 -0400 Subject: [PATCH 1/4] refactor(config): Add "post_release_hook" field to Config --- src/config.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/config.rs b/src/config.rs index 04230a4a4..c73847260 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,6 +29,7 @@ pub struct Config { pub pre_release_replacements: Option>, pub post_release_replacements: Option>, pub pre_release_hook: Option, + pub post_release_hook: Option, pub tag_message: Option, pub tag_prefix: Option, pub tag_name: Option, @@ -77,6 +78,7 @@ impl Config { pre_release_replacements: Some(empty.pre_release_replacements().to_vec()), post_release_replacements: Some(empty.post_release_replacements().to_vec()), pre_release_hook: empty.pre_release_hook().cloned(), + post_release_hook: empty.post_release_hook().cloned(), tag_message: Some(empty.tag_message().to_owned()), tag_prefix: None, // Skipping, its location dependent tag_name: Some(empty.tag_name().to_owned()), @@ -149,6 +151,9 @@ impl Config { if let Some(pre_release_hook) = source.pre_release_hook.as_ref() { self.pre_release_hook = Some(pre_release_hook.to_owned()); } + if let Some(post_release_hook) = source.post_release_hook.as_ref() { + self.post_release_hook = Some(post_release_hook.to_owned()); + } if let Some(tag_message) = source.tag_message.as_deref() { self.tag_message = Some(tag_message.to_owned()); } @@ -271,6 +276,10 @@ impl Config { self.pre_release_hook.as_ref() } + pub fn post_release_hook(&self) -> Option<&Command> { + self.post_release_hook.as_ref() + } + pub fn tag_message(&self) -> &str { self.tag_message .as_deref() From 9c64d2294eb43b4b055d36974cf2cc8001e7ad4e Mon Sep 17 00:00:00 2001 From: person93 Date: Tue, 29 Mar 2022 13:19:00 -0400 Subject: [PATCH 2/4] feat: Add support for post-commit hook --- src/release.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/release.rs b/src/release.rs index 35e80aeb3..619d715af 100644 --- a/src/release.rs +++ b/src/release.rs @@ -662,7 +662,27 @@ fn release_packages<'m>( } } - // STEP 7: git push + // STEP 7: post-release hook + for pkg in pkgs { + if let (Some(post_rel_hook), Some(_)) = + (pkg.config.post_release_hook(), pkg.version.as_ref()) + { + let cwd = pkg.package_root; + let post_rel_hook = post_rel_hook.args(); + log::debug!("Calling post-release hook: {:?}", post_rel_hook); + let envs = maplit::btreemap! { + OsStr::new("RELEASE_TAG") => OsStr::new(pkg.tag.as_ref().expect("post-release hook with no tag")), + OsStr::new("DRY_RUN") => OsStr::new(if dry_run { "true" } else { "false" }), + }; + // we use dry_run environmental variable to run the script + // so here we set dry_run=false and always execute the command. + if !cmd::call_with_env(post_rel_hook, envs, cwd, false)? { + todo!("handle non-zero exit from post-release hook") + } + } + } + + // STEP 8: git push if ws_config.push() { let mut shared_refs = HashSet::new(); for pkg in pkgs { From fc4f1174e2343b67c602cf385442e94b43305463 Mon Sep 17 00:00:00 2001 From: person93 Date: Wed, 30 Mar 2022 19:48:54 -0400 Subject: [PATCH 3/4] test: Add integration test for post-release hook --- tests/post-release-hook/crate/.gitignore | 1 + tests/post-release-hook/crate/Cargo.lock | 7 + tests/post-release-hook/crate/Cargo.toml | 5 + tests/post-release-hook/crate/release.toml | 2 + tests/post-release-hook/crate/src/main.rs | 9 ++ tests/post-release-hook/main.rs | 132 ++++++++++++++++++ tests/post-release-hook/workspace/.gitignore | 1 + tests/post-release-hook/workspace/Cargo.lock | 11 ++ tests/post-release-hook/workspace/Cargo.toml | 2 + .../post-release-hook/workspace/a/Cargo.toml | 5 + tests/post-release-hook/workspace/a/src | 1 + .../post-release-hook/workspace/b/Cargo.toml | 5 + tests/post-release-hook/workspace/b/src | 1 + .../post-release-hook/workspace/release.toml | 1 + 14 files changed, 183 insertions(+) create mode 100644 tests/post-release-hook/crate/.gitignore create mode 100644 tests/post-release-hook/crate/Cargo.lock create mode 100644 tests/post-release-hook/crate/Cargo.toml create mode 100644 tests/post-release-hook/crate/release.toml create mode 100644 tests/post-release-hook/crate/src/main.rs create mode 100644 tests/post-release-hook/main.rs create mode 120000 tests/post-release-hook/workspace/.gitignore create mode 100644 tests/post-release-hook/workspace/Cargo.lock create mode 100644 tests/post-release-hook/workspace/Cargo.toml create mode 100644 tests/post-release-hook/workspace/a/Cargo.toml create mode 120000 tests/post-release-hook/workspace/a/src create mode 100644 tests/post-release-hook/workspace/b/Cargo.toml create mode 120000 tests/post-release-hook/workspace/b/src create mode 120000 tests/post-release-hook/workspace/release.toml diff --git a/tests/post-release-hook/crate/.gitignore b/tests/post-release-hook/crate/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/tests/post-release-hook/crate/.gitignore @@ -0,0 +1 @@ +target diff --git a/tests/post-release-hook/crate/Cargo.lock b/tests/post-release-hook/crate/Cargo.lock new file mode 100644 index 000000000..73829e8a5 --- /dev/null +++ b/tests/post-release-hook/crate/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "post-release-hook-crate" +version = "1.2.3" diff --git a/tests/post-release-hook/crate/Cargo.toml b/tests/post-release-hook/crate/Cargo.toml new file mode 100644 index 000000000..b9b58a507 --- /dev/null +++ b/tests/post-release-hook/crate/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "post-release-hook-crate" +version = "1.2.3" +edition = "2021" +publish = false diff --git a/tests/post-release-hook/crate/release.toml b/tests/post-release-hook/crate/release.toml new file mode 100644 index 000000000..f5962b7f2 --- /dev/null +++ b/tests/post-release-hook/crate/release.toml @@ -0,0 +1,2 @@ +push = false +post-release-hook = ["cargo", "run"] diff --git a/tests/post-release-hook/crate/src/main.rs b/tests/post-release-hook/crate/src/main.rs new file mode 100644 index 000000000..4e6b34c37 --- /dev/null +++ b/tests/post-release-hook/crate/src/main.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + eprintln!("START_ENV_VARS"); + for (key, value) in env::vars_os() { + eprintln!("{}={}", key.to_string_lossy(), value.to_string_lossy()); + } + eprintln!("END_ENV_VARS"); +} diff --git a/tests/post-release-hook/main.rs b/tests/post-release-hook/main.rs new file mode 100644 index 000000000..800b3a329 --- /dev/null +++ b/tests/post-release-hook/main.rs @@ -0,0 +1,132 @@ +use assert_fs::prelude::*; +use assert_fs::TempDir; +use maplit::hashset; +use std::collections::HashSet; +use std::env; +use std::fmt::Debug; +use std::path::Path; +use std::process::{Command, Stdio}; +use std::str::Lines; + +#[test] +fn krate() { + let root = Path::new("tests/post-release-hook/crate") + .canonicalize() + .unwrap(); + let expected = hashset! { + EnvVar::new("RELEASE_TAG", "v1.2.3"), + EnvVar::new("DRY_RUN", "true"), + }; + let output = run_cargo_release(root.as_path()); + let mut output = output.lines(); + let actual = read_env_var_dump(&mut output).expect("missing env var dump"); + assert!( + actual.is_superset(&expected), + "not all expected env vars are present and matching\n{expected:#?}" + ); +} + +#[test] +fn workspace() { + let ws_root = Path::new("tests/post-release-hook/workspace") + .canonicalize() + .unwrap(); + let expected = vec![ + hashset! { + EnvVar::new("RELEASE_TAG", "post-release-hook-ws-a-v42.42.42"), + EnvVar::new("DRY_RUN", "true"), + }, + hashset! { + EnvVar::new("RELEASE_TAG", "post-release-hook-ws-b-v420.420.420"), + EnvVar::new("DRY_RUN", "true"), + }, + ]; + let output = run_cargo_release(ws_root.as_path()); + let mut output = output.lines(); + let mut actual = Vec::new(); + while let Some(vars) = read_env_var_dump(&mut output) { + actual.push(vars); + } + for expected in expected { + assert!(actual.iter().any(|actual| actual.is_superset(&expected))); + } +} + +fn run_cargo_release(dir: &Path) -> String { + let temp = TempDir::new().unwrap(); + temp.copy_from(dir, &["**"]).unwrap(); + + git(temp.path(), &["init"]); + git(temp.path(), &["add", "."]); + git( + temp.path(), + &["commit", "--message", "this is a commit message"], + ); + + let mut cargo = env::var_os("CARGO").map_or_else(|| Command::new("cargo"), Command::new); + let output = cargo + .stderr(Stdio::piped()) + .args(["run", "--manifest-path"]) + .arg(Path::new(PROJECT_ROOT).join("Cargo.toml")) + .args(["--", "release", "-vv"]) + .current_dir(&temp) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + + temp.close().unwrap(); + + if !output.status.success() { + panic!("cargo release exited with {}", output.status); + } + let output = String::from_utf8(output.stderr).unwrap(); + eprintln!("{output}"); + output +} + +fn git(dir: &Path, args: &[&str]) { + assert!(Command::new("git") + .args(args) + .current_dir(dir) + .spawn() + .unwrap() + .wait() + .unwrap() + .success()); +} + +fn read_env_var_dump(lines: &mut Lines) -> Option> { + let mut variables = HashSet::new(); + loop { + if lines.next()?.trim() == "START_ENV_VARS" { + break; + } + } + loop { + let line = lines.next().expect("missing end of env var dump").trim(); + if line == "END_ENV_VARS" { + return Some(variables); + } + + let (key, value) = line.split_once('=').unwrap(); + variables.insert(EnvVar::new(key, value)); + } +} + +#[derive(Debug, Eq, PartialEq, Hash)] +struct EnvVar { + key: String, + value: String, +} + +impl EnvVar { + fn new(key: &str, value: &str) -> Self { + Self { + key: key.to_owned(), + value: value.to_owned(), + } + } +} + +const PROJECT_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR")); diff --git a/tests/post-release-hook/workspace/.gitignore b/tests/post-release-hook/workspace/.gitignore new file mode 120000 index 000000000..cb0e2a448 --- /dev/null +++ b/tests/post-release-hook/workspace/.gitignore @@ -0,0 +1 @@ +../crate/.gitignore \ No newline at end of file diff --git a/tests/post-release-hook/workspace/Cargo.lock b/tests/post-release-hook/workspace/Cargo.lock new file mode 100644 index 000000000..4464e5d94 --- /dev/null +++ b/tests/post-release-hook/workspace/Cargo.lock @@ -0,0 +1,11 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "post-release-hook-ws-a" +version = "42.42.42" + +[[package]] +name = "post-release-hook-ws-b" +version = "420.420.420" diff --git a/tests/post-release-hook/workspace/Cargo.toml b/tests/post-release-hook/workspace/Cargo.toml new file mode 100644 index 000000000..6e04d97b6 --- /dev/null +++ b/tests/post-release-hook/workspace/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["a", "b"] diff --git a/tests/post-release-hook/workspace/a/Cargo.toml b/tests/post-release-hook/workspace/a/Cargo.toml new file mode 100644 index 000000000..04337cc61 --- /dev/null +++ b/tests/post-release-hook/workspace/a/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "post-release-hook-ws-a" +version = "42.42.42" +edition = "2021" +publish = false diff --git a/tests/post-release-hook/workspace/a/src b/tests/post-release-hook/workspace/a/src new file mode 120000 index 000000000..af429d9a4 --- /dev/null +++ b/tests/post-release-hook/workspace/a/src @@ -0,0 +1 @@ +../../crate/src/ \ No newline at end of file diff --git a/tests/post-release-hook/workspace/b/Cargo.toml b/tests/post-release-hook/workspace/b/Cargo.toml new file mode 100644 index 000000000..9fecab68e --- /dev/null +++ b/tests/post-release-hook/workspace/b/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "post-release-hook-ws-b" +version = "420.420.420" +edition = "2021" +publish = false diff --git a/tests/post-release-hook/workspace/b/src b/tests/post-release-hook/workspace/b/src new file mode 120000 index 000000000..af429d9a4 --- /dev/null +++ b/tests/post-release-hook/workspace/b/src @@ -0,0 +1 @@ +../../crate/src/ \ No newline at end of file diff --git a/tests/post-release-hook/workspace/release.toml b/tests/post-release-hook/workspace/release.toml new file mode 120000 index 000000000..270d31bf1 --- /dev/null +++ b/tests/post-release-hook/workspace/release.toml @@ -0,0 +1 @@ +../crate/release.toml \ No newline at end of file From 811dd463513bd972ebb3b6bffeac3631800a8aa4 Mon Sep 17 00:00:00 2001 From: person93 Date: Wed, 30 Mar 2022 20:13:44 -0400 Subject: [PATCH 4/4] feat: Exit 107 on post-release hook fail --- src/release.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/release.rs b/src/release.rs index 619d715af..ec029b9de 100644 --- a/src/release.rs +++ b/src/release.rs @@ -677,7 +677,8 @@ fn release_packages<'m>( // we use dry_run environmental variable to run the script // so here we set dry_run=false and always execute the command. if !cmd::call_with_env(post_rel_hook, envs, cwd, false)? { - todo!("handle non-zero exit from post-release hook") + log::error!("Post release hook failed. Aborting. CHANGES NOT ROLLED BACK.",); + return Ok(107); } } }