From 99a02d7904f9594fd9d7b0a781b58e6c22567707 Mon Sep 17 00:00:00 2001 From: Aria Beingessner Date: Tue, 21 Nov 2023 15:39:30 -0500 Subject: [PATCH] feat: make github release bodies prettier --- cargo-dist-schema/src/lib.rs | 29 ++++ cargo-dist/src/announce.rs | 271 ++++++++++++++++++----------------- cargo-dist/src/host.rs | 35 +++-- 3 files changed, 192 insertions(+), 143 deletions(-) diff --git a/cargo-dist-schema/src/lib.rs b/cargo-dist-schema/src/lib.rs index 907150403..ec35aced2 100644 --- a/cargo-dist-schema/src/lib.rs +++ b/cargo-dist-schema/src/lib.rs @@ -401,6 +401,35 @@ impl DistManifest { self.releases.iter().find(|r| r.app_name == name) } + /// Update the download url for an Axo Release (to a prettier one) + pub fn update_release_axodotdev_artifact_download_url(&mut self, name: &str, new_url: String) { + // Find the release + let release = self.releases.iter_mut().find(|r| r.app_name == name); + let Some(release) = release else { + return; + }; + + // Swap the new URL in + let mut old_url = None; + if let Some(host) = &mut release.hosting.axodotdev { + old_url = host.set_download_url.take(); + host.set_download_url = Some(new_url.clone()); + } + + // If the url changed, update install_hints + if let Some(old_url) = old_url { + for artifact_name in &release.artifacts { + let artifact = self + .artifacts + .get_mut(artifact_name) + .expect("release referenced non-existent artifacts"); + if let Some(hint) = &mut artifact.install_hint { + *hint = hint.replace(&old_url, &new_url); + } + } + } + } + /// Either get the release with the given name, or make a minimal one /// with no hosting/artifacts (to be populated) pub fn ensure_release(&mut self, name: String, version: String) -> &mut Release { diff --git a/cargo-dist/src/announce.rs b/cargo-dist/src/announce.rs index 7cb62e133..15de771cc 100644 --- a/cargo-dist/src/announce.rs +++ b/cargo-dist/src/announce.rs @@ -5,15 +5,14 @@ use axoproject::platforms::triple_to_display_name; use axoproject::PackageIdx; use axotag::{parse_tag, Package, PartialAnnouncementTag, ReleaseType}; +use cargo_dist_schema::DistManifest; use itertools::Itertools; use semver::Version; use tracing::{info, warn}; use crate::{ - backend::installer::{homebrew::HomebrewInstallerInfo, npm::NpmInstallerInfo, InstallerImpl}, - config::CiStyle, errors::{DistError, DistResult}, - ArtifactKind, DistGraphBuilder, SortedMap, + DistGraphBuilder, SortedMap, }; /// details on what we're announcing @@ -88,136 +87,7 @@ impl<'a> DistGraphBuilder<'a> { /// If we're publishing to Github, generate some Github notes fn compute_announcement_github(&mut self) { - use std::fmt::Write; - - if !self.inner.ci_style.contains(&CiStyle::Github) { - info!("not publishing to Github, skipping Github Release Notes"); - return; - } - - let mut gh_body = String::new(); - - // add release notes - if let Some(changelog) = self.manifest.announcement_changelog.as_ref() { - gh_body.push_str("## Release Notes\n\n"); - gh_body.push_str(changelog); - gh_body.push_str("\n\n"); - } - - // Add the contents of each Release to the body - for release in &self.inner.releases { - let heading_suffix = format!("{} {}", release.app_name, release.version); - - // Delineate releases if there's more than 1 - if self.inner.releases.len() > 1 { - writeln!(gh_body, "# {heading_suffix}\n").unwrap(); - } - - // Sort out all the artifacts in this Release - let mut global_installers = vec![]; - let mut local_installers = vec![]; - let mut bundles = vec![]; - let mut symbols = vec![]; - - for &artifact_idx in &release.global_artifacts { - let artifact = self.artifact(artifact_idx); - match &artifact.kind { - ArtifactKind::ExecutableZip(zip) => bundles.push((artifact, zip)), - ArtifactKind::Symbols(syms) => symbols.push((artifact, syms)), - ArtifactKind::Checksum(_) => {} - ArtifactKind::Installer(installer) => { - global_installers.push((artifact, installer)) - } - } - } - - for &variant_idx in &release.variants { - let variant = self.variant(variant_idx); - for &artifact_idx in &variant.local_artifacts { - let artifact = self.artifact(artifact_idx); - match &artifact.kind { - ArtifactKind::ExecutableZip(zip) => bundles.push((artifact, zip)), - ArtifactKind::Symbols(syms) => symbols.push((artifact, syms)), - ArtifactKind::Checksum(_) => {} - ArtifactKind::Installer(installer) => { - local_installers.push((artifact, installer)) - } - } - } - } - - if !global_installers.is_empty() { - writeln!(gh_body, "## Install {heading_suffix}\n").unwrap(); - for (_installer, details) in global_installers { - let info = match details { - InstallerImpl::Shell(info) - | InstallerImpl::Homebrew(HomebrewInstallerInfo { inner: info, .. }) - | InstallerImpl::Powershell(info) - | InstallerImpl::Npm(NpmInstallerInfo { inner: info, .. }) => info, - InstallerImpl::Msi(_) => { - // Should be unreachable, but let's not crash over it - continue; - } - }; - writeln!(&mut gh_body, "### {}\n", info.desc).unwrap(); - writeln!(&mut gh_body, "```sh\n{}\n```\n", info.hint).unwrap(); - } - } - - let other_artifacts: Vec<_> = bundles - .iter() - .map(|i| i.0) - .chain(local_installers.iter().map(|i| i.0)) - .chain(symbols.iter().map(|i| i.0)) - .collect(); - - let download_url = self - .manifest - .release_by_name(&release.app_name) - .and_then(|r| r.artifact_download_url()); - if !other_artifacts.is_empty() && download_url.is_some() { - let download_url = download_url.as_ref().unwrap(); - writeln!(gh_body, "## Download {heading_suffix}\n",).unwrap(); - gh_body.push_str("| File | Platform | Checksum |\n"); - gh_body.push_str("|--------|----------|----------|\n"); - - for artifact in other_artifacts { - let mut targets = String::new(); - let mut multi_target = false; - for target in &artifact.target_triples { - if multi_target { - targets.push_str(", "); - } - targets.push_str(target); - multi_target = true; - } - let name = &artifact.id; - let artifact_download_url = format!("{download_url}/{name}"); - let download = format!("[{name}]({artifact_download_url})"); - let checksum = if let Some(checksum_idx) = artifact.checksum { - let checksum_name = &self.artifact(checksum_idx).id; - let checksum_download_url = format!("{download_url}/{checksum_name}"); - format!("[checksum]({checksum_download_url})") - } else { - String::new() - }; - let mut triple = artifact - .target_triples - .iter() - .filter_map(|t| triple_to_display_name(t)) - .join(", "); - if triple.is_empty() { - triple = "Unknown".to_string(); - } - writeln!(&mut gh_body, "| {download} | {triple} | {checksum} |").unwrap(); - } - writeln!(&mut gh_body).unwrap(); - } - } - - info!("successfully generated github release body!"); - // self.inner.artifact_download_url = Some(download_url); - self.manifest.announcement_github_body = Some(gh_body); + announcement_github(&mut self.manifest); } } @@ -501,3 +371,138 @@ fn tag_help( help } + +/// If we're publishing to Axodotdev, generate the announcement body +pub fn announcement_axodotdev(manifest: &DistManifest) -> String { + // Create a merged announcement body to send, announcement_title should always be set at this point + let title = manifest.announcement_title.clone().unwrap_or_default(); + let body = manifest.announcement_changelog.clone().unwrap_or_default(); + format!("# {title}\n\n{body}") +} + +/// If we're publishing to Github, generate the announcement body +/// +/// Currently mutates the manifest, in the future it should output it +pub fn announcement_github(manifest: &mut DistManifest) { + use std::fmt::Write; + + let mut gh_body = String::new(); + + // add release notes + if let Some(changelog) = manifest.announcement_changelog.as_ref() { + gh_body.push_str("## Release Notes\n\n"); + gh_body.push_str(changelog); + gh_body.push_str("\n\n"); + } + + // Add the contents of each Release to the body + let mut announcing_github = false; + for release in &manifest.releases { + // Only bother if there's actually github hosting + if release.hosting.github.is_none() { + continue; + } + announcing_github = true; + + let heading_suffix = format!("{} {}", release.app_name, release.app_version); + + // Delineate releases if there's more than 1 + if manifest.releases.len() > 1 { + writeln!(gh_body, "# {heading_suffix}\n").unwrap(); + } + + // Sort out all the artifacts in this Release + let mut global_installers = vec![]; + let mut local_installers = vec![]; + let mut bundles = vec![]; + let mut symbols = vec![]; + + for (_name, artifact) in manifest.artifacts_for_release(release) { + match artifact.kind { + cargo_dist_schema::ArtifactKind::ExecutableZip => bundles.push(artifact), + cargo_dist_schema::ArtifactKind::Symbols => symbols.push(artifact), + cargo_dist_schema::ArtifactKind::Installer => { + if let (Some(desc), Some(hint)) = + (&artifact.description, &artifact.install_hint) + { + global_installers.push((desc, hint)); + } else { + local_installers.push(artifact); + } + } + cargo_dist_schema::ArtifactKind::Checksum => { + // Do Nothing (will be included with the artifact it checksums) + } + cargo_dist_schema::ArtifactKind::Unknown => { + // Do nothing + } + _ => { + // Do nothing + } + } + } + + if !global_installers.is_empty() { + writeln!(gh_body, "## Install {heading_suffix}\n").unwrap(); + for (desc, hint) in global_installers { + writeln!(&mut gh_body, "### {}\n", desc).unwrap(); + writeln!(&mut gh_body, "```sh\n{}\n```\n", hint).unwrap(); + } + } + + let other_artifacts: Vec<_> = bundles + .into_iter() + .chain(local_installers) + .chain(symbols) + .collect(); + + let download_url = release.artifact_download_url(); + if !other_artifacts.is_empty() && download_url.is_some() { + let download_url = download_url.as_ref().unwrap(); + writeln!(gh_body, "## Download {heading_suffix}\n",).unwrap(); + gh_body.push_str("| File | Platform | Checksum |\n"); + gh_body.push_str("|--------|----------|----------|\n"); + + for artifact in other_artifacts { + // Artifacts with no name do not exist as files, and should have had install-hints + let Some(name) = &artifact.name else { + continue; + }; + + let mut targets = String::new(); + let mut multi_target = false; + for target in &artifact.target_triples { + if multi_target { + targets.push_str(", "); + } + targets.push_str(target); + multi_target = true; + } + + let artifact_download_url = format!("{download_url}/{name}"); + let download = format!("[{name}]({artifact_download_url})"); + let checksum = if let Some(checksum_name) = &artifact.checksum { + let checksum_download_url = format!("{download_url}/{checksum_name}"); + format!("[checksum]({checksum_download_url})") + } else { + String::new() + }; + let mut triple = artifact + .target_triples + .iter() + .filter_map(|t| triple_to_display_name(t)) + .join(", "); + if triple.is_empty() { + triple = "Unknown".to_string(); + } + writeln!(&mut gh_body, "| {download} | {triple} | {checksum} |").unwrap(); + } + writeln!(&mut gh_body).unwrap(); + } + } + + if announcing_github { + info!("successfully generated github release body!"); + manifest.announcement_github_body = Some(gh_body); + } +} diff --git a/cargo-dist/src/host.rs b/cargo-dist/src/host.rs index ef1380e71..6ccd175ee 100644 --- a/cargo-dist/src/host.rs +++ b/cargo-dist/src/host.rs @@ -1,7 +1,7 @@ //! Details for hosting artifacts use crate::{ - announce::AnnouncementTag, + announce::{announcement_axodotdev, announcement_github, AnnouncementTag}, check_integrity, config::{CiStyle, Config, HostArgs, HostStyle, HostingStyle}, errors::{DistResult, Result}, @@ -23,7 +23,7 @@ pub fn do_host(cfg: &Config, host_args: HostArgs) -> Result { create_hosting: host_args.steps.contains(&HostStyle::Create), ..cfg.clone() }; - let (dist, manifest) = gather_work(&cfg)?; + let (dist, mut manifest) = gather_work(&cfg)?; // The rest of the steps are more self-contained @@ -40,7 +40,7 @@ pub fn do_host(cfg: &Config, host_args: HostArgs) -> Result { upload_to_hosting(&dist, &manifest, &abyss)?; } if host_args.steps.contains(&HostStyle::Release) { - release_hosting(&dist, &manifest, &abyss)?; + release_hosting(&dist, &mut manifest, &abyss)?; } if host_args.steps.contains(&HostStyle::Announce) { announce_hosting(&dist, &manifest, &abyss)?; @@ -189,8 +189,12 @@ fn upload_to_hosting(dist: &DistGraph, manifest: &DistManifest, abyss: &Gazenot) Ok(()) } -fn release_hosting(_dist: &DistGraph, manifest: &DistManifest, abyss: &Gazenot) -> DistResult<()> { - // Perform all the releases +fn release_hosting( + _dist: &DistGraph, + manifest: &mut DistManifest, + abyss: &Gazenot, +) -> DistResult<()> { + // Gather up the releases let releases = manifest.releases.iter().filter_map(|release| { // Github Releases only has semantics on Announce let Hosting { @@ -208,7 +212,21 @@ fn release_hosting(_dist: &DistGraph, manifest: &DistManifest, abyss: &Gazenot) None } }); - tokio::runtime::Handle::current().block_on(abyss.create_releases(releases))?; + + // Tell The Abyss To Release + let new_releases = + tokio::runtime::Handle::current().block_on(abyss.create_releases(releases))?; + + // Update artifact download URLs with release results + for new_release in new_releases { + if let Some(new_url) = new_release.release_download_url { + manifest.update_release_axodotdev_artifact_download_url(&new_release.package, new_url); + } + } + + // Update Github Announcement body with new URLs + announcement_github(manifest); + eprintln!("release published!"); Ok(()) } @@ -230,11 +248,8 @@ fn announce_hosting(_dist: &DistGraph, manifest: &DistManifest, abyss: &Gazenot) }) .collect::>(); - // Create a merged announcement body to send, announcement_title should always be set at this point - let title = manifest.announcement_title.clone().unwrap_or_default(); - let body = manifest.announcement_changelog.clone().unwrap_or_default(); let announcement = AnnouncementKey { - body: format!("# {title}\n\n{body}"), + body: announcement_axodotdev(manifest), }; tokio::runtime::Handle::current() .block_on(abyss.create_announcements(&releases, announcement))?;