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