| 1 | use clap::{Args, Subcommand, ValueEnum}; |
| 2 | use xshell::{Cmd, cmd}; |
| 3 | |
| 4 | use crate::{Result, sh}; |
| 5 | |
| 6 | #[derive(Args)] |
| 7 | pub struct ReleaseArgs { |
| 8 | #[clap(subcommand)] |
| 9 | cmd: ReleaseCommand, |
| 10 | } |
| 11 | |
| 12 | #[derive(PartialEq, Subcommand)] |
| 13 | enum ReleaseCommand { |
| 14 | /// Prepare the release of the matrix-sdk workspace. |
| 15 | /// |
| 16 | /// This command will update the `README.md`, update the `CHANGELOG.md` file |
| 17 | /// using, and bump the versions in the `Cargo.toml` files. |
| 18 | Prepare { |
| 19 | /// What type of version bump we should perform. |
| 20 | version: ReleaseVersion, |
| 21 | /// Actually prepare a release. Dry-run mode is the default. |
| 22 | #[clap(long)] |
| 23 | execute: bool, |
| 24 | /// The crate or package that should be released. Use this if you'd like |
| 25 | /// to release only one specific crate. The default is to |
| 26 | /// release all crates. |
| 27 | #[clap(long)] |
| 28 | package: Option<String>, |
| 29 | }, |
| 30 | /// Publish the release. |
| 31 | /// |
| 32 | /// This command will create signed tags, publish the release on crates.io, |
| 33 | /// and finally push the tags to the repo. |
| 34 | Publish { |
| 35 | /// Actually publish a release. Dry-run mode is the default |
| 36 | #[clap(long)] |
| 37 | execute: bool, |
| 38 | /// The crate or package that should be released. Use this if you'd like |
| 39 | /// to release only one specific crate. The default is to |
| 40 | /// release all crates. |
| 41 | #[clap(long)] |
| 42 | package: Option<String>, |
| 43 | }, |
| 44 | /// Get a list of interesting changes that happened in the last week. |
| 45 | WeeklyReport, |
| 46 | } |
| 47 | |
| 48 | #[derive(Clone, Copy, Debug, Default, PartialEq, ValueEnum)] |
| 49 | enum ReleaseVersion { |
| 50 | // TODO: Add a Major variant here once we're stable. |
| 51 | /// Create a new minor release. |
| 52 | #[default] |
| 53 | Minor, |
| 54 | /// Create a new patch release. |
| 55 | Patch, |
| 56 | /// Create a new release candidate. |
| 57 | Rc, |
| 58 | } |
| 59 | |
| 60 | impl ReleaseVersion { |
| 61 | fn as_str(&self) -> &str { |
| 62 | match self { |
| 63 | ReleaseVersion::Minor => "minor", |
| 64 | ReleaseVersion::Patch => "patch", |
| 65 | ReleaseVersion::Rc => "rc", |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | impl ReleaseArgs { |
| 71 | pub fn run(self) -> Result<()> { |
| 72 | check_prerequisites(); |
| 73 | |
| 74 | match self.cmd { |
| 75 | ReleaseCommand::Prepare { version, execute, package } => { |
| 76 | prepare(version, execute, package) |
| 77 | } |
| 78 | ReleaseCommand::Publish { execute, package } => publish(execute, package), |
| 79 | ReleaseCommand::WeeklyReport => weekly_report(), |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | fn check_prerequisites() { |
| 85 | let sh = sh(); |
| 86 | |
| 87 | if cmd!(sh, "cargo release --version").quiet().ignore_stdout().run().is_err() { |
| 88 | eprintln!("This command requires cargo-release, please install it."); |
| 89 | eprintln!( |
| 90 | "More info can be found at: \ |
| 91 | https://github.com/crate-ci/cargo-release?tab=readme-ov-file#install" |
| 92 | ); |
| 93 | |
| 94 | std::process::exit(1); |
| 95 | } |
| 96 | |
| 97 | if cmd!(sh, "gh version").quiet().ignore_stdout().run().is_err() { |
| 98 | eprintln!("This command requires GitHub CLI, please install it."); |
| 99 | eprintln!("More info can be found at: https://cli.github.com/"); |
| 100 | |
| 101 | std::process::exit(1); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | fn append_options<'a>(command: Cmd<'a>, execute: &bool, package: &Option<String>) -> Cmd<'a> { |
| 106 | let command = if *execute { command.arg("--execute") } else { command }; |
| 107 | if let Some(package) = package.as_deref() { |
| 108 | command.args(["--package", package]) |
| 109 | } else { |
| 110 | command.arg("--workspace") |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | fn prepare(version: ReleaseVersion, execute: bool, package: Option<String>) -> Result<()> { |
| 115 | let sh = sh(); |
| 116 | |
| 117 | let cmd = cmd!(sh, "cargo release --no-publish --no-tag --no-push"); |
| 118 | let cmd = append_options(cmd, &execute, &package); |
| 119 | |
| 120 | let cmd = cmd.arg(version.as_str()); |
| 121 | |
| 122 | cmd.run()?; |
| 123 | |
| 124 | if execute { |
| 125 | eprintln!( |
| 126 | "Please double check the changelogs and edit them if necessary, \ |
| 127 | publish the PR, and once it's merged, switch to `main` and pull the PR \ |
| 128 | and run `cargo xtask release publish`" |
| 129 | ); |
| 130 | } |
| 131 | |
| 132 | Ok(()) |
| 133 | } |
| 134 | |
| 135 | fn publish(execute: bool, package: Option<String>) -> Result<()> { |
| 136 | let sh = sh(); |
| 137 | |
| 138 | let cmd = cmd!(sh, "cargo release tag"); |
| 139 | let cmd = append_options(cmd, &execute, &package); |
| 140 | cmd.run()?; |
| 141 | |
| 142 | let cmd = cmd!(sh, "cargo release publish"); |
| 143 | let cmd = append_options(cmd, &execute, &package); |
| 144 | cmd.run()?; |
| 145 | |
| 146 | let cmd = cmd!(sh, "cargo release push"); |
| 147 | let cmd = append_options(cmd, &execute, &package); |
| 148 | cmd.run()?; |
| 149 | |
| 150 | Ok(()) |
| 151 | } |
| 152 | |
| 153 | fn weekly_report() -> Result<()> { |
| 154 | const JSON_FIELDS: &str = "title,number,url,author"; |
| 155 | |
| 156 | let sh = sh(); |
| 157 | |
| 158 | let one_week_ago = cmd!(sh, "date -d '1 week ago' +%Y-%m-%d").read()?; |
| 159 | let today = cmd!(sh, "date +%Y-%m-%d").read()?; |
| 160 | |
| 161 | let _env_pager = sh.push_env("GH_PAGER", ""); |
| 162 | |
| 163 | let header = format!("# This Week in the Matrix Rust SDK ({today})\n\n"); |
| 164 | let template = "{{range .}}- {{.title}} ([#{{.number}}](https://github.com/matrix-org/matrix-rust-sdk/pull/{{.number}})){{\"\\n\\n\"}}{{end}}"; |
| 165 | let template = format!("{header}{template}"); |
| 166 | |
| 167 | cmd!( |
| 168 | sh, |
| 169 | "gh pr list --search merged:>{one_week_ago} --limit 100 --json {JSON_FIELDS} --template {template}" |
| 170 | ) |
| 171 | .quiet() |
| 172 | .run()?; |
| 173 | |
| 174 | Ok(()) |
| 175 | } |