From 83af60855f8c3b902cb596e2d2e7f89f638ec8b2 Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Wed, 11 May 2022 15:20:27 -0700 Subject: [PATCH] Add option to create a release manifest to changelog tool (#1384) --- tools/sdk-lints/Cargo.lock | 24 ++++++++ tools/sdk-lints/Cargo.toml | 1 + tools/sdk-lints/src/changelog.rs | 74 ++++++++++++++++------ tools/sdk-lints/src/main.rs | 102 +++++++++++++++++++++++++------ 4 files changed, 167 insertions(+), 34 deletions(-) diff --git a/tools/sdk-lints/Cargo.lock b/tools/sdk-lints/Cargo.lock index 0d94c5ae5..7908b2e24 100644 --- a/tools/sdk-lints/Cargo.lock +++ b/tools/sdk-lints/Cargo.lock @@ -137,6 +137,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "lazy_static" version = "1.4.0" @@ -255,6 +261,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + [[package]] name = "sdk-lints" version = "0.1.0" @@ -266,6 +278,7 @@ dependencies = [ "ordinal", "pretty_assertions", "serde", + "serde_json", "time", "toml", ] @@ -290,6 +303,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "strsim" version = "0.10.0" diff --git a/tools/sdk-lints/Cargo.toml b/tools/sdk-lints/Cargo.toml index 66db55577..f3763d258 100644 --- a/tools/sdk-lints/Cargo.toml +++ b/tools/sdk-lints/Cargo.toml @@ -16,6 +16,7 @@ cargo_toml = "0.10.1" clap = { version = "3.1.7", features = ["derive"]} toml = "0.5.8" serde = { version = "1", features = ["derive"]} +serde_json = "1" lazy_static = "1.4.0" time = { version = "0.3.9", features = ["local-offset"]} ordinal = "0.3.2" diff --git a/tools/sdk-lints/src/changelog.rs b/tools/sdk-lints/src/changelog.rs index b5fa60361..67e996c2e 100644 --- a/tools/sdk-lints/src/changelog.rs +++ b/tools/sdk-lints/src/changelog.rs @@ -6,7 +6,7 @@ use crate::lint::LintError; use crate::{repo_root, Check, Lint}; use anyhow::{bail, Context, Result}; -use serde::{de, Deserialize, Deserializer}; +use serde::{de, Deserialize, Deserializer, Serialize}; use std::fmt::Write; use std::path::{Path, PathBuf}; use std::process::Command; @@ -282,12 +282,28 @@ fn no_uncommited_changes(path: &Path) -> Result<()> { Ok(()) } +pub struct ReleaseMetadata { + pub title: String, + pub tag: String, + pub manifest_name: String, +} + +#[derive(Serialize)] +struct ReleaseManifest { + #[serde(rename = "tagName")] + tag_name: String, + name: String, + body: String, + prerelease: bool, +} + pub(crate) fn update_changelogs( changelog_next: impl AsRef, smithy_rs_path: impl AsRef, aws_sdk_rust_path: impl AsRef, - smithy_rs_release_header: &str, - aws_sdk_rust_release_header: &str, + smithy_rs_metadata: &ReleaseMetadata, + aws_sdk_rust_metadata: &ReleaseMetadata, + release_manifest_output_path: Option<&Path>, ) -> Result<()> { no_uncommited_changes(changelog_next.as_ref()).context( "CHANGELOG.next.toml had unstaged changes. Refusing to perform changelog update.", @@ -302,19 +318,35 @@ pub(crate) fn update_changelogs( smithy_rs, aws_sdk_rust, } = changelog.into_entries(); - for (entries, path, release_header) in [ - (smithy_rs, smithy_rs_path.as_ref(), smithy_rs_release_header), + for (entries, path, release_metadata) in [ + (smithy_rs, smithy_rs_path.as_ref(), smithy_rs_metadata), ( aws_sdk_rust, aws_sdk_rust_path.as_ref(), - aws_sdk_rust_release_header, + aws_sdk_rust_metadata, ), ] { no_uncommited_changes(path) .with_context(|| format!("{} had unstaged changes", path.display()))?; + let (release_header, release_notes) = render(&entries, &release_metadata.title); + if let Some(output_path) = release_manifest_output_path { + let release_manifest = ReleaseManifest { + tag_name: release_metadata.tag.clone(), + name: release_metadata.title.clone(), + body: release_notes.clone(), + // All releases are pre-releases for now + prerelease: true, + }; + std::fs::write( + output_path.join(&release_metadata.manifest_name), + serde_json::to_string_pretty(&release_manifest)?, + )?; + } + let mut update = USE_UPDATE_CHANGELOGS.to_string(); update.push('\n'); - update.push_str(&render(&entries, release_header)); + update.push_str(&release_header); + update.push_str(&release_notes); let current = std::fs::read_to_string(path)?.replace(USE_UPDATE_CHANGELOGS, ""); update.push_str(¤t); std::fs::write(path, update)?; @@ -370,16 +402,18 @@ fn render_sdk_model_entries<'a>( } } -/// Convert a list of changelog entries into markdown -fn render(entries: &[ChangelogEntry], release_header: &str) -> String { - let mut out = String::new(); - out.push_str(release_header); - out.push('\n'); +/// Convert a list of changelog entries into markdown. +/// Returns (header, body) +fn render(entries: &[ChangelogEntry], release_header: &str) -> (String, String) { + let mut header = String::new(); + header.push_str(release_header); + header.push('\n'); for _ in 0..release_header.len() { - out.push('='); + header.push('='); } - out.push('\n'); + header.push('\n'); + let mut out = String::new(); render_handauthored( entries.iter().filter_map(ChangelogEntry::hand_authored), &mut out, @@ -429,7 +463,7 @@ fn render(entries: &[ChangelogEntry], release_header: &str) -> String { } } - out + (header, out) } pub(crate) struct ChangelogNext; @@ -475,8 +509,14 @@ fn check_changelog_next(path: impl AsRef) -> std::result::Result String { + let (header, body) = render(entries, release_header); + return format!("{}{}", header, body); + } + #[test] fn end_to_end_changelog() { let changelog_toml = r#" @@ -544,7 +584,7 @@ message = "Some API change" smithy_rs, } = changelog.into_entries(); - let smithy_rs_rendered = render(&smithy_rs, "v0.3.0 (January 4th, 2022)"); + let smithy_rs_rendered = render_full(&smithy_rs, "v0.3.0 (January 4th, 2022)"); let smithy_rs_expected = r#" v0.3.0 (January 4th, 2022) ========================== @@ -567,7 +607,7 @@ Thank you for your contributions! ❤ .trim_start(); pretty_assertions::assert_str_eq!(smithy_rs_expected, smithy_rs_rendered); - let aws_sdk_rust_rendered = render(&aws_sdk_rust, "v0.1.0 (January 4th, 2022)"); + let aws_sdk_rust_rendered = render_full(&aws_sdk_rust, "v0.1.0 (January 4th, 2022)"); let aws_sdk_expected = r#" v0.1.0 (January 4th, 2022) ========================== diff --git a/tools/sdk-lints/src/main.rs b/tools/sdk-lints/src/main.rs index eb71aae41..b7481f5e7 100644 --- a/tools/sdk-lints/src/main.rs +++ b/tools/sdk-lints/src/main.rs @@ -10,6 +10,7 @@ use crate::lint_cargo_toml::{CrateAuthor, CrateLicense, DocsRs}; use crate::readmes::{ReadmesExist, ReadmesHaveFooters}; use crate::todos::TodosHaveContext; use anyhow::{bail, Context, Result}; +use changelog::ReleaseMetadata; use clap::Parser; use lazy_static::lazy_static; use ordinal::Ordinal; @@ -68,6 +69,9 @@ enum Args { /// Whether or not independent crate versions are being used (defaults to false) #[clap(long)] independent_versioning: bool, + /// Optional path to output a release manifest file to + #[clap(long)] + release_manifest_output_path: Option, }, } @@ -166,27 +170,44 @@ fn main() -> Result<()> { } Args::UpdateChangelog { independent_versioning, + release_manifest_output_path, } => { + let now = OffsetDateTime::now_local()?; let changelog_next_path = repo_root().join("CHANGELOG.next.toml"); let changelog_path = repo_root().join("CHANGELOG.md"); let aws_changelog_path = repo_root().join("aws/SDK_CHANGELOG.md"); if independent_versioning { - let header = date_header()?; + let smithy_rs_metadata = + date_based_release_metadata(now, "smithy-rs-release-manifest.json"); + let sdk_metadata = + date_based_release_metadata(now, "aws-sdk-rust-release-manifest.json"); changelog::update_changelogs( changelog_next_path, changelog_path, aws_changelog_path, - &header, - &header, + &smithy_rs_metadata, + &sdk_metadata, + release_manifest_output_path.as_deref(), )? } else { let auto = auto_changelog_meta()?; + let smithy_rs_metadata = version_based_release_metadata( + now, + &auto.smithy_version, + "smithy-rs-release-manifest.json", + ); + let sdk_metadata = version_based_release_metadata( + now, + &auto.sdk_version, + "aws-sdk-rust-release-manifest.json", + ); changelog::update_changelogs( changelog_next_path, changelog_path, aws_changelog_path, - &release_header_sync_versioned(&auto.smithy_version)?, - &release_header_sync_versioned(&auto.sdk_version)?, + &smithy_rs_metadata, + &sdk_metadata, + release_manifest_output_path.as_deref(), )? } } @@ -199,22 +220,45 @@ struct ChangelogMeta { sdk_version: String, } -fn date_header() -> Result { - let now = OffsetDateTime::now_local()?; - Ok(format!( +fn date_based_release_metadata( + now: OffsetDateTime, + manifest_name: impl Into, +) -> ReleaseMetadata { + ReleaseMetadata { + title: date_title(&now), + tag: format!( + "release-{year}-{month:02}-{day:02}", + year = now.date().year(), + month = u8::from(now.date().month()), + day = now.date().day() + ), + manifest_name: manifest_name.into(), + } +} + +fn version_based_release_metadata( + now: OffsetDateTime, + version: &str, + manifest_name: impl Into, +) -> ReleaseMetadata { + ReleaseMetadata { + title: format!( + "v{version} ({date})", + version = version, + date = date_title(&now) + ), + tag: format!("v{version}", version = version), + manifest_name: manifest_name.into(), + } +} + +fn date_title(now: &OffsetDateTime) -> String { + format!( "{month} {day}, {year}", month = now.date().month(), day = Ordinal(now.date().day()), year = now.date().year() - )) -} - -fn release_header_sync_versioned(version: &str) -> Result { - Ok(format!( - "v{version} ({date})", - version = version, - date = date_header()? - )) + ) } /// Discover the new version for the changelog from gradle.properties and the date. @@ -266,3 +310,27 @@ fn all_runtime_crates() -> Result> { fn all_cargo_tomls() -> Result> { Ok(all_runtime_crates()?.map(|pkg| pkg.join("Cargo.toml"))) } + +#[cfg(test)] +mod tests { + use crate::{date_based_release_metadata, version_based_release_metadata}; + use time::OffsetDateTime; + + #[test] + fn test_date_based_release_metadata() { + let now = OffsetDateTime::from_unix_timestamp(100_000_000).unwrap(); + let result = date_based_release_metadata(now, "some-manifest.json"); + assert_eq!("March 3rd, 1973", result.title); + assert_eq!("release-1973-03-03", result.tag); + assert_eq!("some-manifest.json", result.manifest_name); + } + + #[test] + fn test_version_based_release_metadata() { + let now = OffsetDateTime::from_unix_timestamp(100_000_000).unwrap(); + let result = version_based_release_metadata(now, "0.11.0", "some-other-manifest.json"); + assert_eq!("v0.11.0 (March 3rd, 1973)", result.title); + assert_eq!("v0.11.0", result.tag); + assert_eq!("some-other-manifest.json", result.manifest_name); + } +} -- GitLab