diff --git a/crates/cargo-test-support/build.rs b/crates/cargo-test-support/build.rs new file mode 100644 index 00000000000..4519defc356 --- /dev/null +++ b/crates/cargo-test-support/build.rs @@ -0,0 +1,6 @@ +fn main() { + println!( + "cargo:rustc-env=NATIVE_ARCH={}", + std::env::var("TARGET").unwrap() + ); +} diff --git a/crates/cargo-test-support/src/cross_compile.rs b/crates/cargo-test-support/src/cross_compile.rs index ca18055dec8..eda21107703 100644 --- a/crates/cargo-test-support/src/cross_compile.rs +++ b/crates/cargo-test-support/src/cross_compile.rs @@ -179,6 +179,23 @@ rustup does not appear to be installed. Make sure that the appropriate panic!("{}", message); } +/// The arch triple of the test-running host. +pub fn native() -> &'static str { + env!("NATIVE_ARCH") +} + +pub fn native_arch() -> &'static str { + match native() + .split("-") + .next() + .expect("Target triple has unexpected format") + { + "x86_64" => "x86_64", + "i686" => "x86", + _ => panic!("This test should be gated on cross_compile::disabled."), + } +} + /// The alternate target-triple to build with. /// /// Only use this function on tests that check `cross_compile::disabled`. @@ -204,6 +221,15 @@ pub fn alternate_arch() -> &'static str { } } +/// A target-triple that is neither the host nor the target. +/// +/// Rustc may not work with it and it's alright, apart from being a +/// valid target triple it is supposed to be used only as a +/// placeholder for targets that should not be considered. +pub fn unused() -> &'static str { + "wasm32-unknown-unknown" +} + /// Whether or not the host can run cross-compiled executables. pub fn can_run_on_host() -> bool { if disabled() { diff --git a/src/cargo/core/compiler/build_context/mod.rs b/src/cargo/core/compiler/build_context/mod.rs index cd42c3a4ec1..f1f31336743 100644 --- a/src/cargo/core/compiler/build_context/mod.rs +++ b/src/cargo/core/compiler/build_context/mod.rs @@ -39,7 +39,7 @@ pub struct BuildContext<'a, 'cfg> { pub packages: PackageSet<'cfg>, /// Information about rustc and the target platform. - pub target_data: RustcTargetData, + pub target_data: RustcTargetData<'cfg>, /// The root units of `unit_graph` (units requested on the command-line). pub roots: Vec, @@ -58,7 +58,7 @@ impl<'a, 'cfg> BuildContext<'a, 'cfg> { build_config: &'a BuildConfig, profiles: Profiles, extra_compiler_args: HashMap>, - target_data: RustcTargetData, + target_data: RustcTargetData<'cfg>, roots: Vec, unit_graph: UnitGraph, ) -> CargoResult> { diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index c2e1cce2f00..325d4168bc3 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -655,9 +655,14 @@ fn env_args( } /// Collection of information about `rustc` and the host and target. -pub struct RustcTargetData { +pub struct RustcTargetData<'cfg> { /// Information about `rustc` itself. pub rustc: Rustc, + + /// Config + config: &'cfg Config, + requested_kinds: Vec, + /// Build information for the "host", which is information about when /// `rustc` is invoked without a `--target` flag. This is used for /// procedural macros, build scripts, etc. @@ -670,27 +675,17 @@ pub struct RustcTargetData { target_info: HashMap, } -impl RustcTargetData { +impl<'cfg> RustcTargetData<'cfg> { pub fn new( - ws: &Workspace<'_>, + ws: &Workspace<'cfg>, requested_kinds: &[CompileKind], - ) -> CargoResult { + ) -> CargoResult> { let config = ws.config(); let rustc = config.load_global_rustc(Some(ws))?; let host_config = config.target_cfg_triple(&rustc.host)?; let host_info = TargetInfo::new(config, requested_kinds, &rustc, CompileKind::Host)?; let mut target_config = HashMap::new(); let mut target_info = HashMap::new(); - for kind in requested_kinds { - if let CompileKind::Target(target) = *kind { - let tcfg = config.target_cfg_triple(target.short_name())?; - target_config.insert(target, tcfg); - target_info.insert( - target, - TargetInfo::new(config, requested_kinds, &rustc, *kind)?, - ); - } - } // This is a hack. The unit_dependency graph builder "pretends" that // `CompileKind::Host` is `CompileKind::Target(host)` if the @@ -703,13 +698,49 @@ impl RustcTargetData { target_config.insert(ct, host_config.clone()); } - Ok(RustcTargetData { + let mut res = RustcTargetData { rustc, + config, + requested_kinds: requested_kinds.into(), host_config, host_info, target_config, target_info, - }) + }; + + // Get all kinds we currently know about. + // + // For now, targets can only ever come from the root workspace + // units as artifact dependencies are not a thing yet, so this + // correctly represents all the kinds that can happen. When we + // have artifact dependencies or other ways for targets to + // appear at places that are not the root units, we may have + // to revisit this. + let all_kinds = requested_kinds + .iter() + .copied() + .chain(ws.members().flat_map(|p| { + p.manifest() + .default_kind() + .into_iter() + .chain(p.manifest().forced_kind()) + })); + for kind in all_kinds { + if let CompileKind::Target(target) = kind { + if !res.target_config.contains_key(&target) { + res.target_config + .insert(target, res.config.target_cfg_triple(target.short_name())?); + } + if !res.target_info.contains_key(&target) { + res.target_info.insert( + target, + TargetInfo::new(res.config, &res.requested_kinds, &res.rustc, kind)?, + ); + } + } + } + + Ok(res) } /// Returns a "short" name for the given kind, suitable for keying off diff --git a/src/cargo/core/compiler/compilation.rs b/src/cargo/core/compiler/compilation.rs index c1f5abce34c..cdb62231b80 100644 --- a/src/cargo/core/compiler/compilation.rs +++ b/src/cargo/core/compiler/compilation.rs @@ -127,10 +127,10 @@ impl<'cfg> Compilation<'cfg> { sysroot_target_libdir: bcx .all_kinds .iter() - .map(|kind| { + .map(|&kind| { ( - *kind, - bcx.target_data.info(*kind).sysroot_target_libdir.clone(), + kind, + bcx.target_data.info(kind).sysroot_target_libdir.clone(), ) }) .collect(), diff --git a/src/cargo/core/compiler/standard_lib.rs b/src/cargo/core/compiler/standard_lib.rs index cd6d136fe6a..b9986d3badf 100644 --- a/src/cargo/core/compiler/standard_lib.rs +++ b/src/cargo/core/compiler/standard_lib.rs @@ -33,7 +33,7 @@ pub fn parse_unstable_flag(value: Option<&str>) -> Vec { /// Resolve the standard library dependencies. pub fn resolve_std<'cfg>( ws: &Workspace<'cfg>, - target_data: &RustcTargetData, + target_data: &RustcTargetData<'cfg>, requested_targets: &[CompileKind], crates: &[String], ) -> CargoResult<(PackageSet<'cfg>, Resolve, ResolvedFeatures)> { @@ -185,7 +185,7 @@ pub fn generate_std_roots( Ok(ret) } -fn detect_sysroot_src_path(target_data: &RustcTargetData) -> CargoResult { +fn detect_sysroot_src_path(target_data: &RustcTargetData<'_>) -> CargoResult { if let Some(s) = env::var_os("__CARGO_TESTS_ONLY_SRC_ROOT") { return Ok(s.into()); } diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs index cfcda149b37..bd3288f4d1d 100644 --- a/src/cargo/core/compiler/unit_dependencies.rs +++ b/src/cargo/core/compiler/unit_dependencies.rs @@ -44,7 +44,7 @@ struct State<'a, 'cfg> { /// library. is_std: bool, global_mode: CompileMode, - target_data: &'a RustcTargetData, + target_data: &'a RustcTargetData<'cfg>, profiles: &'a Profiles, interner: &'a UnitInterner, @@ -63,7 +63,7 @@ pub fn build_unit_dependencies<'a, 'cfg>( roots: &[Unit], std_roots: &HashMap>, global_mode: CompileMode, - target_data: &'a RustcTargetData, + target_data: &'a RustcTargetData<'cfg>, profiles: &'a Profiles, interner: &'a UnitInterner, ) -> CargoResult { diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 83850d48a47..56321cdde80 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -390,6 +390,9 @@ features! { // Support for 2021 edition. (unstable, edition2021, "", "reference/unstable.html#edition-2021"), + + // Allow to specify per-package targets (compile kinds) + (unstable, per_package_target, "", "reference/unstable.html#per-package-target"), } const PUBLISH_LOCKFILE_REMOVED: &str = "The publish-lockfile key in Cargo.toml \ diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index ec297fca61f..7f5e1f55ee1 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -11,7 +11,7 @@ use serde::ser; use serde::Serialize; use url::Url; -use crate::core::compiler::CrateType; +use crate::core::compiler::{CompileKind, CrateType}; use crate::core::resolver::ResolveBehavior; use crate::core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary}; use crate::core::{Edition, Feature, Features, WorkspaceConfig}; @@ -32,6 +32,8 @@ pub enum EitherManifest { pub struct Manifest { summary: Summary, targets: Vec, + default_kind: Option, + forced_kind: Option, links: Option, warnings: Warnings, exclude: Vec, @@ -366,6 +368,8 @@ compact_debug! { impl Manifest { pub fn new( summary: Summary, + default_kind: Option, + forced_kind: Option, targets: Vec, exclude: Vec, include: Vec, @@ -388,6 +392,8 @@ impl Manifest { ) -> Manifest { Manifest { summary, + default_kind, + forced_kind, targets, warnings: Warnings::new(), exclude, @@ -414,6 +420,12 @@ impl Manifest { pub fn dependencies(&self) -> &[Dependency] { self.summary.dependencies() } + pub fn default_kind(&self) -> Option { + self.default_kind + } + pub fn forced_kind(&self) -> Option { + self.forced_kind + } pub fn exclude(&self) -> &[String] { &self.exclude } @@ -503,6 +515,15 @@ impl Manifest { })?; } + if self.default_kind.is_some() || self.forced_kind.is_some() { + self.unstable_features + .require(Feature::per_package_target()) + .with_context(|| { + "the `package.default-target` and `package.forced-target` \ + manifest keys are unstable and may not work properly" + })?; + } + Ok(()) } diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index 812be912c4f..6f160db5334 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -500,7 +500,7 @@ impl<'cfg> PackageSet<'cfg> { root_ids: &[PackageId], has_dev_units: HasDevUnits, requested_kinds: &[CompileKind], - target_data: &RustcTargetData, + target_data: &RustcTargetData<'cfg>, force_all_targets: ForceAllTargets, ) -> CargoResult<()> { fn collect_used_deps( @@ -509,7 +509,7 @@ impl<'cfg> PackageSet<'cfg> { pkg_id: PackageId, has_dev_units: HasDevUnits, requested_kinds: &[CompileKind], - target_data: &RustcTargetData, + target_data: &RustcTargetData<'_>, force_all_targets: ForceAllTargets, ) -> CargoResult<()> { if !used.insert(pkg_id) { diff --git a/src/cargo/core/resolver/features.rs b/src/cargo/core/resolver/features.rs index 9e601d96cd2..eb58c391a2b 100644 --- a/src/cargo/core/resolver/features.rs +++ b/src/cargo/core/resolver/features.rs @@ -414,7 +414,7 @@ pub struct FeatureDifferences { pub struct FeatureResolver<'a, 'cfg> { ws: &'a Workspace<'cfg>, - target_data: &'a RustcTargetData, + target_data: &'a RustcTargetData<'cfg>, /// The platforms to build for, requested by the user. requested_targets: &'a [CompileKind], resolve: &'a Resolve, @@ -452,7 +452,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { /// with the result. pub fn resolve( ws: &Workspace<'cfg>, - target_data: &RustcTargetData, + target_data: &RustcTargetData<'cfg>, resolve: &Resolve, package_set: &'a PackageSet<'cfg>, cli_features: &CliFeatures, diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 44945d91986..7249af86a44 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -467,11 +467,17 @@ pub fn create_bcx<'a, 'cfg>( }) .collect(); + // Passing `build_config.requested_kinds` instead of + // `explicit_host_kinds` here so that `generate_targets` can do + // its own special handling of `CompileKind::Host`. It will + // internally replace the host kind by the `explicit_host_kind` + // before setting as a unit. let mut units = generate_targets( ws, &to_builds, filter, - &explicit_host_kinds, + &build_config.requested_kinds, + explicit_host_kind, build_config.mode, &resolve, &workspace_resolve, @@ -842,6 +848,7 @@ fn generate_targets( packages: &[&Package], filter: &CompileFilter, requested_kinds: &[CompileKind], + explicit_host_kind: CompileKind, mode: CompileMode, resolve: &Resolve, workspace_resolve: &Option, @@ -915,7 +922,27 @@ fn generate_targets( let features_for = FeaturesFor::from_for_host(target.proc_macro()); let features = resolved_features.activated_features(pkg.package_id(), features_for); - for kind in requested_kinds { + // If `--target` has not been specified, then the unit + // graph is built almost like if `--target $HOST` was + // specified. See `rebuild_unit_graph_shared` for more on + // why this is done. However, if the package has its own + // `package.target` key, then this gets used instead of + // `$HOST` + let explicit_kinds = if let Some(k) = pkg.manifest().forced_kind() { + vec![k] + } else { + requested_kinds + .iter() + .map(|kind| match kind { + CompileKind::Host => { + pkg.manifest().default_kind().unwrap_or(explicit_host_kind) + } + CompileKind::Target(t) => CompileKind::Target(*t), + }) + .collect() + }; + + for kind in explicit_kinds.iter() { let profile = profiles.get_profile( pkg.package_id(), ws.is_member(pkg), diff --git a/src/cargo/ops/cargo_output_metadata.rs b/src/cargo/ops/cargo_output_metadata.rs index baabce47c24..b2e100f6c0b 100644 --- a/src/cargo/ops/cargo_output_metadata.rs +++ b/src/cargo/ops/cargo_output_metadata.rs @@ -171,7 +171,7 @@ fn build_resolve_graph_r( pkg_id: PackageId, resolve: &Resolve, package_map: &BTreeMap, - target_data: &RustcTargetData, + target_data: &RustcTargetData<'_>, requested_kinds: &[CompileKind], ) { if node_map.contains_key(&pkg_id) { diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index b3199654ace..6a08a08d3f6 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -79,7 +79,7 @@ pub fn resolve_ws<'a>(ws: &Workspace<'a>) -> CargoResult<(PackageSet<'a>, Resolv /// members. In this case, `opts.all_features` must be `true`. pub fn resolve_ws_with_opts<'cfg>( ws: &Workspace<'cfg>, - target_data: &RustcTargetData, + target_data: &RustcTargetData<'cfg>, requested_targets: &[CompileKind], cli_features: &CliFeatures, specs: &[PackageIdSpec], diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index 034ef0735bb..37442a4dafd 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -249,7 +249,7 @@ pub fn build<'a>( resolved_features: &ResolvedFeatures, specs: &[PackageIdSpec], cli_features: &CliFeatures, - target_data: &RustcTargetData, + target_data: &RustcTargetData<'_>, requested_kinds: &[CompileKind], package_map: HashMap, opts: &TreeOptions, @@ -294,7 +294,7 @@ fn add_pkg( resolved_features: &ResolvedFeatures, package_id: PackageId, features_for: FeaturesFor, - target_data: &RustcTargetData, + target_data: &RustcTargetData<'_>, requested_kind: CompileKind, opts: &TreeOptions, ) -> usize { diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 5132428db35..28c313baff9 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -15,6 +15,7 @@ use serde::ser; use serde::{Deserialize, Serialize}; use url::Url; +use crate::core::compiler::{CompileKind, CompileTarget}; use crate::core::dependency::DepKind; use crate::core::manifest::{ManifestMetadata, TargetSourcePath, Warnings}; use crate::core::resolver::ResolveBehavior; @@ -793,6 +794,10 @@ pub struct TomlProject { authors: Option>, build: Option, metabuild: Option, + #[serde(rename = "default-target")] + default_target: Option, + #[serde(rename = "forced-target")] + forced_target: Option, links: Option, exclude: Option>, include: Option>, @@ -1313,9 +1318,24 @@ impl TomlManifest { } } + let default_kind = project + .default_target + .as_ref() + .map(|t| CompileTarget::new(&*t)) + .transpose()? + .map(CompileKind::Target); + let forced_kind = project + .forced_target + .as_ref() + .map(|t| CompileTarget::new(&*t)) + .transpose()? + .map(CompileKind::Target); + let custom_metadata = project.metadata.clone(); let mut manifest = Manifest::new( summary, + default_kind, + forced_kind, targets, exclude, include, diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index bf7db5772db..8e7927f8308 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -895,6 +895,26 @@ In this example, the `std` feature enables the `std` feature on the `serde` dependency. However, unlike the normal `serde/std` syntax, it will not enable the optional dependency `serde` unless something else has included it. +### per-package-target + +The `per-package-target` feature adds two keys to the manifest: +`package.default-target` and `package.forced-target`. The first makes +the package be compiled by default (ie. when no `--target` argument is +passed) for some target. The second one makes the package always be +compiled for the target. + +Example: + +```toml +[package] +forced-target = "wasm32-unknown-unknown" +``` + +In this example, the crate is always built for +`wasm32-unknown-unknown`, for instance because it is going to be used +as a plugin for a main program that runs on the host (or provided on +the command line) target. + ### credential-process * Tracking Issue: [#8933](https://github.com/rust-lang/cargo/issues/8933) * RFC: [#2730](https://github.com/rust-lang/rfcs/pull/2730) diff --git a/tests/testsuite/cross_compile.rs b/tests/testsuite/cross_compile.rs index 8d1947f2203..827795dab48 100644 --- a/tests/testsuite/cross_compile.rs +++ b/tests/testsuite/cross_compile.rs @@ -153,6 +153,209 @@ fn simple_deps() { } } +/// Always take care of setting these so that +/// `cross_compile::alternate()` is the actually-picked target +fn per_crate_target_test( + default_target: Option<&'static str>, + forced_target: Option<&'static str>, + arg_target: Option<&'static str>, +) { + if cross_compile::disabled() { + return; + } + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + cargo-features = ["per-package-target"] + + [package] + name = "foo" + version = "0.0.0" + authors = [] + build = "build.rs" + {} + {} + "#, + default_target + .map(|t| format!(r#"default-target = "{}""#, t)) + .unwrap_or(String::new()), + forced_target + .map(|t| format!(r#"forced-target = "{}""#, t)) + .unwrap_or(String::new()), + ), + ) + .file( + "build.rs", + &format!( + r#" + fn main() {{ + assert_eq!(std::env::var("TARGET").unwrap(), "{}"); + }} + "#, + cross_compile::alternate() + ), + ) + .file( + "src/main.rs", + &format!( + r#" + use std::env; + fn main() {{ + assert_eq!(env::consts::ARCH, "{}"); + }} + "#, + cross_compile::alternate_arch() + ), + ) + .build(); + + let mut cmd = p.cargo("build -v"); + if let Some(t) = arg_target { + cmd.arg("--target").arg(&t); + } + cmd.masquerade_as_nightly_cargo().run(); + assert!(p.target_bin(cross_compile::alternate(), "foo").is_file()); + + if cross_compile::can_run_on_host() { + p.process(&p.target_bin(cross_compile::alternate(), "foo")) + .run(); + } +} + +#[cargo_test] +fn per_crate_default_target_is_default() { + per_crate_target_test(Some(cross_compile::alternate()), None, None); +} + +#[cargo_test] +fn per_crate_default_target_gets_overridden() { + per_crate_target_test( + Some(cross_compile::unused()), + None, + Some(cross_compile::alternate()), + ); +} + +#[cargo_test] +fn per_crate_forced_target_is_default() { + per_crate_target_test(None, Some(cross_compile::alternate()), None); +} + +#[cargo_test] +fn per_crate_forced_target_does_not_get_overridden() { + per_crate_target_test( + None, + Some(cross_compile::alternate()), + Some(cross_compile::unused()), + ); +} + +#[cargo_test] +fn workspace_with_multiple_targets() { + if cross_compile::disabled() { + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["native", "cross"] + "#, + ) + .file( + "native/Cargo.toml", + r#" + cargo-features = ["per-package-target"] + + [package] + name = "native" + version = "0.0.0" + authors = [] + build = "build.rs" + "#, + ) + .file( + "native/build.rs", + &format!( + r#" + fn main() {{ + assert_eq!(std::env::var("TARGET").unwrap(), "{}"); + }} + "#, + cross_compile::native() + ), + ) + .file( + "native/src/main.rs", + &format!( + r#" + use std::env; + fn main() {{ + assert_eq!(env::consts::ARCH, "{}"); + }} + "#, + cross_compile::native_arch() + ), + ) + .file( + "cross/Cargo.toml", + &format!( + r#" + cargo-features = ["per-package-target"] + + [package] + name = "cross" + version = "0.0.0" + authors = [] + build = "build.rs" + default-target = "{}" + "#, + cross_compile::alternate(), + ), + ) + .file( + "cross/build.rs", + &format!( + r#" + fn main() {{ + assert_eq!(std::env::var("TARGET").unwrap(), "{}"); + }} + "#, + cross_compile::alternate() + ), + ) + .file( + "cross/src/main.rs", + &format!( + r#" + use std::env; + fn main() {{ + assert_eq!(env::consts::ARCH, "{}"); + }} + "#, + cross_compile::alternate_arch() + ), + ) + .build(); + + let mut cmd = p.cargo("build -v"); + cmd.masquerade_as_nightly_cargo().run(); + + assert!(p.bin("native").is_file()); + assert!(p.target_bin(cross_compile::alternate(), "cross").is_file()); + + p.process(&p.bin("native")).run(); + if cross_compile::can_run_on_host() { + p.process(&p.target_bin(cross_compile::alternate(), "cross")) + .run(); + } +} + #[cargo_test] fn linker() { if cross_compile::disabled() {