Skip to content

Don't canonicalize crate paths #139834

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
21 changes: 15 additions & 6 deletions compiler/rustc_metadata/src/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,12 +427,21 @@ impl<'a> CrateLocator<'a> {

let (rlibs, rmetas, dylibs) =
candidates.entry(hash.to_string()).or_default();
let path =
try_canonicalize(&spf.path).unwrap_or_else(|_| spf.path.to_path_buf());
if seen_paths.contains(&path) {
continue;
};
seen_paths.insert(path.clone());
{
// As a perforamnce optimisation we canonicalize the path and skip
// ones we've already seeen. This allows us to ignore crates
// we know are exactual equal to ones we've already found.
// Going to the same crate through different symlinks does not change the result.
let path = try_canonicalize(&spf.path)
.unwrap_or_else(|_| spf.path.to_path_buf());
if seen_paths.contains(&path) {
continue;
};
seen_paths.insert(path);
}
// Use the original path (potentially with unresolved symlinks),
// filesystem code should not care, but this is nicer for diagnostics.
let path = spf.path.to_path_buf();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: Can you add comments here? I envision something like this:

                        {
                            // Use canonical path to check if we already processed it.
                            // Going through different symlinks does not change the result.
                            let path =
                                try_canonicalize(&spf.path).unwrap_or_else(|_| spf.path.to_path_buf());
                            if seen_paths.contains(&path) {
                                continue;
                            };
                            seen_paths.insert(path);
                        }
                        // Use the original path (potentially with unresolved symlinks),
                        // filesystem code should not care, but this is nicer for diagnostics.
                        let path = spf.path.to_path_buf();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Does this also make the error not reproducible if case there are 2 symlinks? (i.e. you could go from either of them first, the second is ignored)

Copy link
Member Author

@ChrisDenton ChrisDenton Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's as deterministic as our Vec<SearchPath> of library paths, so it should always pick the first library path passed in that resolves to the same directory. Changing the library order passed in would change the diagnostic. Though I don't think that's too different from other diagnostics that depend on the order of things passed in?

Aside: I am slightly uncertain if we need to use canonicalise at all. If two rlibs, for example, are byte-for-byte equal then we should treat them as not conflicting, whether that's due to being a symlink, hard link, copy or a deterministic rebuild. So skipping directories is just a potential performance optimisation. Though I don't know if it has that much impact in practice or if using same-file (thus skipping potentially more expensive checks) would recover most performance loss, if any. I've not looked into it so didn't want to touch that here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense

match kind {
CrateFlavor::Rlib => rlibs.insert(path, search_path.kind),
CrateFlavor::Rmeta => rmetas.insert(path, search_path.kind),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#![crate_name = "crateresolve1"]
#![crate_type = "lib"]

pub fn f() -> isize {
10
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#![crate_name = "crateresolve1"]
#![crate_type = "lib"]

pub fn f() -> isize {
20
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extern crate crateresolve1;

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error[E0464]: multiple candidates for `rlib` dependency `crateresolve1` found
--> multiple-candidates.rs:1:1
|
LL | extern crate crateresolve1;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: candidate #1: ./mylibs/libcrateresolve1-1.rlib
= note: candidate #2: ./mylibs/libcrateresolve1-2.rlib

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0464`.
34 changes: 34 additions & 0 deletions tests/run-make/crate-loading-multiple-candidates/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//@ needs-symlink
//@ ignore-cross-compile

// Tests that the multiple candidate dependencies diagnostic prints relative
// paths if a relative library path was passed in.

use run_make_support::{bare_rustc, diff, rfs, rustc};

fn main() {
// Check that relative paths are preserved in the diagnostic
rfs::create_dir("mylibs");
rustc().input("crateresolve1-1.rs").out_dir("mylibs").extra_filename("-1").run();
rustc().input("crateresolve1-2.rs").out_dir("mylibs").extra_filename("-2").run();
check("./mylibs");

// Check that symlinks aren't followed when printing the diagnostic
rfs::rename("mylibs", "original");
rfs::symlink_dir("original", "mylibs");
check("./mylibs");
}

fn check(library_path: &str) {
let out = rustc()
.input("multiple-candidates.rs")
.library_search_path(library_path)
.ui_testing()
.run_fail()
.stderr_utf8();
diff()
.expected_file("multiple-candidates.stderr")
.normalize(r"\\", "/")
.actual_text("(rustc)", &out)
.run();
}
Loading