Skip to content

Commit 97bc22f

Browse files
committed
Search cargo build directories before system for libraries
Regardless of crate search paths emitted, always prefer searching search paths pointing into the artifacts directory to those pointing outside. This way libraries built by Cargo are preferred even if the same library name exists in the system & a crate earlier in the build process emitted a system library path for searching.
1 parent c354b53 commit 97bc22f

File tree

5 files changed

+138
-17
lines changed

5 files changed

+138
-17
lines changed

src/cargo/core/compiler/build_runner/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,9 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
301301
.extend(output.env.iter().cloned());
302302

303303
for dir in output.library_paths.iter() {
304-
self.compilation.native_dirs.insert(dir.clone());
304+
self.compilation
305+
.native_dirs
306+
.insert(dir.clone().into_path_buf());
305307
}
306308
}
307309
Ok(self.compilation)

src/cargo/core/compiler/custom_build.rs

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
//! [`CompileMode::RunCustomBuild`]: super::CompileMode
3232
//! [instructions]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
3333
34-
use super::{fingerprint, BuildRunner, Job, Unit, Work};
34+
use super::{fingerprint, get_dynamic_search_path, BuildRunner, Job, Unit, Work};
3535
use crate::core::compiler::artifact;
3636
use crate::core::compiler::build_runner::UnitHash;
3737
use crate::core::compiler::fingerprint::DirtyReason;
@@ -46,6 +46,7 @@ use cargo_util::paths;
4646
use cargo_util_schemas::manifest::RustVersion;
4747
use std::collections::hash_map::{Entry, HashMap};
4848
use std::collections::{BTreeSet, HashSet};
49+
use std::ffi::OsStr;
4950
use std::path::{Path, PathBuf};
5051
use std::str::{self, FromStr};
5152
use std::sync::{Arc, Mutex};
@@ -74,11 +75,96 @@ pub enum Severity {
7475

7576
pub type LogMessage = (Severity, String);
7677

78+
/// This represents a path added to the library search path. We need to keep track of requests
79+
/// to add search paths within the cargo build directory separately from paths outside of Cargo.
80+
/// The reason is that we want to give precedence to linking against libraries within the Cargo
81+
/// build directory even if a similar library exists in the system (e.g. crate A adds /usr/lib
82+
/// to the search path and then a later build of crate B adds target/debug/... to satisfy
83+
/// it's request to link against the library B that it built, but B is also found in /usr/lib).
84+
///
85+
/// There's some nuance here because we want to preserve relative order of paths of the same type.
86+
/// For example, if the build process would in declaration order emit the following linker line:
87+
/// ```bash
88+
/// -L/usr/lib -Ltarget/debug/build/crate1/libs -L/lib -Ltarget/debug/build/crate2/libs)
89+
/// ```
90+
///
91+
/// we want the linker to actually receive:
92+
/// ```bash
93+
/// -Ltarget/debug/build/crate1/libs -Ltarget/debug/build/crate2/libs) -L/usr/lib -L/lib
94+
/// ```
95+
///
96+
/// so that the library search paths within the crate artifacts directory come first but retain
97+
/// relative ordering while the system library paths come after while still retaining relative
98+
/// ordering among them; ordering is the order they are emitted within the build process,
99+
/// not lexicographic order.
100+
///
101+
/// WARNING: Even though this type implements PartialOrd + Ord, this is a lexicographic ordering.
102+
/// The linker line will require an explicit sorting algorithm. PartialOrd + Ord is derived because
103+
/// BuildOutput requires it but that ordering is different from the one for the linker search path,
104+
/// at least today. It may be worth reconsidering & perhaps it's ok if BuildOutput doesn't have
105+
/// a lexicographic ordering for the library_paths? I'm not sure the consequence of that.
106+
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
107+
pub enum LibraryPath {
108+
/// The path is pointing within the output folder of the crate and takes priority over
109+
/// external paths when passed to the linker.
110+
CargoArtifact(PathBuf),
111+
/// The path is pointing outside of the crate's build location. The linker will always
112+
/// receive such paths after `CargoArtifact`.
113+
External(PathBuf),
114+
}
115+
116+
impl LibraryPath {
117+
fn new(p: PathBuf, script_out_dir: &Path) -> Self {
118+
let search_path = get_dynamic_search_path(&p);
119+
if search_path.starts_with(script_out_dir) {
120+
Self::CargoArtifact(p)
121+
} else {
122+
Self::External(p)
123+
}
124+
}
125+
126+
pub fn into_path_buf(self) -> std::path::PathBuf {
127+
match self {
128+
LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p,
129+
}
130+
}
131+
132+
pub fn display(&self) -> std::path::Display<'_> {
133+
match self {
134+
LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p.display(),
135+
}
136+
}
137+
}
138+
139+
impl AsRef<Path> for LibraryPath {
140+
fn as_ref(&self) -> &Path {
141+
match self {
142+
LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p,
143+
}
144+
}
145+
}
146+
147+
impl AsRef<PathBuf> for LibraryPath {
148+
fn as_ref(&self) -> &PathBuf {
149+
match self {
150+
LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p,
151+
}
152+
}
153+
}
154+
155+
impl AsRef<OsStr> for LibraryPath {
156+
fn as_ref(&self) -> &OsStr {
157+
match self {
158+
LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p.as_os_str(),
159+
}
160+
}
161+
}
162+
77163
/// Contains the parsed output of a custom build script.
78164
#[derive(Clone, Debug, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
79165
pub struct BuildOutput {
80166
/// Paths to pass to rustc with the `-L` flag.
81-
pub library_paths: Vec<PathBuf>,
167+
pub library_paths: Vec<LibraryPath>,
82168
/// Names and link kinds of libraries, suitable for the `-l` flag.
83169
pub library_links: Vec<String>,
84170
/// Linker arguments suitable to be passed to `-C link-arg=<args>`
@@ -884,10 +970,16 @@ impl BuildOutput {
884970
"rustc-flags" => {
885971
let (paths, links) = BuildOutput::parse_rustc_flags(&value, &whence)?;
886972
library_links.extend(links.into_iter());
887-
library_paths.extend(paths.into_iter());
973+
library_paths.extend(
974+
paths
975+
.into_iter()
976+
.map(|p| LibraryPath::new(p, script_out_dir)),
977+
);
888978
}
889979
"rustc-link-lib" => library_links.push(value.to_string()),
890-
"rustc-link-search" => library_paths.push(PathBuf::from(value)),
980+
"rustc-link-search" => {
981+
library_paths.push(LibraryPath::new(PathBuf::from(value), script_out_dir))
982+
}
891983
"rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
892984
if !targets.iter().any(|target| target.is_cdylib()) {
893985
log_messages.push((

src/cargo/core/compiler/mod.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ pub use self::compilation::{Compilation, Doctest, UnitOutput};
7979
pub use self::compile_kind::{CompileKind, CompileTarget};
8080
pub use self::crate_type::CrateType;
8181
pub use self::custom_build::LinkArgTarget;
82-
pub use self::custom_build::{BuildOutput, BuildScriptOutputs, BuildScripts};
82+
pub use self::custom_build::{BuildOutput, BuildScriptOutputs, BuildScripts, LibraryPath};
8383
pub(crate) use self::fingerprint::DirtyReason;
8484
pub use self::job_queue::Freshness;
8585
use self::job_queue::{Job, JobQueue, JobState, Work};
@@ -495,16 +495,39 @@ fn rustc(
495495
target: &Target,
496496
current_id: PackageId,
497497
) -> CargoResult<()> {
498+
let mut library_paths = vec![];
499+
500+
for key in build_scripts.to_link.iter() {
501+
let output = build_script_outputs.get(key.1).ok_or_else(|| {
502+
internal(format!(
503+
"couldn't find build script output for {}/{}",
504+
key.0, key.1
505+
))
506+
})?;
507+
library_paths.extend(output.library_paths.iter());
508+
}
509+
510+
// NOTE: This very intentionally does not use the derived ord from LibraryPath because we need to
511+
// retain relative ordering within the same type (i.e. not lexicographic). The use of a stable sort
512+
// is also important here because it ensures that paths of the same type retain the same relative
513+
// ordering (for an unstable sort to work here, the list would need to retain the idx of each element
514+
// and then sort by that idx when the type is equivalent.
515+
library_paths.sort_by_key(|p| match p {
516+
LibraryPath::CargoArtifact(_) => 0,
517+
LibraryPath::External(_) => 1,
518+
});
519+
520+
for path in library_paths.iter() {
521+
rustc.arg("-L").arg(path);
522+
}
523+
498524
for key in build_scripts.to_link.iter() {
499525
let output = build_script_outputs.get(key.1).ok_or_else(|| {
500526
internal(format!(
501527
"couldn't find build script output for {}/{}",
502528
key.0, key.1
503529
))
504530
})?;
505-
for path in output.library_paths.iter() {
506-
rustc.arg("-L").arg(path);
507-
}
508531

509532
if key.0 == current_id {
510533
if pass_l_flag {
@@ -650,7 +673,7 @@ fn add_plugin_deps(
650673
.get(*metadata)
651674
.ok_or_else(|| internal(format!("couldn't find libs for plugin dep {}", pkg_id)))?;
652675
search_path.append(&mut filter_dynamic_search_path(
653-
output.library_paths.iter(),
676+
output.library_paths.iter().map(AsRef::as_ref),
654677
root_output,
655678
));
656679
}

src/cargo/util/context/target.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{ConfigKey, ConfigRelativePath, GlobalContext, OptValue, PathAndArgs, StringList, CV};
2-
use crate::core::compiler::{BuildOutput, LinkArgTarget};
2+
use crate::core::compiler::{BuildOutput, LibraryPath, LinkArgTarget};
33
use crate::util::CargoResult;
44
use serde::Deserialize;
55
use std::collections::{BTreeMap, HashMap};
@@ -167,7 +167,9 @@ fn parse_links_overrides(
167167
let flags = value.string(key)?;
168168
let whence = format!("target config `{}.{}` (in {})", target_key, key, flags.1);
169169
let (paths, links) = BuildOutput::parse_rustc_flags(flags.0, &whence)?;
170-
output.library_paths.extend(paths);
170+
output
171+
.library_paths
172+
.extend(paths.into_iter().map(LibraryPath::External));
171173
output.library_links.extend(links);
172174
}
173175
"rustc-link-lib" => {
@@ -178,9 +180,11 @@ fn parse_links_overrides(
178180
}
179181
"rustc-link-search" => {
180182
let list = value.list(key)?;
181-
output
182-
.library_paths
183-
.extend(list.iter().map(|v| PathBuf::from(&v.0)));
183+
output.library_paths.extend(
184+
list.iter()
185+
.map(|v| PathBuf::from(&v.0))
186+
.map(LibraryPath::External),
187+
);
184188
}
185189
"rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
186190
let args = extra_link_args(LinkArgTarget::Cdylib, key, value)?;

tests/testsuite/build_script.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6120,7 +6120,7 @@ fn linker_search_path_preference() {
61206120

61216121
p.cargo("build -v").with_stderr_data(str![[r#"
61226122
...
6123-
[RUNNING] `rustc --crate-name foo [..] -L /usr/lib -L [ROOT]/foo/target/debug/build/foo-[HASH]/out/libs2 -L /lib -L [ROOT]/foo/target/debug/build/foo-[HASH]/out/libs1`
6123+
[RUNNING] `rustc --crate-name foo [..] -L [ROOT]/foo/target/debug/build/foo-[HASH]/out/libs2 -L [ROOT]/foo/target/debug/build/foo-[HASH]/out/libs1 -L /usr/lib -L /lib`
61246124
...
61256125
"#]]).run();
6126-
}
6126+
}

0 commit comments

Comments
 (0)