diff --git a/Cargo.lock b/Cargo.lock index 81d2331cf..1358042ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,15 +212,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "autolib" +version = "0.1.0" +dependencies = [ + "anyhow", + "log", + "regex", + "semver", +] + [[package]] name = "automator" version = "0.1.0" dependencies = [ "anyhow", + "autolib", "chrono", "clap", "map-macro", "octocrab", + "semver", "tokio", "url", ] @@ -2766,12 +2778,12 @@ name = "release-operator" version = "0.3.1" dependencies = [ "anyhow", + "autolib", "cargo_metadata", "clap", "cmd_lib", "env_logger", "log", - "regex", "reqwest", "secstr", "semver", diff --git a/Cargo.toml b/Cargo.toml index 7eeb8fabc..5d414451c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "models/star", "models/test", + "tools/autolib", "tools/automator", "tools/export-validator", "tools/release-operator", diff --git a/tools/autolib/Cargo.toml b/tools/autolib/Cargo.toml new file mode 100644 index 000000000..8ee83f38d --- /dev/null +++ b/tools/autolib/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "autolib" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.64" +log = "0.4.17" +regex = "1.6.0" +semver = "1.0.13" diff --git a/tools/autolib/README.md b/tools/autolib/README.md new file mode 100644 index 000000000..9f533caa4 --- /dev/null +++ b/tools/autolib/README.md @@ -0,0 +1,3 @@ +# `autolib` + +Code that is shared between automation tools. diff --git a/tools/autolib/src/lib.rs b/tools/autolib/src/lib.rs new file mode 100644 index 000000000..b236addac --- /dev/null +++ b/tools/autolib/src/lib.rs @@ -0,0 +1,26 @@ +use regex::Regex; + +pub fn find_version_in_str(s: &str) -> anyhow::Result> { + let version = Regex::new(r"(\d+\.\d+\.\d+)")? + .find_iter(s) + .inspect(|version| { + log::info!( + "Found candidate for version in commit message: {}", + version.as_str(), + ); + }) + .filter_map(|m| { + let version = semver::Version::parse(m.as_str()).ok(); + + if version.is_some() { + log::info!("Candidate confirmed."); + } else { + log::info!("Candidate not confirmed."); + } + + version + }) + .next(); + + Ok(version) +} diff --git a/tools/automator/Cargo.toml b/tools/automator/Cargo.toml index e2959db14..41554bba1 100644 --- a/tools/automator/Cargo.toml +++ b/tools/automator/Cargo.toml @@ -9,8 +9,12 @@ anyhow = "1.0.64" chrono = "0.4.22" map-macro = "0.2.4" octocrab = "0.17.0" +semver = "1.0.13" url = "2.2.2" +[dependencies.autolib] +path = "../autolib" + [dependencies.clap] version = "3.2.20" features = ["derive"] diff --git a/tools/automator/src/announcement.rs b/tools/automator/src/announcement.rs index 4a51688c6..9087e00ca 100644 --- a/tools/automator/src/announcement.rs +++ b/tools/automator/src/announcement.rs @@ -8,21 +8,30 @@ use tokio::{ io::AsyncWriteExt, }; -use crate::pull_requests::{Author, PullRequest}; +use crate::pull_requests::{Author, PullRequest, PullRequestsSinceLastRelease}; -pub async fn create_release_announcement( - version: String, -) -> anyhow::Result<()> { +pub async fn create_release_announcement() -> anyhow::Result<()> { let now = Utc::now(); let year = now.year(); let week = now.iso_week().week(); + let pull_requests_since_last_release = + PullRequestsSinceLastRelease::fetch().await?; + let pull_requests = - PullRequest::fetch_since_last_release().await?.into_values(); + pull_requests_since_last_release.pull_requests.into_values(); + + // For now, it's good enough to just release a new minor version every week. + // We could also determine whether there were breaking changes to make sure + // we actually need it, but as of now, breaking changes every week are + // pretty much a given. + let mut version = pull_requests_since_last_release.version_of_last_release; + version.minor += 1; let mut file = create_file(year, week).await?; - generate_announcement(week, version, pull_requests, &mut file).await?; + generate_announcement(week, version.to_string(), pull_requests, &mut file) + .await?; Ok(()) } diff --git a/tools/automator/src/args.rs b/tools/automator/src/args.rs index 360345b24..95a864506 100644 --- a/tools/automator/src/args.rs +++ b/tools/automator/src/args.rs @@ -10,6 +10,4 @@ impl Args { } #[derive(clap::Parser)] -pub struct CreateReleaseAnnouncement { - pub version: String, -} +pub struct CreateReleaseAnnouncement {} diff --git a/tools/automator/src/pull_requests.rs b/tools/automator/src/pull_requests.rs index 4a1cdc43f..5c8177d56 100644 --- a/tools/automator/src/pull_requests.rs +++ b/tools/automator/src/pull_requests.rs @@ -1,26 +1,24 @@ use std::collections::BTreeMap; use anyhow::anyhow; +use autolib::find_version_in_str; use octocrab::{ models::pulls::PullRequest as OctoPullRequest, params::{pulls::Sort, Direction, State}, }; use url::Url; -pub struct PullRequest { - pub number: u64, - pub title: String, - pub url: Url, - pub author: Author, +pub struct PullRequestsSinceLastRelease { + pub pull_requests: BTreeMap, + pub version_of_last_release: semver::Version, } -impl PullRequest { - pub async fn fetch_since_last_release( - ) -> anyhow::Result> { +impl PullRequestsSinceLastRelease { + pub async fn fetch() -> anyhow::Result { let mut pull_requests = BTreeMap::new(); let mut page = 1u32; - 'outer: loop { + let version_of_last_release = 'outer: loop { const MAX_RESULTS_PER_PAGE: u8 = 100; println!("Fetching page {}...", page); @@ -46,7 +44,21 @@ impl PullRequest { // PR. Unless it has been updated since being merged // (which we prevent, by locking release PRs as part // of the release procedure), we can stop here. - break 'outer; + + let title = + pull_request.title.ok_or_else(|| { + anyhow!("Release PR is missing title") + })?; + let version = find_version_in_str(&title)?; + + let version = version.ok_or_else(|| { + anyhow!( + "Pull request title contains no version:\ + {title}" + ) + })?; + + break 'outer version; } } } @@ -62,7 +74,7 @@ impl PullRequest { .ok_or_else(|| anyhow!("Pull request is missing URL"))?; let author = Author::from_pull_request(&pull_request)?; - let pull_request = Self { + let pull_request = PullRequest { number, title, url, @@ -75,14 +87,24 @@ impl PullRequest { if pull_request_page.next.is_some() { page += 1; } else { - break; + return Err(anyhow!("Could not find previous release PR")); } - } + }; - Ok(pull_requests) + Ok(Self { + pull_requests, + version_of_last_release, + }) } } +pub struct PullRequest { + pub number: u64, + pub title: String, + pub url: Url, + pub author: Author, +} + pub struct Author { pub name: String, pub profile: Url, diff --git a/tools/automator/src/run.rs b/tools/automator/src/run.rs index 8feefea20..34c6c1174 100644 --- a/tools/automator/src/run.rs +++ b/tools/automator/src/run.rs @@ -4,8 +4,8 @@ use crate::{announcement::create_release_announcement, args::Args}; pub async fn run() -> anyhow::Result<()> { match Args::parse() { - Args::CreateReleaseAnnouncement(args) => { - create_release_announcement(args.version) + Args::CreateReleaseAnnouncement(_) => { + create_release_announcement() .await .context("Failed to create release announcement")?; } diff --git a/tools/release-operator/Cargo.toml b/tools/release-operator/Cargo.toml index 1f3b6c08b..d2d8454a5 100644 --- a/tools/release-operator/Cargo.toml +++ b/tools/release-operator/Cargo.toml @@ -4,7 +4,6 @@ version = "0.3.1" edition = "2021" publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0.64" @@ -12,12 +11,14 @@ cargo_metadata = "0.15.0" cmd_lib = "1.3.0" env_logger = "0.9.0" log = "0.4.17" -regex = "1.6.0" secstr = "0.5.0" semver = "1.0.13" serde_json = "1.0.85" thiserror = "1.0.34" +[dependencies.autolib] +path = "../autolib" + [dependencies.reqwest] version = "0.11.11" features = ["blocking"] diff --git a/tools/release-operator/src/release.rs b/tools/release-operator/src/release.rs index 5ecda9e05..248189803 100644 --- a/tools/release-operator/src/release.rs +++ b/tools/release-operator/src/release.rs @@ -1,5 +1,6 @@ +use autolib::find_version_in_str; + use crate::{Actions, GitHub}; -use regex::Regex; use std::fmt::{Display, Formatter}; pub struct Release { @@ -43,28 +44,10 @@ impl Release { let commit: String = cmd_lib::run_fun!(git log -n 1 "${sha}")?; // A release commits need to contain a semver version number. - let version = Regex::new(r"(\d+\.\d+\.\d+)")? - .find_iter(&commit) - .inspect(|version| { - log::info!( - "Found candidate for version in commit message: {}", - version.as_str(), - ); - }) - .find(|m| { - let confirmed = semver::Version::parse(m.as_str()).is_ok(); - - if confirmed { - log::info!("Candidate confirmed."); - } else { - log::info!("Candidate not confirmed."); - } - - confirmed - }); + let version = find_version_in_str(&commit)?; match version { - Some(v) => self.hit(v.as_str()), + Some(v) => self.hit(&v.to_string()), None => { log::info!( "Commit message is missing version number:\n\