From ffe70b8822bd70a23e95207e33ee00668ffff277 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Thu, 4 Jun 2026 08:34:53 -0400 Subject: [PATCH] fix(bindeps): default artifacts to package target When an artifact dependency does not specify `target`, use the target selected for the dependency package. This lets packages that already declare an inherent per-package target avoid repeating that target on every artifact dependency edge. The fallback honors `forced-target`, applies `default-target` only when no command-line target was specified, and lets an explicit artifact dependency `target` win. Closes #17050. --- src/cargo/core/compiler/unit_dependencies.rs | 55 +++- src/cargo/core/profiles.rs | 3 +- src/cargo/core/resolver/features.rs | 30 +- src/doc/src/reference/unstable.md | 5 + tests/testsuite/artifact_dep.rs | 273 +++++++++++++++++++ 5 files changed, 352 insertions(+), 14 deletions(-) diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs index 2d0ea7c3c62..45e9599ab09 100644 --- a/src/cargo/core/compiler/unit_dependencies.rs +++ b/src/cargo/core/compiler/unit_dependencies.rs @@ -465,17 +465,30 @@ fn calc_artifact_deps<'a>( !unit.mode.is_run_custom_build(), "BUG: This should be handled in a separate branch" ); + let package_target = artifact_package_target( + artifact, + artifact_pkg, + state.target_data.requested_kinds(), + ); + let compile_kind = artifact + .target() + .and_then(|t| match t { + ArtifactTarget::BuildDependencyAssumeTarget => None, + ArtifactTarget::Force(kind) => Some(CompileKind::Target(kind)), + }) + .or(package_target) + .unwrap_or(unit.kind); + let unit_for = match package_target { + Some(kind) => { + unit_for.with_artifact_features_from_resolved_compile_kind(Some(kind)) + } + None => unit_for.with_artifact_features(artifact), + }; ret.extend(artifact_targets_to_unit_deps( unit, - unit_for.with_artifact_features(artifact), + unit_for, state, - artifact - .target() - .and_then(|t| match t { - ArtifactTarget::BuildDependencyAssumeTarget => None, - ArtifactTarget::Force(kind) => Some(CompileKind::Target(kind)), - }) - .unwrap_or(unit.kind), + compile_kind, artifact_pkg, dep, )?); @@ -554,7 +567,14 @@ fn compute_deps_custom_build( let artifact = dep.artifact().expect("artifact dep"); let resolved_artifact_compile_kind = artifact .target() - .map(|target| target.to_resolved_compile_kind(root_unit_compile_target)); + .map(|target| target.to_resolved_compile_kind(root_unit_compile_target)) + .or_else(|| { + artifact_package_target( + artifact, + artifact_pkg, + state.target_data.requested_kinds(), + ) + }); result.extend(artifact_targets_to_unit_deps( unit, @@ -572,6 +592,23 @@ fn compute_deps_custom_build( Ok(result) } +fn artifact_package_target( + artifact: &Artifact, + artifact_pkg: &Package, + requested_kinds: &[CompileKind], +) -> Option { + if artifact.target().is_some() { + return None; + } + if let Some(kind) = artifact_pkg.manifest().forced_kind() { + return Some(kind); + } + if requested_kinds.iter().any(CompileKind::is_host) { + return artifact_pkg.manifest().default_kind(); + } + None +} + /// Given a `parent` unit containing a dependency `dep` whose package is `artifact_pkg`, /// find all targets in `artifact_pkg` which refer to the `dep`s artifact declaration /// and turn them into units. diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index 5319656836d..847f7505591 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -1252,7 +1252,8 @@ impl UnitFor { match dep_artifact { Some(artifact) => artifact .target() - .and_then(|t| t.to_resolved_compile_target(self.root_compile_kind)), + .and_then(|t| t.to_resolved_compile_target(self.root_compile_kind)) + .or(self.artifact_target_for_features), None => self.artifact_target_for_features, }, ) diff --git a/src/cargo/core/resolver/features.rs b/src/cargo/core/resolver/features.rs index 6d45edc6691..2efef4e11d7 100644 --- a/src/cargo/core/resolver/features.rs +++ b/src/cargo/core/resolver/features.rs @@ -809,6 +809,7 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { fn artifact_features_for( this: &mut FeatureResolver<'_, '_>, pkg_id: PackageId, + dep_pkg_id: PackageId, dep: &Dependency, lib_fk: FeaturesFor, unstable_json_spec: bool, @@ -817,6 +818,21 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { return Ok(vec![lib_fk]); }; let mut result = Vec::new(); + let artifact_target = artifact.target().or_else(|| { + let dep_pkg = this + .package_set + .get_one(dep_pkg_id) + .expect("packages downloaded"); + if let Some(CompileKind::Target(target)) = dep_pkg.manifest().forced_kind() { + return Some(ArtifactTarget::Force(target)); + } + if this.requested_targets.iter().any(CompileKind::is_host) { + if let Some(CompileKind::Target(target)) = dep_pkg.manifest().default_kind() { + return Some(ArtifactTarget::Force(target)); + } + } + None + }); let host_triple = this.target_data.rustc.host; // Not all targets may be queried before resolution since artifact // dependencies and per-pkg-targets are not immediately known. @@ -834,7 +850,7 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { }) }; - if let Some(target) = artifact.target() { + if let Some(target) = artifact_target { match target { ArtifactTarget::Force(target) => { activate_target(target)?; @@ -856,7 +872,7 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { } } } - if artifact.is_lib() || artifact.target().is_none() { + if artifact.is_lib() || artifact_target.is_none() { result.push(lib_fk); } Ok(result) @@ -919,8 +935,14 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { fk }; - let dep_fks = - artifact_features_for(self, pkg_id, dep, lib_fk, unstable_json_spec)?; + let dep_fks = artifact_features_for( + self, + pkg_id, + dep_id, + dep, + lib_fk, + unstable_json_spec, + )?; Ok(dep_fks.into_iter().map(move |dep_fk| (dep, dep_fk))) }) .flatten_ok() diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 139b7f7491e..5f0277cddc2 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1040,8 +1040,13 @@ Artifact-dependencies adds the following keys to a dependency declaration in `Ca This field can only be specified when `artifact` is specified. The default if this is not specified depends on the dependency kind. + If the dependency package specifies `package.forced-target`, that target is + used. + If no command-line target is specified and the dependency package specifies + `package.default-target`, that target is used. For build dependencies, it will be built for the host target. For all other dependencies, it will be built for the same targets the declaring package is built for. + An explicit artifact dependency `target` overrides either package target. For a build dependency, this can also take the special value of `"target"` which means to build the dependency for the same targets that the package is being built for. diff --git a/tests/testsuite/artifact_dep.rs b/tests/testsuite/artifact_dep.rs index db28f921e59..2107fd77dfe 100644 --- a/tests/testsuite/artifact_dep.rs +++ b/tests/testsuite/artifact_dep.rs @@ -1546,6 +1546,279 @@ foo v0.0.0 ([ROOT]/foo) .run(); } +#[cargo_test] +fn artifact_dep_uses_dependency_forced_target_as_default() { + if cross_compile_disabled() { + return; + } + let target = cross_compile::alternate(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.0" + authors = [] + resolver = "2" + edition = "2015" + + [dependencies] + bindep = { path = "bindep", artifact = "bin" } + "#, + ) + .file( + "src/lib.rs", + r#" + pub fn foo() { + let _bin = include_bytes!(env!("CARGO_BIN_FILE_BINDEP")); + } + "#, + ) + .file( + "bindep/Cargo.toml", + &format!( + r#" + cargo-features = ["per-package-target"] + + [package] + name = "bindep" + version = "0.0.0" + edition = "2015" + forced-target = "{target}" + "#, + ), + ) + .file("bindep/src/main.rs", "fn main() {}") + .build(); + + p.cargo("check -Z bindeps") + .masquerade_as_nightly_cargo(&["bindeps", "per-package-target"]) + .with_stderr_data(str![[r#" +[LOCKING] 1 package to latest compatible version +[COMPILING] bindep v0.0.0 ([ROOT]/foo/bindep) +[CHECKING] foo v0.0.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .with_status(0) + .run(); + + assert!( + p.glob(&format!( + "target/{}/debug/deps/artifact/bindep-*/bin/bindep*", + target + )) + .next() + .is_some() + ); +} + +#[cargo_test] +fn artifact_dep_uses_dependency_default_target_as_default() { + if cross_compile_disabled() { + return; + } + let target = cross_compile::alternate(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.0" + authors = [] + resolver = "2" + edition = "2015" + + [dependencies] + bindep = { path = "bindep", artifact = "bin" } + "#, + ) + .file( + "src/lib.rs", + r#" + pub fn foo() { + let _bin = include_bytes!(env!("CARGO_BIN_FILE_BINDEP")); + } + "#, + ) + .file( + "bindep/Cargo.toml", + &format!( + r#" + cargo-features = ["per-package-target"] + + [package] + name = "bindep" + version = "0.0.0" + edition = "2015" + default-target = "{target}" + "#, + ), + ) + .file("bindep/src/main.rs", "fn main() {}") + .build(); + + p.cargo("check -Z bindeps") + .masquerade_as_nightly_cargo(&["bindeps", "per-package-target"]) + .with_stderr_data(str![[r#" +[LOCKING] 1 package to latest compatible version +[COMPILING] bindep v0.0.0 ([ROOT]/foo/bindep) +[CHECKING] foo v0.0.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .with_status(0) + .run(); + + assert!( + p.glob(&format!( + "target/{}/debug/deps/artifact/bindep-*/bin/bindep*", + target + )) + .next() + .is_some() + ); +} + +#[cargo_test] +fn command_line_target_overrides_dependency_default_target() { + if cross_compile_disabled() { + return; + } + let target = cross_compile::alternate(); + let default_target = rustc_host(); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.0" + authors = [] + resolver = "2" + edition = "2015" + + [dependencies] + bindep = {{ path = "bindep", artifact = "bin" }} + "#, + ), + ) + .file( + "src/lib.rs", + r#" + pub fn foo() { + let _bin = include_bytes!(env!("CARGO_BIN_FILE_BINDEP")); + } + "#, + ) + .file( + "bindep/Cargo.toml", + &format!( + r#" + cargo-features = ["per-package-target"] + + [package] + name = "bindep" + version = "0.0.0" + edition = "2015" + default-target = "{default_target}" + "#, + ), + ) + .file("bindep/src/main.rs", "fn main() {}") + .build(); + + p.cargo("check -Z bindeps --target") + .arg(target) + .masquerade_as_nightly_cargo(&["bindeps", "per-package-target"]) + .with_status(0) + .run(); + + assert!( + p.glob(&format!( + "target/{}/debug/deps/artifact/bindep-*/bin/bindep*", + target + )) + .next() + .is_some() + ); +} + +#[cargo_test] +fn explicit_artifact_dep_target_overrides_dependency_forced_target() { + if cross_compile_disabled() { + return; + } + let target = cross_compile::alternate(); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.0" + authors = [] + resolver = "2" + edition = "2015" + + [dependencies] + bindep = {{ path = "bindep", artifact = "bin", target = "{target}" }} + "#, + ), + ) + .file( + "src/lib.rs", + r#" + pub fn foo() { + let _bin = include_bytes!(env!("CARGO_BIN_FILE_BINDEP")); + } + "#, + ) + .file( + "bindep/Cargo.toml", + r#" + cargo-features = ["per-package-target"] + + [package] + name = "bindep" + version = "0.0.0" + edition = "2015" + forced-target = "wasm32-unknown-unknown" + "#, + ) + .file("bindep/src/main.rs", "fn main() {}") + .build(); + + p.cargo("check -Z bindeps") + .masquerade_as_nightly_cargo(&["bindeps", "per-package-target"]) + .with_stderr_data(str![[r#" +[LOCKING] 1 package to latest compatible version +[COMPILING] bindep v0.0.0 ([ROOT]/foo/bindep) +[CHECKING] foo v0.0.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .with_status(0) + .run(); + + assert!( + p.glob(&format!( + "target/{}/debug/deps/artifact/bindep-*/bin/bindep*", + target + )) + .next() + .is_some() + ); +} + /// From issue #10593 /// The case where: /// * artifact dep is { target = }