Expose build.target .cargo/config setting as packages.target in Cargo.toml

This commit is contained in:
Léo Gaspard 2020-12-31 02:33:39 +01:00
parent 0ed318d182
commit 5e11afc1ab
19 changed files with 404 additions and 36 deletions

View File

@ -5,6 +5,8 @@ authors = ["Alex Crichton <alex@alexcrichton.com>"]
license = "MIT OR Apache-2.0"
edition = "2018"
build = "build.rs"
[lib]
doctest = false

View File

@ -0,0 +1,6 @@
fn main() {
println!(
"cargo:rustc-env=NATIVE_ARCH={}",
std::env::var("TARGET").unwrap()
);
}

View File

@ -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() {

View File

@ -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<Unit>,
@ -58,7 +58,7 @@ impl<'a, 'cfg> BuildContext<'a, 'cfg> {
build_config: &'a BuildConfig,
profiles: Profiles,
extra_compiler_args: HashMap<Unit, Vec<String>>,
target_data: RustcTargetData,
target_data: RustcTargetData<'cfg>,
roots: Vec<Unit>,
unit_graph: UnitGraph,
) -> CargoResult<BuildContext<'a, 'cfg>> {

View File

@ -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<CompileKind>,
/// 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<CompileTarget, TargetInfo>,
}
impl RustcTargetData {
impl<'cfg> RustcTargetData<'cfg> {
pub fn new(
ws: &Workspace<'_>,
ws: &Workspace<'cfg>,
requested_kinds: &[CompileKind],
) -> CargoResult<RustcTargetData> {
) -> CargoResult<RustcTargetData<'cfg>> {
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,56 @@ 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 {
match res.target_config.entry(target) {
std::collections::hash_map::Entry::Occupied(_) => (),
std::collections::hash_map::Entry::Vacant(place) => {
place.insert(res.config.target_cfg_triple(target.short_name())?);
}
}
match res.target_info.entry(target) {
std::collections::hash_map::Entry::Occupied(_) => (),
std::collections::hash_map::Entry::Vacant(place) => {
place.insert(TargetInfo::new(
res.config,
&res.requested_kinds,
&res.rustc,
kind,
)?);
}
}
}
}
Ok(res)
}
/// Returns a "short" name for the given kind, suitable for keying off

View File

@ -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(),

View File

@ -33,7 +33,7 @@ pub fn parse_unstable_flag(value: Option<&str>) -> Vec<String> {
/// 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<PathBuf> {
fn detect_sysroot_src_path(target_data: &RustcTargetData<'_>) -> CargoResult<PathBuf> {
if let Some(s) = env::var_os("__CARGO_TESTS_ONLY_SRC_ROOT") {
return Ok(s.into());
}

View File

@ -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<CompileKind, Vec<Unit>>,
global_mode: CompileMode,
target_data: &'a RustcTargetData,
target_data: &'a RustcTargetData<'cfg>,
profiles: &'a Profiles,
interner: &'a UnitInterner,
) -> CargoResult<UnitGraph> {

View File

@ -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 \

View File

@ -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<Target>,
default_kind: Option<CompileKind>,
forced_kind: Option<CompileKind>,
links: Option<String>,
warnings: Warnings,
exclude: Vec<String>,
@ -366,6 +368,8 @@ compact_debug! {
impl Manifest {
pub fn new(
summary: Summary,
default_kind: Option<CompileKind>,
forced_kind: Option<CompileKind>,
targets: Vec<Target>,
exclude: Vec<String>,
include: Vec<String>,
@ -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<CompileKind> {
self.default_kind
}
pub fn forced_kind(&self) -> Option<CompileKind> {
self.forced_kind
}
pub fn exclude(&self) -> &[String] {
&self.exclude
}
@ -503,6 +515,17 @@ impl Manifest {
})?;
}
if self.default_kind.is_some() || self.forced_kind.is_some() {
self.unstable_features
.require(Feature::per_package_target())
.chain_err(|| {
anyhow::format_err!(
"the `package.default-kind` and `package.forced-kind` \
manifest keys are unstable and may not work properly"
)
})?;
}
Ok(())
}

View File

@ -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) {

View File

@ -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,

View File

@ -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<Resolve>,
@ -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),

View File

@ -171,7 +171,7 @@ fn build_resolve_graph_r(
pkg_id: PackageId,
resolve: &Resolve,
package_map: &BTreeMap<PackageId, Package>,
target_data: &RustcTargetData,
target_data: &RustcTargetData<'_>,
requested_kinds: &[CompileKind],
) {
if node_map.contains_key(&pkg_id) {

View File

@ -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],

View File

@ -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<PackageId, &'a Package>,
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 {

View File

@ -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<Vec<String>>,
build: Option<StringOrBool>,
metabuild: Option<StringOrVec>,
#[serde(rename = "default-target")]
default_target: Option<String>,
#[serde(rename = "forced-target")]
forced_target: Option<String>,
links: Option<String>,
exclude: Option<Vec<String>>,
include: Option<Vec<String>>,
@ -1313,9 +1318,24 @@ impl TomlManifest {
}
}
let default_kind = project
.default_target
.as_ref()
.map(|t| CompileTarget::new(&*t))
.transpose()? // TODO: anyhow::Context isn't imported yet so I guess .context() isn't the right way to do it?
.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,

View File

@ -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)

View File

@ -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() {