diff --git a/auditable-cyclonedx/src/lib.rs b/auditable-cyclonedx/src/lib.rs index 6964e4f..98482bc 100644 --- a/auditable-cyclonedx/src/lib.rs +++ b/auditable-cyclonedx/src/lib.rs @@ -124,7 +124,7 @@ fn purl(pkg: &auditable_serde::Package) -> String { Source::Local => "&download_url=redacted", Source::Registry => "&repository_url=redacted", Source::Other(_) => "&download_url=redacted", - unknown => panic!("Unknown source: {:?}", unknown), + unknown => panic!("Unknown source: {unknown:?}"), }); purl } diff --git a/cargo-auditable/src/auditable_from_metadata.rs b/cargo-auditable/src/auditable_from_metadata.rs index 0353d69..3b06102 100644 --- a/cargo-auditable/src/auditable_from_metadata.rs +++ b/cargo-auditable/src/auditable_from_metadata.rs @@ -1,7 +1,12 @@ //! Converts from `cargo_metadata` crate structs to `auditable-serde` structs, //! which map to our own serialialized representation. -use std::{cmp::min, cmp::Ordering::*, collections::HashMap, error::Error, fmt::Display}; +use std::{ + cmp::{min, Ordering::*}, + collections::{HashMap, HashSet}, + error::Error, + fmt::Display, +}; use auditable_serde::{DependencyKind, Package, Source, VersionInfo}; @@ -84,6 +89,8 @@ pub fn encode_audit_data( .repr .as_str(); + let proc_macros = proc_macro_packages(metadata); + // Walk the dependency tree and resolve dependency kinds for each package. // We need this because there may be several different paths to the same package // and we need to aggregate dependency types across all of them. @@ -103,8 +110,14 @@ pub fn encode_audit_data( let parent_dep_kind = id_to_dep_kind[parent.id.repr.as_str()]; for child in &parent.deps { let child_id = child.pkg.repr.as_str(); - let dep_kind = strongest_dep_kind(child.dep_kinds.as_slice()); - let dep_kind = min(dep_kind, parent_dep_kind); + let mut dep_kind = strongest_dep_kind(child.dep_kinds.as_slice()); + // If the parent is a build dependency that has a runtime dependency, overall dependency should be 'build'. + // This propagates the dependency kinds that way from parent to child. + dep_kind = min(dep_kind, parent_dep_kind); + // proc macros require special handling since cargo_metadata reports them as normal deps + if proc_macros.contains(child_id) { + dep_kind = min(dep_kind, PrivateDepKind::Build); + } let dep_kind_on_previous_visit = id_to_dep_kind.get(child_id); if dep_kind_on_previous_visit.is_none() || &dep_kind > dep_kind_on_previous_visit.unwrap() @@ -215,6 +228,25 @@ fn strongest_dep_kind(deps: &[cargo_metadata::DepKindInfo]) -> PrivateDepKind { .unwrap_or(PrivateDepKind::Runtime) // for compatibility with Rust earlier than 1.41 } +fn proc_macro_packages(metadata: &cargo_metadata::Metadata) -> HashSet<&str> { + metadata + .packages + .iter() + .filter_map(|pkg| { + // As of Rust 1.88 a single crate cannot be both a proc macro and something else. + // Checking that length is 1 is purely to hedge against support for it being added in the future. + if pkg.targets.len() == 1 + && pkg.targets[0].kind.len() == 1 + && pkg.targets[0].kind[0] == "proc-macro" + { + Some(pkg.id.repr.as_str()) + } else { + None + } + }) + .collect() +} + #[cfg(test)] mod tests { #![allow(unused_imports)] // otherwise conditional compilation emits warnings diff --git a/cargo-auditable/src/rustc_wrapper.rs b/cargo-auditable/src/rustc_wrapper.rs index 4287213..91165c1 100644 --- a/cargo-auditable/src/rustc_wrapper.rs +++ b/cargo-auditable/src/rustc_wrapper.rs @@ -93,9 +93,8 @@ pub fn main(rustc_path: &OsStr) { command_with_args.extend(command.get_args()); eprintln!( "Failed to invoke rustc! Make sure it's in your $PATH\n\ - The error was: {}\n\ - The attempted call was: {:?}", - err, command_with_args, + The error was: {err}\n\ + The attempted call was: {command_with_args:?}", ); std::process::exit(1); }); diff --git a/cargo-auditable/tests/fixtures/proc-macro-dependency/Cargo.lock b/cargo-auditable/tests/fixtures/proc-macro-dependency/Cargo.lock new file mode 100644 index 0000000..f5b5d47 --- /dev/null +++ b/cargo-auditable/tests/fixtures/proc-macro-dependency/Cargo.lock @@ -0,0 +1,65 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "proc-macro-dependency" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/cargo-auditable/tests/fixtures/proc-macro-dependency/Cargo.toml b/cargo-auditable/tests/fixtures/proc-macro-dependency/Cargo.toml new file mode 100644 index 0000000..aa9a038 --- /dev/null +++ b/cargo-auditable/tests/fixtures/proc-macro-dependency/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "proc-macro-dependency" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1.0.219", features = ["derive"] } + +[workspace] diff --git a/cargo-auditable/tests/fixtures/proc-macro-dependency/src/main.rs b/cargo-auditable/tests/fixtures/proc-macro-dependency/src/main.rs new file mode 100644 index 0000000..f5fa411 --- /dev/null +++ b/cargo-auditable/tests/fixtures/proc-macro-dependency/src/main.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +fn main() { + println!("{:?}", Hello("Hello, world!")); +} + +#[derive(Serialize, Deserialize, Debug)] +struct Hello (&'static str); diff --git a/cargo-auditable/tests/it.rs b/cargo-auditable/tests/it.rs index 9e3377a..22d926c 100644 --- a/cargo-auditable/tests/it.rs +++ b/cargo-auditable/tests/it.rs @@ -552,3 +552,36 @@ fn test_path_not_equal_name_inner(sbom: bool) { .iter() .any(|p| p.name == "baz" && p.kind == DependencyKind::Runtime)); } + +#[test] +fn test_proc_macro() { + test_proc_macro_inner(false); + test_proc_macro_inner(true); +} +fn test_proc_macro_inner(sbom: bool) { + // Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies. + let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/proc-macro-dependency/Cargo.toml"); + // Run in workspace root with default features + let bins = run_cargo_auditable(workspace_cargo_toml, &[], &[], sbom); + eprintln!("Proc macro binary map: {bins:?}"); + + // proc-macro-dependency should depend on + let binary = &bins.get("proc-macro-dependency").unwrap()[0]; + let dep_info = get_dependency_info(binary); + eprintln!("{binary} dependency info: {dep_info:?}"); + // locate the serde_derive proc macro package + let serde_derive_info = dep_info + .packages + .iter() + .find(|p| p.name == "serde_derive") + .expect("Could not find 'serde_derive' in the embedded dependency list!"); + assert_eq!(serde_derive_info.kind, DependencyKind::Build); + // locate the syn package which is norm a dependency of serde-derive + let syn_info = dep_info + .packages + .iter() + .find(|p| p.name == "syn") + .expect("Could not find 'syn' in the embedded dependency list!"); + assert_eq!(syn_info.kind, DependencyKind::Build); +} diff --git a/resolverver/src/error.rs b/resolverver/src/error.rs index 0f1b6f5..7cd8b4c 100644 --- a/resolverver/src/error.rs +++ b/resolverver/src/error.rs @@ -10,10 +10,10 @@ impl std::fmt::Display for UnrecognizedValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { UnrecognizedValue::UnknownResolver(resolver) => { - write!(f, "Unrecognized resolver version: {}", resolver) + write!(f, "Unrecognized resolver version: {resolver}") } UnrecognizedValue::UnknownEdition(edition) => { - write!(f, "Unrecognized Rust edition: {}", edition) + write!(f, "Unrecognized Rust edition: {edition}") } } } @@ -30,8 +30,8 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::UnrecognizedValue(msg) => write!(f, "{}", msg), - Error::TomlParseError(err) => write!(f, "Failed to parse Cargo.toml: {}", err), + Error::UnrecognizedValue(msg) => write!(f, "{msg}"), + Error::TomlParseError(err) => write!(f, "Failed to parse Cargo.toml: {err}"), } } } diff --git a/resolverver/src/raw_fields.rs b/resolverver/src/raw_fields.rs index ba85270..8e4de32 100644 --- a/resolverver/src/raw_fields.rs +++ b/resolverver/src/raw_fields.rs @@ -95,7 +95,7 @@ package.edition = \"2021\" }), }; - let parsed: RawTomlFields = toml::from_str(&toml).unwrap(); + let parsed: RawTomlFields = toml::from_str(toml).unwrap(); assert_eq!(parsed, expected); let resolved_expected = TomlFields { @@ -132,7 +132,7 @@ resolver = \"2\" }), }; - let parsed: RawTomlFields = toml::from_str(&toml).unwrap(); + let parsed: RawTomlFields = toml::from_str(toml).unwrap(); assert_eq!(parsed, expected); let resolved_expected = TomlFields { @@ -160,7 +160,7 @@ edition = \"2021\" workspace: None, }; - let parsed: RawTomlFields = toml::from_str(&toml).unwrap(); + let parsed: RawTomlFields = toml::from_str(toml).unwrap(); assert_eq!(parsed, expected); } @@ -180,7 +180,7 @@ version = \"0.1.0\" workspace: None, }; - let parsed: RawTomlFields = toml::from_str(&toml).unwrap(); + let parsed: RawTomlFields = toml::from_str(toml).unwrap(); assert_eq!(parsed, expected); let resolved_expected = TomlFields { @@ -206,7 +206,7 @@ members = [\"some-package\"] }), }; - let parsed: RawTomlFields = toml::from_str(&toml).unwrap(); + let parsed: RawTomlFields = toml::from_str(toml).unwrap(); assert_eq!(parsed, expected); let resolved_expected = TomlFields {