Skip to content

Commit dbafd68

Browse files
ysaito1001Zelda Hessler
andauthored
Add crate versions in CHANGELOG (#2348)
* Move rendering external contributors to a function This commit moves the code for rendering external contributors to its own function, in line with other render_* functions. * Add type alias `CrateVersionMetadataMap` This commit adds a type alias `CrateVersionMetadataMap` for a field in `VersionsManifest`. In a subsequent commit, the `changelogger` crate will refer to this field, and it's better for that crate to not use the field's bare type `BTreeMap<String, CrateVersion>`. * Render crate versions in CHANGELOG This commit addresses a pain point brought up in awslabs/aws-sdk-rust#731. A binary `changelogger` now has a new command line option `--current-release-versions-manifest` that points to the latest `versions.toml` in the `aws-sdk-rust` repository. The program will generate a markdown table showing crate versions from that manifest and include it in an expand/collapse section. * Add a leading pipe when rendering markdown table This commit addresses #2348 (comment) --------- Co-authored-by: Yuki Saito <[email protected]> Co-authored-by: Zelda Hessler <[email protected]>
1 parent 5183ccb commit dbafd68

File tree

4 files changed

+366
-59
lines changed

4 files changed

+366
-59
lines changed

tools/ci-build/changelogger/src/main.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ mod tests {
6565
source_to_truncate: PathBuf::from("fromplace"),
6666
changelog_output: PathBuf::from("some-changelog"),
6767
release_manifest_output: Some(PathBuf::from("some-manifest")),
68+
current_release_versions_manifest: None,
6869
previous_release_versions_manifest: None,
6970
date_override: None,
7071
smithy_rs_location: None,
@@ -97,6 +98,7 @@ mod tests {
9798
source_to_truncate: PathBuf::from("fromplace"),
9899
changelog_output: PathBuf::from("some-changelog"),
99100
release_manifest_output: None,
101+
current_release_versions_manifest: None,
100102
previous_release_versions_manifest: None,
101103
date_override: None,
102104
smithy_rs_location: None,
@@ -127,6 +129,7 @@ mod tests {
127129
source_to_truncate: PathBuf::from("fromplace"),
128130
changelog_output: PathBuf::from("some-changelog"),
129131
release_manifest_output: None,
132+
current_release_versions_manifest: None,
130133
previous_release_versions_manifest: Some(PathBuf::from("path/to/versions.toml")),
131134
date_override: None,
132135
smithy_rs_location: None,
@@ -148,5 +151,42 @@ mod tests {
148151
])
149152
.unwrap()
150153
);
154+
155+
assert_eq!(
156+
Args::Render(RenderArgs {
157+
change_set: ChangeSet::AwsSdk,
158+
independent_versioning: true,
159+
source: vec![PathBuf::from("fromplace")],
160+
source_to_truncate: PathBuf::from("fromplace"),
161+
changelog_output: PathBuf::from("some-changelog"),
162+
release_manifest_output: None,
163+
current_release_versions_manifest: Some(PathBuf::from(
164+
"path/to/current/versions.toml"
165+
)),
166+
previous_release_versions_manifest: Some(PathBuf::from(
167+
"path/to/previous/versions.toml"
168+
)),
169+
date_override: None,
170+
smithy_rs_location: None,
171+
}),
172+
Args::try_parse_from([
173+
"./changelogger",
174+
"render",
175+
"--change-set",
176+
"aws-sdk",
177+
"--independent-versioning",
178+
"--source",
179+
"fromplace",
180+
"--source-to-truncate",
181+
"fromplace",
182+
"--changelog-output",
183+
"some-changelog",
184+
"--current-release-versions-manifest",
185+
"path/to/current/versions.toml",
186+
"--previous-release-versions-manifest",
187+
"path/to/previous/versions.toml"
188+
])
189+
.unwrap()
190+
);
151191
}
152192
}

tools/ci-build/changelogger/src/render.rs

Lines changed: 194 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ use smithy_rs_tool_common::changelog::{
1313
Changelog, HandAuthoredEntry, Reference, SdkModelChangeKind, SdkModelEntry,
1414
};
1515
use smithy_rs_tool_common::git::{find_git_repository_root, Git, GitCLI};
16+
use smithy_rs_tool_common::versions_manifest::{CrateVersionMetadataMap, VersionsManifest};
1617
use std::env;
1718
use std::fmt::Write;
18-
use std::path::PathBuf;
19+
use std::path::{Path, PathBuf};
1920
use time::OffsetDateTime;
2021

2122
pub const EXAMPLE_ENTRY: &str = r#"
@@ -67,6 +68,10 @@ pub struct RenderArgs {
6768
/// Optional path to output a release manifest file to
6869
#[clap(long, action)]
6970
pub release_manifest_output: Option<PathBuf>,
71+
/// Optional path to the SDK's versions.toml file for the current release.
72+
/// This is used to generate a markdown table showing crate versions.
73+
#[clap(long, action)]
74+
pub current_release_versions_manifest: Option<PathBuf>,
7075
/// Optional path to the SDK's versions.toml file for the previous release.
7176
/// This is used to filter out changelog entries that have `since_commit` information.
7277
#[clap(long, action)]
@@ -217,6 +222,16 @@ fn indented_message(message: &str) -> String {
217222
out
218223
}
219224

225+
fn render_table_row(columns: [&str; 2], out: &mut String) {
226+
let mut row = "|".to_owned();
227+
for column in columns {
228+
row.push_str(column);
229+
row.push('|');
230+
}
231+
write!(out, "{row}").unwrap();
232+
out.push('\n');
233+
}
234+
220235
fn load_changelogs(args: &RenderArgs) -> Result<Changelog> {
221236
let mut combined = Changelog::new();
222237
for source in &args.source {
@@ -233,6 +248,19 @@ fn load_changelogs(args: &RenderArgs) -> Result<Changelog> {
233248
Ok(combined)
234249
}
235250

251+
fn load_current_crate_version_metadata_map(
252+
current_release_versions_manifest: Option<&Path>,
253+
) -> CrateVersionMetadataMap {
254+
current_release_versions_manifest
255+
.and_then(
256+
|manifest_path| match VersionsManifest::from_file(manifest_path) {
257+
Ok(manifest) => Some(manifest.crates),
258+
Err(_) => None,
259+
},
260+
)
261+
.unwrap_or_default()
262+
}
263+
236264
fn update_changelogs(
237265
args: &RenderArgs,
238266
smithy_rs: &dyn Git,
@@ -250,7 +278,13 @@ fn update_changelogs(
250278
args.change_set,
251279
args.previous_release_versions_manifest.as_deref(),
252280
)?;
253-
let (release_header, release_notes) = render(&entries, &release_metadata.title);
281+
let current_crate_version_metadata_map =
282+
load_current_crate_version_metadata_map(args.current_release_versions_manifest.as_deref());
283+
let (release_header, release_notes) = render(
284+
&entries,
285+
current_crate_version_metadata_map,
286+
&release_metadata.title,
287+
);
254288
if let Some(output_path) = &args.release_manifest_output {
255289
let release_manifest = ReleaseManifest {
256290
tag_name: release_metadata.tag.clone(),
@@ -329,9 +363,94 @@ fn render_sdk_model_entries<'a>(
329363
}
330364
}
331365

332-
/// Convert a list of changelog entries into markdown.
366+
fn render_external_contributors(entries: &[ChangelogEntry], out: &mut String) {
367+
let mut external_contribs = entries
368+
.iter()
369+
.filter_map(|entry| entry.hand_authored().map(|e| &e.author))
370+
.filter(|author| !is_maintainer(author))
371+
.collect::<Vec<_>>();
372+
if external_contribs.is_empty() {
373+
return;
374+
}
375+
external_contribs.sort();
376+
external_contribs.dedup();
377+
out.push_str("**Contributors**\nThank you for your contributions! ❤\n");
378+
for contributor_handle in external_contribs {
379+
// retrieve all contributions this author made
380+
let mut contribution_references = entries
381+
.iter()
382+
.filter(|entry| {
383+
entry
384+
.hand_authored()
385+
.map(|e| e.author.eq_ignore_ascii_case(contributor_handle.as_str()))
386+
.unwrap_or(false)
387+
})
388+
.flat_map(|entry| {
389+
entry
390+
.hand_authored()
391+
.unwrap()
392+
.references
393+
.iter()
394+
.map(to_md_link)
395+
})
396+
.collect::<Vec<_>>();
397+
contribution_references.sort();
398+
contribution_references.dedup();
399+
let contribution_references = contribution_references.as_slice().join(", ");
400+
out.push_str("- @");
401+
out.push_str(contributor_handle);
402+
if !contribution_references.is_empty() {
403+
write!(out, " ({})", contribution_references)
404+
// The `Write` implementation for `String` is infallible,
405+
// see https://doc.rust-lang.org/src/alloc/string.rs.html#2815
406+
.unwrap()
407+
}
408+
out.push('\n');
409+
}
410+
out.push('\n');
411+
}
412+
413+
fn render_details(summary: &str, body: &str, out: &mut String) {
414+
out.push_str("<details>");
415+
out.push('\n');
416+
write!(out, "<summary>{}</summary>", summary).unwrap();
417+
out.push('\n');
418+
// A blank line is required for the body to be rendered properly
419+
out.push('\n');
420+
out.push_str(body);
421+
out.push_str("</details>");
422+
out.push('\n');
423+
}
424+
425+
fn render_crate_versions(crate_version_metadata_map: CrateVersionMetadataMap, out: &mut String) {
426+
if crate_version_metadata_map.is_empty() {
427+
// If the map is empty, we choose to not render anything, as opposed to
428+
// rendering the <details> element with empty contents and a user toggling
429+
// it only to find out there is nothing in it.
430+
return;
431+
}
432+
433+
out.push_str("**Crate Versions**");
434+
out.push('\n');
435+
436+
let mut table = String::new();
437+
render_table_row(["Crate", "Version"], &mut table);
438+
render_table_row(["-", "-"], &mut table);
439+
for (crate_name, version_metadata) in &crate_version_metadata_map {
440+
render_table_row([crate_name, &version_metadata.version], &mut table);
441+
}
442+
443+
render_details("Click to expand to view crate versions...", &table, out);
444+
out.push('\n');
445+
}
446+
447+
/// Convert a list of changelog entries and crate versions into markdown.
333448
/// Returns (header, body)
334-
fn render(entries: &[ChangelogEntry], release_header: &str) -> (String, String) {
449+
fn render(
450+
entries: &[ChangelogEntry],
451+
crate_version_metadata_map: CrateVersionMetadataMap,
452+
release_header: &str,
453+
) -> (String, String) {
335454
let mut header = String::new();
336455
header.push_str(release_header);
337456
header.push('\n');
@@ -349,61 +468,24 @@ fn render(entries: &[ChangelogEntry], release_header: &str) -> (String, String)
349468
entries.iter().filter_map(ChangelogEntry::aws_sdk_model),
350469
&mut out,
351470
);
352-
353-
let mut external_contribs = entries
354-
.iter()
355-
.filter_map(|entry| entry.hand_authored().map(|e| &e.author))
356-
.filter(|author| !is_maintainer(author))
357-
.collect::<Vec<_>>();
358-
external_contribs.sort();
359-
external_contribs.dedup();
360-
if !external_contribs.is_empty() {
361-
out.push_str("**Contributors**\nThank you for your contributions! ❤\n");
362-
for contributor_handle in external_contribs {
363-
// retrieve all contributions this author made
364-
let mut contribution_references = entries
365-
.iter()
366-
.filter(|entry| {
367-
entry
368-
.hand_authored()
369-
.map(|e| e.author.eq_ignore_ascii_case(contributor_handle.as_str()))
370-
.unwrap_or(false)
371-
})
372-
.flat_map(|entry| {
373-
entry
374-
.hand_authored()
375-
.unwrap()
376-
.references
377-
.iter()
378-
.map(to_md_link)
379-
})
380-
.collect::<Vec<_>>();
381-
contribution_references.sort();
382-
contribution_references.dedup();
383-
let contribution_references = contribution_references.as_slice().join(", ");
384-
out.push_str("- @");
385-
out.push_str(contributor_handle);
386-
if !contribution_references.is_empty() {
387-
write!(&mut out, " ({})", contribution_references)
388-
// The `Write` implementation for `String` is infallible,
389-
// see https://doc.rust-lang.org/src/alloc/string.rs.html#2815
390-
.unwrap()
391-
}
392-
out.push('\n');
393-
}
394-
}
471+
render_external_contributors(entries, &mut out);
472+
render_crate_versions(crate_version_metadata_map, &mut out);
395473

396474
(header, out)
397475
}
398476

399477
#[cfg(test)]
400478
mod test {
401479
use super::{date_based_release_metadata, render, Changelog, ChangelogEntries, ChangelogEntry};
402-
use smithy_rs_tool_common::changelog::SdkAffected;
480+
use smithy_rs_tool_common::{
481+
changelog::SdkAffected,
482+
package::PackageCategory,
483+
versions_manifest::{CrateVersion, CrateVersionMetadataMap},
484+
};
403485
use time::OffsetDateTime;
404486

405487
fn render_full(entries: &[ChangelogEntry], release_header: &str) -> String {
406-
let (header, body) = render(entries, release_header);
488+
let (header, body) = render(entries, CrateVersionMetadataMap::new(), release_header);
407489
format!("{header}{body}")
408490
}
409491

@@ -494,6 +576,7 @@ v0.3.0 (January 4th, 2022)
494576
Thank you for your contributions! ❤
495577
- @another-contrib ([smithy-rs#200](https://github.com/awslabs/smithy-rs/issues/200))
496578
- @external-contrib ([smithy-rs#446](https://github.com/awslabs/smithy-rs/issues/446))
579+
497580
"#
498581
.trim_start();
499582
pretty_assertions::assert_str_eq!(smithy_rs_expected, smithy_rs_rendered);
@@ -518,6 +601,7 @@ v0.1.0 (January 4th, 2022)
518601
**Contributors**
519602
Thank you for your contributions! ❤
520603
- @external-contrib ([smithy-rs#446](https://github.com/awslabs/smithy-rs/issues/446))
604+
521605
"#
522606
.trim_start();
523607
pretty_assertions::assert_str_eq!(aws_sdk_expected, aws_sdk_rust_rendered);
@@ -592,9 +676,69 @@ author = "rcoh"
592676
#[test]
593677
fn test_empty_render() {
594678
let smithy_rs = Vec::<ChangelogEntry>::new();
595-
let (release_title, release_notes) = render(&smithy_rs, "some header");
679+
let (release_title, release_notes) =
680+
render(&smithy_rs, CrateVersionMetadataMap::new(), "some header");
596681

597682
assert_eq!(release_title, "some header\n===========\n");
598683
assert_eq!(release_notes, "");
599684
}
685+
686+
#[test]
687+
fn test_crate_versions() {
688+
let mut crate_version_metadata_map = CrateVersionMetadataMap::new();
689+
crate_version_metadata_map.insert(
690+
"aws-config".to_owned(),
691+
CrateVersion {
692+
category: PackageCategory::AwsRuntime,
693+
version: "0.54.1".to_owned(),
694+
source_hash: "e93380cfbd05e68d39801cbf0113737ede552a5eceb28f4c34b090048d539df9"
695+
.to_owned(),
696+
model_hash: None,
697+
},
698+
);
699+
crate_version_metadata_map.insert(
700+
"aws-sdk-accessanalyzer".to_owned(),
701+
CrateVersion {
702+
category: PackageCategory::AwsSdk,
703+
version: "0.24.0".to_owned(),
704+
source_hash: "a7728756b41b33d02f68a5865d3456802b7bc3949ec089790bc4e726c0de8539"
705+
.to_owned(),
706+
model_hash: Some(
707+
"71f1f130504ebd55396c3166d9441513f97e49b281a5dd420fd7e2429860b41b".to_owned(),
708+
),
709+
},
710+
);
711+
crate_version_metadata_map.insert(
712+
"aws-smithy-async".to_owned(),
713+
CrateVersion {
714+
category: PackageCategory::SmithyRuntime,
715+
version: "0.54.1".to_owned(),
716+
source_hash: "8ced52afc783cbb0df47ee8b55260b98e9febdc95edd796ed14c43db5199b0a9"
717+
.to_owned(),
718+
model_hash: None,
719+
},
720+
);
721+
let (release_title, release_notes) = render(
722+
&Vec::<ChangelogEntry>::new(),
723+
crate_version_metadata_map,
724+
"some header",
725+
);
726+
727+
assert_eq!(release_title, "some header\n===========\n");
728+
let expected_body = r#"
729+
**Crate Versions**
730+
<details>
731+
<summary>Click to expand to view crate versions...</summary>
732+
733+
|Crate|Version|
734+
|-|-|
735+
|aws-config|0.54.1|
736+
|aws-sdk-accessanalyzer|0.24.0|
737+
|aws-smithy-async|0.54.1|
738+
</details>
739+
740+
"#
741+
.trim_start();
742+
pretty_assertions::assert_str_eq!(release_notes, expected_body);
743+
}
600744
}

0 commit comments

Comments
 (0)