Skip to content

Commit

Permalink
Auto merge of #12535 - hi-rustin:rustin-patch-linker, r=weihanglo
Browse files Browse the repository at this point in the history
Add support for `target.'cfg(..)'.linker`
  • Loading branch information
bors committed Aug 28, 2023
2 parents b8df7aa + c9cc3b9 commit bfa04bb
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 19 deletions.
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 @@ -442,3 +456,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>> {
// 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 @@ -1099,6 +1099,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
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

0 comments on commit bfa04bb

Please sign in to comment.