Unverified Commit 83af6085 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

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

parent fb5e2354
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -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"
+1 −0
Original line number Diff line number Diff line
@@ -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"
+57 −17
Original line number Diff line number Diff line
@@ -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)
==========================
+85 −17
Original line number Diff line number Diff line
@@ -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!(
        "{month} {day}, {year}",
        month = now.date().month(),
        day = Ordinal(now.date().day()),
        year = now.date().year()
    ))
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 release_header_sync_versioned(version: &str) -> Result<String> {
    Ok(format!(
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_header()?
    ))
            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()
    )
}

/// 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);
    }
}