Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand release automation #1046

Merged
merged 13 commits into from
Sep 6, 2022
14 changes: 13 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
"models/star",
"models/test",

"tools/autolib",
"tools/automator",
"tools/export-validator",
"tools/release-operator",
Expand Down
10 changes: 10 additions & 0 deletions tools/autolib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
3 changes: 3 additions & 0 deletions tools/autolib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `autolib`

Code that is shared between automation tools.
26 changes: 26 additions & 0 deletions tools/autolib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use regex::Regex;

pub fn find_version_in_str(s: &str) -> anyhow::Result<Option<semver::Version>> {
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)
}
4 changes: 4 additions & 0 deletions tools/automator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
21 changes: 15 additions & 6 deletions tools/automator/src/announcement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down
4 changes: 1 addition & 3 deletions tools/automator/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,4 @@ impl Args {
}

#[derive(clap::Parser)]
pub struct CreateReleaseAnnouncement {
pub version: String,
}
pub struct CreateReleaseAnnouncement {}
50 changes: 36 additions & 14 deletions tools/automator/src/pull_requests.rs
Original file line number Diff line number Diff line change
@@ -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<u64, PullRequest>,
pub version_of_last_release: semver::Version,
}

impl PullRequest {
pub async fn fetch_since_last_release(
) -> anyhow::Result<BTreeMap<u64, Self>> {
impl PullRequestsSinceLastRelease {
pub async fn fetch() -> anyhow::Result<Self> {
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);
Expand All @@ -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;
}
}
}
Expand All @@ -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,
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions tools/automator/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")?;
}
Expand Down
5 changes: 3 additions & 2 deletions tools/release-operator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ 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"
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"]
Expand Down
25 changes: 4 additions & 21 deletions tools/release-operator/src/release.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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\
Expand Down