Skip to content

cargo fmt fails to format code when using symlinks #6555

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
ckoehne opened this issue May 6, 2025 · 2 comments
Open

cargo fmt fails to format code when using symlinks #6555

ckoehne opened this issue May 6, 2025 · 2 comments

Comments

@ckoehne
Copy link

ckoehne commented May 6, 2025

When symlinking a file, it looks like cargo fmt isn't able to properly resolve modules. It causes cargo fmt to ignore certain modules referenced by that symlinked file.

Here's a minimal reproducable example:

.
├── Cargo.toml
├── linked
│   ├── Cargo.toml
│   └── src
│       ├── main.rs -> ../../plain/src/main.rs
│       └── unformatted.rs
└── plain
    ├── Cargo.toml
    └── src
        ├── main.rs
        └── unformatted.rs
==> ./Cargo.toml <==
[workspace]
resolver = "2"
members = [ "plain", "linked" ]

==> ./linked/Cargo.toml <==
[package]
name = "linked"
version = "0.1.0"
edition = "2024"

[dependencies]

==> ./linked/src/unformatted.rs <==
pub fn unformatted() { println!("Hello from linked!"); }

==> ./plain/Cargo.toml <==
[package]
name = "plain"
version = "0.1.0"
edition = "2024"

[dependencies]

==> ./plain/src/main.rs <==
mod unformatted;

fn main() {
    unformatted::unformatted();
}

==> ./plain/src/unformatted.rs <==
pub fn unformatted() { println!("Hello from plain!"); }

cargo fmt only formats the plain file by default. You have to explicitly pass linked/src/unformatted.rs to cargo fmt to format it.

> cargo fmt --check
Diff in plain/src/unformatted.rs:1:
-pub fn unformatted() { println!("Hello from plain!"); }
+pub fn unformatted() {
+    println!("Hello from plain!");
+}
 
> cargo fmt --check -- linked/src/unformatted.rs
Diff in plain/src/unformatted.rs:1:
-pub fn unformatted() { println!("Hello from plain!"); }
+pub fn unformatted() {
+    println!("Hello from plain!");
+}
 
Diff in linked/src/unformatted.rs:1:
-pub fn unformatted() { println!("Hello from linked!"); }
+pub fn unformatted() {
+    println!("Hello from linked!");
+}

The rust code itself is working as expected:

> cargo run --manifest-path linked/Cargo.toml
Hello from linked!
> cargo run --manifest-path plain/Cargo.toml
Hello from plain!
@calebcartwright
Copy link
Member

It's not entirely clear to me what your exact setup is, nor what behavior you're expecting (are you expecting to be able to run cargo fmt in the root directory and have linked/src/unformatted.rs also be formatted?)

cargo fmt is just a shorthand for invoking rustfmt against the entry point files in scope (where that scope is determined by the directory from which you invoke cargo fmt, and any additional flags like --manifest-path).

As such I assume that if you ran cargo fmt with the --verbose flag from the root directory that you'll probably see the plain files getting two passes (if not then please let us know). What's the behavior when you run cargo fmt with the same --manifest-path flags you're using in your cargo run commands?

@ckoehne
Copy link
Author

ckoehne commented May 9, 2025

I've create a repo to share my setup: https://github.com/ckoehne/rustfmt-symlink-bug/tree/master

Yes, I'd expect that cargo fmt called from the root directory formats linked/src/unformatted.rs too. Here's my output from cargo fmt with --verbose and --manifest-path flags set:

> cargo fmt --verbose
[bin (2024)] "/home/corvink/tmp/rust/fmt-bug/plain/src/main.rs"
rustfmt --edition 2024 /home/corvink/tmp/rust/fmt-bug/plain/src/main.rs
> cargo fmt --manifest-path plain/Cargo.toml --verbose
[bin (2024)] "/home/corvink/tmp/rust/fmt-bug/plain/src/main.rs"
rustfmt --edition 2024 /home/corvink/tmp/rust/fmt-bug/plain/src/main.rs
> cargo fmt --manifest-path linked/Cargo.toml --verbose
[bin (2024)] "/home/corvink/tmp/rust/fmt-bug/plain/src/main.rs"
rustfmt --edition 2024 /home/corvink/tmp/rust/fmt-bug/plain/src/main.rs

As you can see, when specifying linked/Cargo.toml as manifest, it tries to format plain/src/main.rs instead of linked/src/main.rs. By removing the symlink and copying the file, it works as expected:

> ls -l linked/src/main.rs
lrwxrwxrwx 1 corvink corvink 23 May  6 08:59 linked/src/main.rs -> ../../plain/src/main.rs
> rm linked/src/main.rs 
> cp plain/src/main.rs linked/src/main.rs
> cargo fmt --verbose
[bin (2024)] "/home/corvink/tmp/rust/fmt-bug/linked/src/main.rs"
[bin (2024)] "/home/corvink/tmp/rust/fmt-bug/plain/src/main.rs"
rustfmt --edition 2024 /home/corvink/tmp/rust/fmt-bug/linked/src/main.rs /home/corvink/tmp/rust/fmt-bug/plain/src/main.rs
> cargo fmt --manifest-path plain/Cargo.toml --verbose
[bin (2024)] "/home/corvink/tmp/rust/fmt-bug/plain/src/main.rs"
rustfmt --edition 2024 /home/corvink/tmp/rust/fmt-bug/plain/src/main.rs
> cargo fmt --manifest-path linked/Cargo.toml --verbose
[bin (2024)] "/home/corvink/tmp/rust/fmt-bug/linked/src/main.rs"
rustfmt --edition 2024 /home/corvink/tmp/rust/fmt-bug/linked/src/main.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants