diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 1d3d9af8b..1ace0a236 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -38,6 +38,6 @@ jobs: for path in "${added_modified[@]}"; do basenames+=($(basename "${path}")) done - cargo xtask validate-changelog "${basenames[@]}" + cargo xtask changelog validate "${basenames[@]}" fi fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f4dbc779..5c00629a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: - uses: ./.github/actions/setup-rust with: components: clippy - toolchain: 1.64.0 # MSRV, Minimally Supported Rust Version. Make sure to update README.md + toolchain: 1.64.0 # MSRV, Minimally Supported Rust Version. Make sure to update README.md and clippy.toml - name: Run clippy run: cargo clippy --locked --all-targets --all-features --workspace -- -D warnings test: diff --git a/Cargo.lock b/Cargo.lock index f37bf6c10..5ccf440e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,7 +140,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.8", + "syn", ] [[package]] @@ -259,7 +259,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.8", + "syn", ] [[package]] @@ -276,7 +276,7 @@ checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn", ] [[package]] @@ -515,9 +515,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -768,7 +768,7 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn", ] [[package]] @@ -862,17 +862,6 @@ dependencies = [ "is_ci", ] -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.8" @@ -923,7 +912,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn", ] [[package]] @@ -1065,9 +1054,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1075,24 +1064,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1100,22 +1089,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "which" diff --git a/Cargo.toml b/Cargo.toml index 7b61e1bac..442fab05b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,8 @@ ignore = "0.4.20" push = false publish = false tag = false -pre-release-hook = ["cargo", "xtask", "build-changelog"] +consolidate-commits = false +pre-release-hook = ["cargo", "xtask", "changelog", "build", "--release", "{{version}}"] pre-release-commit-message = "release version {{version}}" [[package.metadata.release.pre-release-replacements]] diff --git a/clippy.toml b/clippy.toml index 94044bfd9..00fd94a2b 100644 --- a/clippy.toml +++ b/clippy.toml @@ -2,3 +2,4 @@ disallowed-methods = [ { path = "std::path::Path::display", reason = "incorrect handling of non-Unicode paths, use path.to_utf8() or debug (`{path:?}`) instead" }, ] allow-unwrap-in-tests = true +msrv = "1.64.0" diff --git a/src/docker/image.rs b/src/docker/image.rs index 2a469fe70..cbce5c066 100644 --- a/src/docker/image.rs +++ b/src/docker/image.rs @@ -35,7 +35,7 @@ impl PossibleImage { } } else { let platform = if self.toolchain.len() == 1 { - self.toolchain.get(0).expect("should contain at least one") + self.toolchain.first().expect("should contain at least one") } else { let same_arch = self .toolchain @@ -48,7 +48,7 @@ impl PossibleImage { if same_arch.len() == 1 { // pick the platform with the same architecture - same_arch.get(0).expect("should contain one element") + same_arch.first().expect("should contain one element") } else if let Some(platform) = same_arch .iter() .find(|platform| &platform.os == engine.os.as_ref().unwrap_or(&Os::Linux)) @@ -62,7 +62,7 @@ impl PossibleImage { } else { let platform = self .toolchain - .get(0) + .first() .expect("should be at least one platform"); // FIXME: Don't throw away msg_info.warn( diff --git a/src/docker/shared.rs b/src/docker/shared.rs index b0f19a7b4..5040f8e28 100644 --- a/src/docker/shared.rs +++ b/src/docker/shared.rs @@ -1278,7 +1278,7 @@ pub fn get_image_name( }; Ok(compatible - .get(0) + .first() .expect("should not be empty") .image_name(CROSS_IMAGE, version)) } @@ -1316,7 +1316,7 @@ pub fn get_image( let pick = if compatible.len() == 1 { // If only one match, use that - compatible.get(0).expect("should not be empty") + compatible.first().expect("should not be empty") } else if compatible .iter() .filter(|provided| provided.sub.is_none()) diff --git a/src/errors.rs b/src/errors.rs index dac8e890f..bcb4d3595 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -12,7 +12,7 @@ pub static mut TERMINATED: AtomicBool = AtomicBool::new(false); pub fn install_panic_hook() -> Result<()> { let is_dev = !crate::commit_info().is_empty() || std::env::var("CROSS_DEBUG").is_ok(); color_eyre::config::HookBuilder::new() - .display_env_section(is_dev) + .display_env_section(false) .display_location_section(is_dev) .install() } diff --git a/src/shell.rs b/src/shell.rs index 96498bc37..f3c36b639 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -481,8 +481,9 @@ pub fn default_ident() -> usize { #[must_use] pub fn indent(message: &str, spaces: usize) -> String { - message - .lines() - .map(|s| format!("{:spaces$}{s}", "")) - .collect() + use std::fmt::Write as _; + message.lines().fold(String::new(), |mut string, line| { + let _ = write!(string, "{:spaces$}{line}", ""); + string + }) } diff --git a/xtask/src/build_docker_image.rs b/xtask/src/build_docker_image.rs index 1591e6aba..cf22e3cee 100644 --- a/xtask/src/build_docker_image.rs +++ b/xtask/src/build_docker_image.rs @@ -342,10 +342,10 @@ pub fn build_docker_image( } if results.iter().any(|r| r.is_err()) { #[allow(unknown_lints, clippy::manual_try_fold)] - results - .into_iter() - .filter_map(Result::err) - .fold(Err(eyre::eyre!("encountered error(s)")), |_, e| Err(e.1))?; + return Err(crate::util::with_section_reports( + eyre::eyre!("some error(s) encountered"), + results.into_iter().filter_map(Result::err).map(|e| e.1), + )); } Ok(()) } diff --git a/xtask/src/changelog.rs b/xtask/src/changelog.rs index 036b22617..49c5f15a0 100644 --- a/xtask/src/changelog.rs +++ b/xtask/src/changelog.rs @@ -5,17 +5,33 @@ use std::fs; use std::path::Path; use crate::util::{project_dir, write_to_string}; -use chrono::{Datelike, Utc}; -use clap::Args; use cross::shell::MessageInfo; + +use chrono::{Datelike, Utc}; +use clap::{Args, Subcommand}; use cross::ToUtf8; use eyre::Context; use serde::Deserialize; +pub fn changelog(args: Changelog, msg_info: &mut MessageInfo) -> cross::Result<()> { + match args { + Changelog::Build(args) => build_changelog(args, msg_info), + Changelog::Validate(args) => validate_changelog(args, msg_info), + } +} + +#[derive(Subcommand, Debug)] +pub enum Changelog { + /// Build the changelog. + Build(BuildChangelog), + /// Validate changelog entries. + Validate(ValidateChangelog), +} + #[derive(Args, Debug)] pub struct BuildChangelog { /// Build a release changelog. - #[clap(long, env = "NEW_VERSION")] + #[clap(long, env = "NEW_VERSION", required = true)] release: Option, /// Whether we're doing a dry run or not. #[clap(long, env = "DRY_RUN")] @@ -80,7 +96,7 @@ impl IdType { impl cmp::PartialOrd for IdType { fn partial_cmp(&self, other: &IdType) -> Option { - self.max_number().partial_cmp(&other.max_number()) + Some(self.cmp(other)) } } @@ -125,7 +141,7 @@ impl ChangelogType { impl cmp::PartialOrd for ChangelogType { fn partial_cmp(&self, other: &ChangelogType) -> Option { - self.sort_by().partial_cmp(&other.sort_by()) + Some(self.cmp(other)) } } @@ -155,7 +171,7 @@ impl ChangelogContents { impl cmp::PartialOrd for ChangelogContents { fn partial_cmp(&self, other: &ChangelogContents) -> Option { - self.sort_by().partial_cmp(&other.sort_by()) + Some(self.cmp(other)) } } @@ -238,7 +254,7 @@ impl fmt::Display for ChangelogEntry { prs.iter() .map(|x| x.to_string()) .collect::>() - .join(",") + .join(",#") ))?, IdType::Issue(_) => (), } @@ -485,32 +501,79 @@ pub fn build_changelog( } false => "CHANGELOG.md.draft", }; - write_to_string(&root.join(filename), &output)?; + let path = root.join(filename); + write_to_string(&path, &output)?; + #[allow(clippy::disallowed_methods)] + msg_info.info(format_args!("Changelog written to `{}`", path.display()))?; Ok(()) } +#[allow(clippy::disallowed_methods)] pub fn validate_changelog( - ValidateChangelog { files, .. }: ValidateChangelog, + ValidateChangelog { mut files, .. }: ValidateChangelog, msg_info: &mut MessageInfo, ) -> cross::Result<()> { - msg_info.info("Validating the changelog modifications.")?; - let root = project_dir(msg_info)?; let changes_dir = root.join(".changes"); + if files.is_empty() && std::env::var("GITHUB_ACTIONS").is_err() { + files = fs::read_dir(&changes_dir)? + .filter_map(|x| x.ok()) + .filter(|x| x.file_type().map_or(false, |v| v.is_file())) + .filter_map(|x| { + if x.path() + .extension() + .and_then(|s: &std::ffi::OsStr| s.to_str()) + .unwrap_or_default() + == "json" + { + Some(x.file_name().to_utf8().unwrap().to_owned()) + } else { + None + } + }) + .collect(); + } + let mut errors = vec![]; for file in files { let file_name = Path::new(&file); let path = changes_dir.join(file_name); let stem = file_stem(&path)?; - let contents = - fs::read_to_string(&path).wrap_err_with(|| eyre::eyre!("cannot find file {file}"))?; - let id = IdType::parse_stem(stem)?; - let value = serde_json::from_str(&contents) - .wrap_err_with(|| format!("unable to parse JSON for \"{file}\""))?; - let _ = ChangelogEntry::from_value(id, value) - .wrap_err_with(|| format!("unable to extract changelog from \"{file}\""))?; + let contents = fs::read_to_string(&path) + .wrap_err_with(|| eyre::eyre!("cannot find file {}", path.display()))?; + + let id = match IdType::parse_stem(stem) + .wrap_err_with(|| format!("unable to parse file stem for \"{}\"", path.display())) + { + Ok(id) => id, + Err(e) => { + errors.push(e); + continue; + } + }; + + let value = match serde_json::from_str(&contents) + .wrap_err_with(|| format!("unable to parse JSON for \"{}\"", path.display())) + { + Ok(value) => value, + Err(e) => { + errors.push(e); + continue; + } + }; + + let res = ChangelogEntry::from_value(id, value) + .wrap_err_with(|| format!("unable to extract changelog from \"{}\"", path.display())) + .map(|_| ()); + errors.extend(res.err()); } + if !errors.is_empty() { + return Err(crate::util::with_section_reports( + eyre::eyre!("some files were not validated"), + errors, + )); + } // also need to validate the existing changelog let _ = read_changelog(&root)?; @@ -782,7 +845,7 @@ mod tests { let output = build_changelog_test(None)?; let lines: Vec<&str> = output.lines().collect(); - assert_eq!(lines[10], "- #979,981 - this has 2 PRs associated."); + assert_eq!(lines[10], "- #979,#981 - this has 2 PRs associated."); assert_eq!(lines[11], "- #940 - this is one added entry."); assert_eq!( lines[36], @@ -796,7 +859,7 @@ mod tests { "", "### Added", "", - "- #979,981 - this has 2 PRs associated.", + "- #979,#981 - this has 2 PRs associated.", "- #940 - this is one added entry.", ] ); @@ -816,7 +879,7 @@ mod tests { "", "### Added", "", - "- #979,981 - this has 2 PRs associated.", + "- #979,#981 - this has 2 PRs associated.", "- #940 - this is one added entry.", ] ); @@ -839,7 +902,7 @@ mod tests { "", "### Added", "", - "- #979,981 - this has 2 PRs associated.", + "- #979,#981 - this has 2 PRs associated.", "- #940 - this is one added entry.", ] ); diff --git a/xtask/src/hooks.rs b/xtask/src/hooks.rs index a3acf7ca7..57dcbbe46 100644 --- a/xtask/src/hooks.rs +++ b/xtask/src/hooks.rs @@ -140,7 +140,7 @@ fn python_lint(flake8: Option<&str>, msg_info: &mut MessageInfo) -> cross::Resul .map(parse_command) .unwrap_or_else(|| Ok(vec!["flake8".to_owned()]))?; let mut cmd = Command::new( - args.get(0) + args.first() .ok_or_else(|| eyre::eyre!("empty string provided for flake8 command"))?, ); cmd.args(&args[1..]); @@ -160,7 +160,7 @@ fn python_test(tox: Option<&str>, msg_info: &mut MessageInfo) -> cross::Result<( .map(parse_command) .unwrap_or_else(|| Ok(vec!["tox".to_owned()]))?; let mut cmd = Command::new( - args.get(0) + args.first() .ok_or_else(|| eyre::eyre!("empty string provided for tox command"))?, ); cmd.args(&args[1..]); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index b0da75c7f..d8bbf4f9f 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -18,7 +18,7 @@ use cross::shell::{MessageInfo, Verbosity}; use util::{cargo_metadata, ImageTarget}; use self::build_docker_image::BuildDockerImage; -use self::changelog::{BuildChangelog, ValidateChangelog}; +use self::changelog::Changelog; use self::crosstool::ConfigureCrosstool; use self::hooks::{Check, Test}; use self::install_git_hooks::InstallGitHooks; @@ -67,11 +67,9 @@ enum Commands { CiJob(CiJob), /// Configure crosstool config files. ConfigureCrosstool(ConfigureCrosstool), - /// Build the changelog. - BuildChangelog(BuildChangelog), - /// Validate changelog entries. - #[clap(hide = true)] - ValidateChangelog(ValidateChangelog), + /// Changelog related commands + #[clap(subcommand)] + Changelog(Changelog), /// Code generation Codegen(Codegen), } @@ -120,11 +118,8 @@ pub fn main() -> cross::Result<()> { Commands::ConfigureCrosstool(args) => { crosstool::configure_crosstool(args, &mut msg_info)?; } - Commands::BuildChangelog(args) => { - changelog::build_changelog(args, &mut msg_info)?; - } - Commands::ValidateChangelog(args) => { - changelog::validate_changelog(args, &mut msg_info)?; + Commands::Changelog(args) => { + changelog::changelog(args, &mut msg_info)?; } Commands::Codegen(args) => codegen::codegen(args)?, } diff --git a/xtask/src/util.rs b/xtask/src/util.rs index 146b457e7..64cbe4ae9 100644 --- a/xtask/src/util.rs +++ b/xtask/src/util.rs @@ -118,6 +118,16 @@ pub fn get_matrix() -> &'static Vec { .unwrap() } +pub fn with_section_reports( + origin: eyre::Report, + iter: impl IntoIterator, +) -> eyre::Report { + use color_eyre::{Section as _, SectionExt as _}; + iter.into_iter().fold(origin, |report, e| { + report.section(format!("{e:?}").header("Error:")) + }) +} + pub fn format_repo(registry: &str, repository: &str) -> String { let mut output = String::new(); if !repository.is_empty() { @@ -326,6 +336,32 @@ pub fn read_dockerfiles(msg_info: &mut MessageInfo) -> cross::Result cross::Result<()> { + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(path)?; + writeln!(file, "{}", contents)?; + Ok(()) +} + +// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files +pub fn write_to_gha_env_file(env_name: &str, contents: &str) -> cross::Result<()> { + let path = if let Ok(path) = env::var(env_name) { + PathBuf::from(path) + } else { + eyre::ensure!( + env::var("GITHUB_ACTIONS").is_err(), + "expected GHA envfile to exist" + ); + return Ok(()); + }; + let mut file = fs::OpenOptions::new().append(true).open(path)?; + writeln!(file, "{}", contents)?; + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -406,29 +442,3 @@ mod tests { } } } - -pub fn write_to_string(path: &Path, contents: &str) -> cross::Result<()> { - let mut file = fs::OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(path)?; - writeln!(file, "{}", contents)?; - Ok(()) -} - -// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files -pub fn write_to_gha_env_file(env_name: &str, contents: &str) -> cross::Result<()> { - let path = if let Ok(path) = env::var(env_name) { - PathBuf::from(path) - } else { - eyre::ensure!( - env::var("GITHUB_ACTIONS").is_err(), - "expected GHA envfile to exist" - ); - return Ok(()); - }; - let mut file = fs::OpenOptions::new().append(true).open(path)?; - writeln!(file, "{}", contents)?; - Ok(()) -}