Add option to create a release manifest to changelog tool (#1384)

This commit is contained in:
John DiSanti 2022-05-11 15:20:27 -07:00 committed by GitHub
parent fb5e235446
commit 83af60855f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 167 additions and 34 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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<Path>,
smithy_rs_path: impl AsRef<Path>,
aws_sdk_rust_path: impl AsRef<Path>,
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(&current);
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<Path>) -> std::result::Result<Changelog
#[cfg(test)]
mod test {
use super::ChangelogEntry;
use crate::changelog::{render, Changelog, ChangelogEntries};
fn render_full(entries: &[ChangelogEntry], release_header: &str) -> 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)
==========================

View File

@ -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<PathBuf>,
},
}
@ -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<String> {
let now = OffsetDateTime::now_local()?;
Ok(format!(
fn date_based_release_metadata(
now: OffsetDateTime,
manifest_name: impl Into<String>,
) -> 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<String>,
) -> 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<String> {
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<impl Iterator<Item = PathBuf>> {
fn all_cargo_tomls() -> Result<impl Iterator<Item = PathBuf>> {
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);
}
}