From a80a8803f08d59c2aedd095861f4193ce17d72b0 Mon Sep 17 00:00:00 2001 From: puzzlewolf <23097564+puzzlewolf@users.noreply.github.com> Date: Mon, 8 Aug 2022 17:29:03 +0200 Subject: [PATCH] Add option to continue if an Error occurs in one section (#128) * Collect and emit cargo:warnings. * Optionally continue if Build block failed. * Optionally continue if Git block failed. * Optionally continue if Sysinfo block failed. * Optionally continue if Rustc block failed. * Add test for Git skip_if_error - This test needs a directory that is not a git repository or a subdirectory of a git repository. - Use a temporary directory, provided by the tempfile crate. - The tempfile dependency is only needed for this one test. It is only compiled in the test configuration and if the git feature is active. Sadly, there is no way to define optional dev-dependencies[0], so deactivate the unused_crate_dependencies lint to allow compilation of the other features. [0]: https://github.com/rust-lang/cargo/issues/1596 * Add docs for skip_if_error. * Fix typos * Fix mistake in docs. If the "git" feature is not active, no git2 Errors will be generated. --- Cargo.toml | 1 + src/config.rs | 6 + src/feature/build.rs | 30 ++++- src/feature/git.rs | 255 ++++++++++++++++++++++++------------------- src/feature/rustc.rs | 31 +++++- src/feature/si.rs | 32 +++++- src/gen.rs | 37 ++++++- src/lib.rs | 1 - 8 files changed, 273 insertions(+), 120 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c10e8f95..427dc98b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,4 +44,5 @@ rustversion = "1" lazy_static = "1" regex = "1" serial_test = "0" +tempfile = "3" diff --git a/src/config.rs b/src/config.rs index d28c7d55..c0ebaedc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -283,6 +283,7 @@ pub(crate) struct Config { cfg_map: BTreeMap>, head_path: Option, ref_path: Option, + warnings: Vec, } impl Default for Config { @@ -296,6 +297,7 @@ impl Default for Config { .collect(), head_path: Option::default(), ref_path: Option::default(), + warnings: Vec::new(), } } } @@ -319,6 +321,7 @@ mod test { assert_eq!(*config.timezone(), TimeZone::Utc); assert_eq!(*config.kind(), TimestampKind::Timestamp); assert!(config.semver()); + assert!(!config.skip_if_error()); } #[cfg(not(feature = "build"))] @@ -353,6 +356,7 @@ mod test { assert_eq!(*config.semver_kind(), SemverKind::Normal); assert!(config.sha()); assert_eq!(*config.sha_kind(), ShaKind::Normal); + assert!(!config.skip_if_error()); } #[cfg(not(feature = "git"))] @@ -367,6 +371,7 @@ mod test { assert!(config.host_triple()); assert!(config.llvm_version()); assert!(config.sha()); + assert!(!config.skip_if_error()); } #[cfg(not(feature = "rustc"))] @@ -382,6 +387,7 @@ mod test { assert!(config.memory()); assert!(config.cpu_vendor()); assert!(config.cpu_core_count()); + assert!(!config.skip_if_error()); } #[cfg(not(feature = "si"))] diff --git a/src/feature/build.rs b/src/feature/build.rs index e52f11e8..72b46753 100644 --- a/src/feature/build.rs +++ b/src/feature/build.rs @@ -37,6 +37,8 @@ use { /// * If the `semver` field is false, the semver instruction will not be generated. /// * **NOTE** - By default, the date/time related instructions will use [`UTC`](TimeZone::Utc). /// * **NOTE** - The date/time instruction output is determined by the [`kind`](TimestampKind) field and can be any combination of the three. +/// * **NOTE** - To keep processing other sections if an Error occurs in this one, set +/// [`Build::skip_if_error`](Build::skip_if_error_mut()) to true. /// /// # Example /// @@ -89,6 +91,9 @@ pub struct Build { kind: TimestampKind, /// Enable/Disable the `VERGEN_BUILD_SEMVER` instruction. semver: bool, + /// Enable/Disable skipping [`Build`] if an Error occurs. + /// Use [`option_env!`](std::option_env!) to read the generated environment variables. + skip_if_error: bool, } #[cfg(feature = "build")] @@ -100,6 +105,7 @@ impl Default for Build { timezone: TimeZone::Utc, kind: TimestampKind::Timestamp, semver: true, + skip_if_error: false, } } } @@ -115,7 +121,7 @@ impl Build { pub(crate) fn configure_build(instructions: &Instructions, config: &mut Config) -> Result<()> { let build_config = instructions.build(); - if build_config.has_enabled() { + let mut add_entries = || { if *build_config.timestamp() { match build_config.timezone() { TimeZone::Utc => { @@ -135,8 +141,28 @@ pub(crate) fn configure_build(instructions: &Instructions, config: &mut Config) env::var("CARGO_PKG_VERSION").ok(), ); } + Ok(()) + }; + + if build_config.has_enabled() { + if build_config.skip_if_error { + // hide errors, but emit a warning + let result = add_entries(); + if result.is_err() { + let warning = format!( + "An Error occurred during processing of {}. \ + VERGEN_{}_* may be incomplete.", + "Build", "BUILD" + ); + config.warnings_mut().push(warning); + } + Ok(()) + } else { + add_entries() + } + } else { + Ok(()) } - Ok(()) } #[cfg(feature = "build")] diff --git a/src/feature/git.rs b/src/feature/git.rs index 3c57c1ad..f05857f0 100644 --- a/src/feature/git.rs +++ b/src/feature/git.rs @@ -73,7 +73,7 @@ pub enum ShaKind { /// * If the `commit_timestamp` field is false, the date/time instructions will not be generated. /// * If the `rerun_on_head_changed` field is false, the `cargo:rerun-if-changed` instructions will not be generated. /// * If the `semver` field is false, the `VERGEN_GIT_SEMVER` instruction will not be generated. -/// * If the `sha` field is fale, the `VERGEN_GIT_SHA` instruction will not be generated. +/// * If the `sha` field is false, the `VERGEN_GIT_SHA` instruction will not be generated. /// * **NOTE** - The SHA defaults to the [`Normal`](ShaKind::Normal) variant, but can be changed via the `sha_kind` field. /// * **NOTE** - The [SemVer] defaults to the [`Normal`](SemverKind::Normal) variant, but can be changed via the `semver_kind` field. /// * **NOTE** - The [SemVer] is only useful if you have tags on your repository. If your repository has no tags, this will default to [`CARGO_PKG_VERSION`]. @@ -81,7 +81,11 @@ pub enum ShaKind { /// * **NOTE** - The [`Lightweight`](SemverKind::Lightweight) variant will only differ from the [`Normal`](SemverKind::Normal) variant if you use [lightweight] tags in your repository. /// * **NOTE** - By default, the date/time related instructions will use [`UTC`](crate::TimeZone::Utc). /// * **NOTE** - The date/time instruction output is determined by the [`kind`](crate::TimestampKind) field and can be any combination of the three. -/// * **NOTE** - If the `rerun_on_head_chaged` instructions are enabled, cargo` will re-run the build script when either `<gitpath>/HEAD` or the file that `<gitpath>/HEAD` points at changes. +/// * **NOTE** - If the `rerun_on_head_chaged` instructions are enabled, cargo will re-run the build script when either `/HEAD` or the file that `/HEAD` points at changes. +/// * **NOTE** - Even if a project is developed in a git repository, the repository is not always +/// present with the source code, e.g. in a source tarball. By default, +/// [`vergen`](crate::vergen) returns an Error in this case. To keep processing other +/// sections, set [`Git::skip_if_error`](Git::skip_if_error_mut()) to true. /// /// # Example /// @@ -164,6 +168,10 @@ pub struct Git { /// The kind of SHA instruction to output. #[getset(get = "pub(crate)")] sha_kind: ShaKind, + /// Enable/Disable skipping [`Git`] if an Error occurs. + /// Use [`option_env!`](std::option_env!) to read the generated environment variables. + #[getset(get = "pub(crate)")] + skip_if_error: bool, } #[cfg(feature = "git")] @@ -190,6 +198,7 @@ impl Default for Git { semver_dirty: None, sha: true, sha_kind: ShaKind::Normal, + skip_if_error: false, } } } @@ -231,134 +240,158 @@ pub(crate) fn configure_git( where T: AsRef, { - if let Some(repo_path) = repo_path_opt { - let git_config = instructions.git(); - if git_config.has_enabled() { - let repo = Repository::discover(repo_path)?; - let ref_head = repo.find_reference("HEAD")?; - let repo_path = repo.path().to_path_buf(); - - if *git_config.branch() { - add_branch_name(&repo, config)?; - } + let repo_path = match repo_path_opt { + Some(repo_path) => repo_path, + None => return Ok(()), + }; + + let git_config = instructions.git(); + + let add_entries = || { + let repo = Repository::discover(repo_path)?; + let ref_head = repo.find_reference("HEAD")?; + let repo_path = repo.path().to_path_buf(); - if *git_config.commit_timestamp() - || *git_config.sha() - || *git_config.commit_author() - || *git_config.commit_message() - { - let commit = ref_head.peel_to_commit()?; - - if *git_config.commit_timestamp() { - let commit_time = OffsetDateTime::from_unix_timestamp(commit.time().seconds())?; - - match git_config.commit_timestamp_timezone() { - crate::TimeZone::Utc => { - add_config_entries( - config, - git_config, - &commit_time.to_offset(UtcOffset::UTC), - )?; - } - #[cfg(feature = "local_offset")] - crate::TimeZone::Local => { - add_config_entries( - config, - git_config, - &commit_time.to_offset(UtcOffset::current_local_offset()?), - )?; - } + if *git_config.branch() { + add_branch_name(&repo, config)?; + } + + if *git_config.commit_timestamp() + || *git_config.sha() + || *git_config.commit_author() + || *git_config.commit_message() + { + let commit = ref_head.peel_to_commit()?; + + if *git_config.commit_timestamp() { + let commit_time = OffsetDateTime::from_unix_timestamp(commit.time().seconds())?; + + match git_config.commit_timestamp_timezone() { + crate::TimeZone::Utc => { + add_config_entries( + config, + git_config, + &commit_time.to_offset(UtcOffset::UTC), + )?; + } + #[cfg(feature = "local_offset")] + crate::TimeZone::Local => { + add_config_entries( + config, + git_config, + &commit_time.to_offset(UtcOffset::current_local_offset()?), + )?; } } + } - if *git_config.sha() { - match git_config.sha_kind() { - crate::ShaKind::Normal => { - add_entry( - config.cfg_map_mut(), - VergenKey::Sha, - Some(commit.id().to_string()), - ); - } - crate::ShaKind::Short => { - let obj = repo.revparse_single("HEAD")?; - add_entry( - config.cfg_map_mut(), - VergenKey::ShortSha, - obj.short_id()?.as_str().map(str::to_string), - ); - } - crate::ShaKind::Both => { - add_entry( - config.cfg_map_mut(), - VergenKey::Sha, - Some(commit.id().to_string()), - ); - - let obj = repo.revparse_single("HEAD")?; - add_entry( - config.cfg_map_mut(), - VergenKey::ShortSha, - obj.short_id()?.as_str().map(str::to_string), - ); - } + if *git_config.sha() { + match git_config.sha_kind() { + crate::ShaKind::Normal => { + add_entry( + config.cfg_map_mut(), + VergenKey::Sha, + Some(commit.id().to_string()), + ); + } + crate::ShaKind::Short => { + let obj = repo.revparse_single("HEAD")?; + add_entry( + config.cfg_map_mut(), + VergenKey::ShortSha, + obj.short_id()?.as_str().map(str::to_string), + ); + } + crate::ShaKind::Both => { + add_entry( + config.cfg_map_mut(), + VergenKey::Sha, + Some(commit.id().to_string()), + ); + + let obj = repo.revparse_single("HEAD")?; + add_entry( + config.cfg_map_mut(), + VergenKey::ShortSha, + obj.short_id()?.as_str().map(str::to_string), + ); } } + } - if *git_config.commit_author() { - add_entry( - config.cfg_map_mut(), - VergenKey::CommitAuthorName, - commit.author().name().map(&str::to_string), - ); - add_entry( - config.cfg_map_mut(), - VergenKey::CommitAuthorEmail, - commit.author().email().map(&str::to_string), - ); - } + if *git_config.commit_author() { + add_entry( + config.cfg_map_mut(), + VergenKey::CommitAuthorName, + commit.author().name().map(&str::to_string), + ); + add_entry( + config.cfg_map_mut(), + VergenKey::CommitAuthorEmail, + commit.author().email().map(&str::to_string), + ); + } - if *git_config.commit_message() { - add_entry( - config.cfg_map_mut(), - VergenKey::CommitMessage, - commit.message().map(&str::to_string), - ); - } + if *git_config.commit_message() { + add_entry( + config.cfg_map_mut(), + VergenKey::CommitMessage, + commit.message().map(&str::to_string), + ); } + } - if *git_config.semver() { - let dirty = git_config.semver_dirty(); - match *git_config.semver_kind() { - crate::SemverKind::Normal => { - add_semver(&repo, &DescribeOptions::new(), false, dirty, config); - } - crate::SemverKind::Lightweight => { - let mut opts = DescribeOptions::new(); - let _ = opts.describe_tags(); + if *git_config.semver() { + let dirty = git_config.semver_dirty(); + match *git_config.semver_kind() { + crate::SemverKind::Normal => { + add_semver(&repo, &DescribeOptions::new(), false, dirty, config); + } + crate::SemverKind::Lightweight => { + let mut opts = DescribeOptions::new(); + let _ = opts.describe_tags(); - add_semver(&repo, &opts, true, dirty, config); - } + add_semver(&repo, &opts, true, dirty, config); } } + } - if *git_config.commit_count() { - add_commit_count(&repo, config); - } + if *git_config.commit_count() { + add_commit_count(&repo, config); + } - if let Ok(resolved) = ref_head.resolve() { - if let Some(name) = resolved.name() { - let path = repo_path.join(name); - // Check whether the path exists in the filesystem before emitting it - if path.exists() { - *config.ref_path_mut() = Some(path); - } + if let Ok(resolved) = ref_head.resolve() { + if let Some(name) = resolved.name() { + let path = repo_path.join(name); + // Check whether the path exists in the filesystem before emitting it + if path.exists() { + *config.ref_path_mut() = Some(path); } } - *config.head_path_mut() = Some(repo_path.join("HEAD")); } + *config.head_path_mut() = Some(repo_path.join("HEAD")); + Ok(()) + }; + + if git_config.has_enabled() { + if git_config.skip_if_error { + // hide errors, but emit a warning + let result = add_entries(); + if result.is_err() { + let warning = format!( + "An Error occurred during processing of {}. \ + VERGEN_{}_* may be incomplete.", + "Git", "GIT" + ); + config.warnings_mut().push(warning); + } + Ok(()) + } else { + add_entries() + } + } else { + Ok(()) } - Ok(()) } #[cfg(feature = "git")] diff --git a/src/feature/rustc.rs b/src/feature/rustc.rs index b5a6db56..ab5ff4b1 100644 --- a/src/feature/rustc.rs +++ b/src/feature/rustc.rs @@ -39,6 +39,8 @@ use { /// * If the `sha` field is false, the `VERGEN_RUSTC_COMMIT_HASH` instruction will not be generated. /// * **NOTE** - The `commit_date` filed is only a date, as we are restricted to the output from `rustc_version` /// * **NOTE** - The `VERGEN_RUSTC_LLVM_VERSION` instruction will only be generated on the `nightly` channel, regardless of the `llvm_version` field. +/// * **NOTE** - To keep processing other sections if an Error occurs in this one, set +/// [`Rustc::skip_if_error`](Rustc::skip_if_error_mut()) to true. /// /// # Example /// @@ -79,6 +81,9 @@ pub struct Rustc { semver: bool, /// Enable/Disable the `VERGEN_RUSTC_COMMIT_HASH` instruction sha: bool, + /// Enable/Disable skipping [`Rustc`] if an Error occurs. + /// Use [`option_env!`](std::option_env!) to read the generated environment variables. + skip_if_error: bool, } #[cfg(feature = "rustc")] @@ -92,6 +97,7 @@ impl Default for Rustc { llvm_version: true, semver: true, sha: true, + skip_if_error: false, } } } @@ -111,7 +117,8 @@ impl Rustc { #[cfg(feature = "rustc")] pub(crate) fn configure_rustc(instructions: &Instructions, config: &mut Config) -> Result<()> { let rustc_config = instructions.rustc(); - if rustc_config.has_enabled() { + + let mut add_entries = || { let rustc = version_meta()?; if *rustc_config.channel() { @@ -171,8 +178,28 @@ pub(crate) fn configure_rustc(instructions: &Instructions, config: &mut Config) ); } } + Ok(()) + }; + + if rustc_config.has_enabled() { + if rustc_config.skip_if_error { + // hide errors, but emit a warning + let result = add_entries(); + if result.is_err() { + let warning = format!( + "An Error occurred during processing of {}. \ + VERGEN_{}_* may be incomplete.", + "Rustc", "RUSTC" + ); + config.warnings_mut().push(warning); + } + Ok(()) + } else { + add_entries() + } + } else { + Ok(()) } - Ok(()) } #[cfg(not(feature = "rustc"))] diff --git a/src/feature/si.rs b/src/feature/si.rs index 66b561c7..e46689c8 100644 --- a/src/feature/si.rs +++ b/src/feature/si.rs @@ -42,6 +42,8 @@ use { /// * If the `memory` field is false, the `VERGEN_SYSINFO_TOTAL_MEMORY` instruction will not be generated. /// * If the `cpu_vendor` field is false, the `VERGEN_SYSINFO_CPU_VENDOR` instruction will not be generated. /// * If the `cpu_core_count` field is false, the `VERGEN_SYSINFO_CPU_CORE_COUNT` instruction will not be generated. +/// * **NOTE** - To keep processing other sections if an Error occurs in this one, set +/// [`Sysinfo::skip_if_error`](Sysinfo::skip_if_error_mut()) to true. /// /// # Example /// @@ -88,6 +90,9 @@ pub struct Sysinfo { cpu_brand: bool, /// Enable/Disable the `VERGEN_SYSINFO_CPU_FREQUENCY` instruction cpu_frequency: bool, + /// Enable/Disable skipping [`Sysinfo`] if an Error occurs. + /// Use [`option_env!`](std::option_env!) to read the generated environment variables. + skip_if_error: bool, } #[cfg(feature = "si")] @@ -104,6 +109,7 @@ impl Default for Sysinfo { cpu_name: true, cpu_brand: true, cpu_frequency: true, + skip_if_error: false, } } } @@ -143,7 +149,8 @@ fn setup_system() -> System { #[allow(clippy::unnecessary_wraps, clippy::too_many_lines)] pub(crate) fn configure_sysinfo(instructions: &Instructions, config: &mut Config) -> Result<()> { let sysinfo_config = instructions.sysinfo(); - if sysinfo_config.has_enabled() { + + let mut add_entries = || { let system = setup_system(); if *sysinfo_config.name() { @@ -249,9 +256,28 @@ pub(crate) fn configure_sysinfo(instructions: &Instructions, config: &mut Config .map(|processor| processor.frequency().to_string()), ); } - } + Ok(()) + }; - Ok(()) + if sysinfo_config.has_enabled() { + if sysinfo_config.skip_if_error { + // hide errors, but emit a warning + let result = add_entries(); + if result.is_err() { + let warning = format!( + "An Error occurred during processing of {}. \ + VERGEN_{}_* may be incomplete.", + "Sysinfo", "SYSINFO" + ); + config.warnings_mut().push(warning); + } + Ok(()) + } else { + add_entries() + } + } else { + Ok(()) + } } #[cfg(not(feature = "si"))] diff --git a/src/gen.rs b/src/gen.rs index 8ad66228..60ce78bf 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -19,10 +19,11 @@ use std::{ /// /// # Errors /// -/// * Errors may be generated from the `git2` library. /// * [I/O](std::io::Error) errors may be generated. /// * Errors may be generated from the `rustc_version` library. /// * [env](std::env::VarError) errors may be generated. +/// * To ignore Errors generated in a specific section, set `skip_if_error` to true for that +/// section, e.g. [`Build::skip_if_error`](crate::Build::skip_if_error_mut()). /// /// # Usage /// @@ -50,6 +51,8 @@ pub fn vergen(config: crate::Config) -> Result<()> { /// * [I/O](std::io::Error) errors may be generated. /// * Errors may be generated from the `rustc_version` library. /// * [env](std::env::VarError) errors may be generated. +/// * To ignore Errors generated in a specific section, set `skip_if_error` to true for that +/// section, e.g. [`Build::skip_if_error`](crate::Build::skip_if_error_mut()). /// /// # Usage /// @@ -105,6 +108,11 @@ where writeln!(stdout, "cargo:rerun-if-changed={}", ref_path.display())?; } + // Emit a cargo:warning if a section was skipped over + for w in config.warnings() { + writeln!(stdout, "cargo:warning={}", w)?; + } + Ok(()) } @@ -128,6 +136,9 @@ mod test { use regex::Regex; use std::{io, path::PathBuf}; + #[cfg(feature = "git")] + use tempfile::TempDir; + lazy_static! { static ref VBD_REGEX: Regex = Regex::new(r".*VERGEN_BUILD_TIMESTAMP.*").unwrap(); } @@ -341,6 +352,30 @@ mod test { Ok(()) } + #[test] + #[cfg(feature = "git")] + fn skip_if_error() -> Result<()> { + setup(); + + let tmp_dir = TempDir::new()?; + let not_git_path = tmp_dir.path(); + let mut inst = Instructions::default(); + *inst.git_mut().enabled_mut() = true; + *inst.git_mut().base_dir_mut() = Some(not_git_path.to_owned()); + assert!(vergen(inst.clone()).is_err()); + + *inst.git_mut().skip_if_error_mut() = true; + assert!(vergen(inst.clone()).is_ok()); + + let mut stdout_buf = vec![]; + config_from_instructions(inst, Some(not_git_path), &mut stdout_buf)?; + let stdout = String::from_utf8_lossy(&stdout_buf); + assert!(stdout.contains("cargo:warning")); + + teardown(); + Ok(()) + } + #[test] fn describe_falls_back() -> Result<()> { let no_tags_path = PathBuf::from("testdata").join("notagsrepo"); diff --git a/src/lib.rs b/src/lib.rs index 43e74415..12d031a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -280,7 +280,6 @@ unused_attributes, unused_braces, unused_comparisons, - unused_crate_dependencies, unused_doc_comments, unused_extern_crates, unused_features,