Linker options in Cargo.toml

I deal with custom linker scripts a lot (more than I'd like, really...). Of the "weird embedded things", it simultaneously feels like one of the most common and most painful ones I have to deal with. Having to fuss around with RUSTFLAGS, especially when you're in a workspace with lots of different linker scripts, is a great way to kill code caching (though cargo rustc can sometimes be used to work around this). This also often means you need to wrap Cargo in another build system to set up the correct flags.

What I want is something morally equivalent to this:

[[bin]]
name = "my_kernel"
linker_script = "link.ld"

which would cause Cargo to pass -Clinker-args -T,link.ld to rustc. Of course, this assumes a GNU-flavored linker, and I honestly have no background in linkers on other platforms (I am clueless about how MSVC handles this kind of thing); I would sort of assume this is a "best effort based on the linker flavor" thing. I could imagine having separate keys for each flavor, or something like linker_script = { gnu = "link.ld" } or similar.

(I think it would be amazing if a link.ld was detected much like main.rs and build.rs are, but that feels much more like a "nice-to-have".)

1 Like

That sounds great to me.

For future compatibility, I'd suggest ld-script, and then other linkers can use somethingelse-script.

Otherwise, that seems entirely reasonable, and I'd happily support a Cargo patch to that effect. I've added it to the agenda for this week's Cargo team meeting.

To the best of my knowledge, the MSVC linker handles some very limited cases like base address and entry point, and there's a separate mechanism for definition files to handle symbols; beyond that, from what I've seen, people who need that level of control either write standalone post-processing scripts for object files or they use an ld-compatible linker script with clang or gcc.

I'd love to see that as well. I'd suggest including that from the start, to set a convention for what the script should be called.

In workspaces, link.ld should live within each crate like build.rs does. (If people want to share a linker script across crates in a workspace, which might make sense for some kinds of loadable modules or plugins, this could use the workspace deduplication mechanism.)

I think it would be good to have a more general link.rs script that can print out arguments which are in some way sent to the linker. I'm not against directly supporting the linker specific scripts either but, speaking from the Windows side of things, a more general approach would be useful because it's very common for people to want to change Rust's defaults. Currently doing so is very awkward.

I find it a odd in that it seems like a big departure as very little precedent for embedding paths to files, in Cargo.toml, the closest thing I know of is the path="dir", e.g. in a [[bin]], you specify a name foo rather than a foo.rs.

I guess the first thing that comes to mind is to drop the .ld so that various linkers can look up the path to the script with a different extension?

There is also a build-script="build.rs" key (forget the exact key right now) which takes a path to a Rust file.

1 Like

That doesn't make it unnecessary to have a linker script, and it would make it more complex to handle the common case.

We certainly should allow build.rs scripts to supply arguments to the linker, and specifically to supply a linker script filename (which would allow generating the linker script). But I still think we should enable the common case by having a declarative Cargo.toml option, and a default filename.

I don't think we should assume that some other platform's linker-script mechanism works similarly enough that it suffices to use a file with the same name and a different extension. I would rather see ld-script = "link.ld" and otherlink-script = "link.ols", rather than making assumptions about naming.

build = "build.rs", yes. That'd be the model to work from: a default name and location, the ability to override, and a path relative to the manifest.

Oh definitely. But I mean if it's specifically for ld scripts then that's inherently saying the common case is GNU compatible linkers. For sure, that's probably totally true but I think that it should be made clear that's what's being said.

My bias here is that I'd like a more ergonomic solution for Windows than having to specify linker arguments on the command line when I want to target e.g. a particular binary. Using Cargo is honestly a bit of a pain point here. I end up either having to wrap cargo rustc or call rustc directly.

I guess what I'm saying is it would be great if there was a generic "linker script" file that rust tooling understood. But I'm not at all suggesting this should be a blocker on supporting GNU linker scripts.

I would like to see a better way to handle linker arguments as well. I don't think that should be a separate script, though. I think that should be in build.rs.

@josh Do you think composing a Cargo RFC for this is worthwhile?

Let's see how the discussion goes at tomorrow's Cargo meeting. It may need an RFC, or it may just need a PR.

We discussed this in the Cargo meeting this week.

Our consensus was that we definitely want to support linker scripts, and also support other linker options.

We already have support in progress for doing this via build scripts: https://github.com/rust-lang/cargo/pull/8441 . That got stalled slightly, but we're reviving it, and @alexcrichton plans to r+ that PR this week. Once that goes in, we'll have support in nightly for emitting linker arguments for anything a crate can build. That would include linker scripts (whether universal or target-specific), as well as options like --nmagic or -s.

We felt that that support should fully address this issue, without additionally supporting a subset of cases (those that don't need target-specific scripts or generated scripts) via static information in Cargo.toml.

We'd appreciate help testing https://github.com/rust-lang/cargo/pull/8441 , confirming that it addresses the issue here, providing experience reports, and helping us bring it to stable Rust and stable Cargo.

That should help! Unfortunately I don't have any projects that directly benefit, due to me needing to pass a custom --target JSON file as well... but I'll try it out when I get a chance.