Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for target.'cfg(..)'.linker #12535

Merged
merged 3 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions src/cargo/core/compiler/build_context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crate::util::errors::CargoResult;
use crate::util::interning::InternedString;
use crate::util::Rustc;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;

mod target_info;
pub use self::target_info::{
Expand Down Expand Up @@ -120,15 +119,6 @@ impl<'a, 'cfg> BuildContext<'a, 'cfg> {
&self.target_data.rustc
}

/// Gets the user-specified linker for a particular host or target.
pub fn linker(&self, kind: CompileKind) -> Option<PathBuf> {
self.target_data
.target_config(kind)
.linker
.as_ref()
.map(|l| l.val.clone().resolve_program(self.config))
}

/// Gets the host architecture triple.
///
/// For example, x86_64-unknown-linux-gnu, would be
Expand Down
50 changes: 50 additions & 0 deletions src/cargo/core/compiler/compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ pub struct Compilation<'cfg> {
primary_rustc_process: Option<ProcessBuilder>,

target_runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>,
/// The linker to use for each host or target.
target_linkers: HashMap<CompileKind, Option<PathBuf>>,
}

impl<'cfg> Compilation<'cfg> {
Expand Down Expand Up @@ -150,6 +152,13 @@ impl<'cfg> Compilation<'cfg> {
.chain(Some(&CompileKind::Host))
.map(|kind| Ok((*kind, target_runner(bcx, *kind)?)))
.collect::<CargoResult<HashMap<_, _>>>()?,
target_linkers: bcx
.build_config
.requested_kinds
.iter()
.chain(Some(&CompileKind::Host))
.map(|kind| Ok((*kind, target_linker(bcx, *kind)?)))
.collect::<CargoResult<HashMap<_, _>>>()?,
})
}

Expand Down Expand Up @@ -221,6 +230,11 @@ impl<'cfg> Compilation<'cfg> {
self.target_runners.get(&kind).and_then(|x| x.as_ref())
}

/// Gets the user-specified linker for a particular host or target.
pub fn target_linker(&self, kind: CompileKind) -> Option<PathBuf> {
self.target_linkers.get(&kind).and_then(|x| x.clone())
}

/// Returns a [`ProcessBuilder`] appropriate for running a process for the
/// target platform. This is typically used for `cargo run` and `cargo
/// test`.
Expand Down Expand Up @@ -441,3 +455,39 @@ fn target_runner(
)
}))
}

/// Gets the user-specified linker for a particular host or target from the configuration.
fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> {
weihanglo marked this conversation as resolved.
Show resolved Hide resolved
// Try host.linker and target.{}.linker.
if let Some(path) = bcx
.target_data
.target_config(kind)
.linker
.as_ref()
.map(|l| l.val.clone().resolve_program(bcx.config))
{
return Ok(Some(path));
}

// Try target.'cfg(...)'.linker.
let target_cfg = bcx.target_data.info(kind).cfg();
let mut cfgs = bcx
.config
.target_cfgs()?
.iter()
.filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
.filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
let matching_linker = cfgs.next();
if let Some((key, linker)) = cfgs.next() {
anyhow::bail!(
"several matching instances of `target.'cfg(..)'.linker` in configurations\n\
first match `{}` located in {}\n\
second match `{}` located in {}",
matching_linker.unwrap().0,
matching_linker.unwrap().1.definition,
key,
linker.definition
);
}
Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.config)))
}
2 changes: 1 addition & 1 deletion src/cargo/core/compiler/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
unit: unit.clone(),
args,
unstable_opts,
linker: self.bcx.linker(unit.kind),
linker: self.compilation.target_linker(unit.kind).clone(),
script_meta,
env: artifact::get_env(&self, self.unit_deps(unit))?,
});
Expand Down
7 changes: 2 additions & 5 deletions src/cargo/core/compiler/custom_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,8 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Job> {
cmd.env(&var, value);
}

if let Some(linker) = &bcx.target_data.target_config(unit.kind).linker {
cmd.env(
"RUSTC_LINKER",
linker.val.clone().resolve_program(bcx.config),
);
if let Some(linker) = &cx.compilation.target_linker(unit.kind) {
cmd.env("RUSTC_LINKER", linker);
}

if let Some(links) = unit.pkg.manifest().links() {
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/core/compiler/fingerprint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1426,7 +1426,7 @@ fn calculate_normal(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Finger
let m = unit.pkg.manifest().metadata();
let metadata = util::hash_u64((&m.authors, &m.description, &m.homepage, &m.repository));
let mut config = StableHasher::new();
if let Some(linker) = cx.bcx.linker(unit.kind) {
if let Some(linker) = cx.compilation.target_linker(unit.kind) {
linker.hash(&mut config);
}
if unit.mode.is_doc() && cx.bcx.config.cli_unstable().rustdoc_map {
Expand Down
5 changes: 4 additions & 1 deletion src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1117,7 +1117,10 @@ fn build_base_args(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder, unit: &Unit)
cmd,
"-C",
"linker=",
bcx.linker(unit.kind).as_ref().map(|s| s.as_ref()),
cx.compilation
.target_linker(unit.kind)
.as_ref()
.map(|s| s.as_ref()),
);
if incremental {
let dir = cx.files().layout(unit.kind).incremental().as_os_str();
Expand Down
1 change: 1 addition & 0 deletions src/cargo/util/config/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::path::PathBuf;
pub struct TargetCfgConfig {
pub runner: OptValue<PathAndArgs>,
pub rustflags: OptValue<StringList>,
pub linker: OptValue<ConfigRelativePath>,
// This is here just to ignore fields from normal `TargetConfig` because
// all `[target]` tables are getting deserialized, whether they start with
// `cfg(` or not.
Expand Down
6 changes: 6 additions & 0 deletions src/doc/src/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,12 @@ This option is deprecated and unused.
Specifies the linker which is passed to `rustc` (via [`-C linker`]) when the
[`<triple>`] is being compiled for. By default, the linker is not overridden.

##### `target.<cfg>.linker`
This is similar to the [target linker](#targettriplelinker), but using
a [`cfg()` expression]. If both a [`<triple>`] and `<cfg>` runner match,
the `<triple>` will take precedence. It is an error if more than one
`<cfg>` runner matches the current target.

##### `target.<triple>.runner`
* Type: string or array of strings ([program path with args])
* Default: none
Expand Down
37 changes: 37 additions & 0 deletions tests/testsuite/build_script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,43 @@ fn custom_build_env_var_rustc_linker() {
p.cargo("build --target").arg(&target).run();
}

// Only run this test on linux, since it's difficult to construct
// a case suitable for all platforms.
// See:https://github.com/rust-lang/cargo/pull/12535#discussion_r1306618264
#[cargo_test]
#[cfg(target_os = "linux")]
fn custom_build_env_var_rustc_linker_with_target_cfg() {
if cross_compile::disabled() {
return;
}

let target = cross_compile::alternate();
let p = project()
.file(
".cargo/config",
r#"
[target.'cfg(target_pointer_width = "32")']
linker = "/path/to/linker"
"#,
)
.file(
"build.rs",
r#"
use std::env;
fn main() {
assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
}
"#,
)
.file("src/lib.rs", "")
.build();

// no crate type set => linker never called => build succeeds if and
// only if build.rs succeeds, despite linker binary not existing.
p.cargo("build --target").arg(&target).run();
}

#[cargo_test]
fn custom_build_env_var_rustc_linker_bad_host_target() {
let target = rustc_host();
Expand Down
88 changes: 87 additions & 1 deletion tests/testsuite/tool_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,93 @@ fn pathless_tools() {
.run();
}

// can set a custom linker via `target.'cfg(..)'.linker`
#[cargo_test]
fn custom_linker_cfg() {
let foo = project()
.file("Cargo.toml", &basic_lib_manifest("foo"))
.file("src/lib.rs", "")
.file(
".cargo/config",
r#"
[target.'cfg(not(target_os = "none"))']
linker = "nonexistent-linker"
"#,
)
.build();

foo.cargo("build --verbose")
.with_stderr(
"\
[COMPILING] foo v0.5.0 ([CWD])
[RUNNING] `rustc [..] -C linker=nonexistent-linker [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
}

// custom linker set via `target.$triple.linker` have precede over `target.'cfg(..)'.linker`
#[cargo_test]
fn custom_linker_cfg_precedence() {
let target = rustc_host();

let foo = project()
.file("Cargo.toml", &basic_lib_manifest("foo"))
.file("src/lib.rs", "")
.file(
".cargo/config",
&format!(
r#"
[target.'cfg(not(target_os = "none"))']
linker = "ignored-linker"
[target.{}]
linker = "nonexistent-linker"
"#,
target
),
)
.build();

foo.cargo("build --verbose")
.with_stderr(
"\
[COMPILING] foo v0.5.0 ([CWD])
[RUNNING] `rustc [..] -C linker=nonexistent-linker [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
}

#[cargo_test]
fn custom_linker_cfg_collision() {
let foo = project()
.file("Cargo.toml", &basic_lib_manifest("foo"))
.file("src/lib.rs", "")
.file(
".cargo/config",
r#"
[target.'cfg(not(target_arch = "avr"))']
linker = "nonexistent-linker1"
[target.'cfg(not(target_os = "none"))']
linker = "nonexistent-linker2"
"#,
)
.build();

foo.cargo("build --verbose")
.with_status(101)
.with_stderr(&format!(
"\
[ERROR] several matching instances of `target.'cfg(..)'.linker` in configurations
Rustin170506 marked this conversation as resolved.
Show resolved Hide resolved
first match `cfg(not(target_arch = \"avr\"))` located in [..]/foo/.cargo/config
second match `cfg(not(target_os = \"none\"))` located in [..]/foo/.cargo/config
",
))
.run();
}

#[cargo_test]
fn absolute_tools() {
let target = rustc_host();
Expand Down Expand Up @@ -393,7 +480,6 @@ fn cfg_ignored_fields() {
[WARNING] unused key `ar` in [target] config table `cfg(not(target_os = \"none\"))`
[WARNING] unused key `foo` in [target] config table `cfg(not(target_os = \"none\"))`
[WARNING] unused key `invalid` in [target] config table `cfg(not(target_os = \"none\"))`
[WARNING] unused key `linker` in [target] config table `cfg(not(target_os = \"none\"))`
[CHECKING] foo v0.0.1 ([..])
[FINISHED] [..]
",
Expand Down