-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Make Cargo aware of standard library dependencies #1133
Changes from 6 commits
7f3d678
4c0ea2a
eee7dba
9da1b02
630ac97
a7425e6
37e9246
d549d15
b3476e1
c22463f
c257f34
4a8bf9b
4b4d8a3
1ee980f
f0a0b3b
3422763
8392c4a
ffc442b
1c53ae1
cc8a1f2
71ba640
9a61baa
89a461a
4656eb9
984bcb5
af9442b
d16f8c3
77be419
afae118
a1f8ab1
2334770
61cd8ca
fac0c9b
a062095
bbf1ec6
81a923a
51cfdb6
a36a46b
e6aae38
7e73557
f7fd3df
b327075
d38daad
f8859d5
d208a5f
cb67deb
e8b1aa2
477d8bc
b9aa2da
26e1b6e
d35237f
28353b2
44f1e84
2ea3e83
324e225
02889b8
bbc503b
b1e1b4b
50a0862
e8f14b7
c501fd9
2719c44
54aa001
2774668
d85091a
ee4104f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
- Feature Name: cargo_libstd_awareness | ||
- Start Date: 2015-05-26 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
|
||
Currently, Cargo doesn't know whether packages depend on libstd. This makes Cargo unsuitable for | ||
packages that need a cross-compiled or custom libstd, or otherwise depend on crates with the same | ||
names as libstd and the crates behind the facade. The proposed fixes also open the door to a future | ||
where libstd can be Cargoized. | ||
|
||
|
||
# Motivation | ||
|
||
First some background. The current situation seems to be more of an accident of `rustc`'s pre-Cargo | ||
history than an explicit design decision. Cargo passes the location and name of all depended-on | ||
crates to `rustc`. This method is good for a number of reasons stemming from its fine granularity, | ||
such as: | ||
|
||
- No undeclared dependencies can be used | ||
|
||
- Conversely, `rustc` can warn against *unused* declared dependencies | ||
|
||
- Crate/symbol names are frobbed so that packages with the overlapping names don't conflict | ||
|
||
|
||
However rather than passing in libstd and its deps, Cargo lets the compiler look for them as need in | ||
the compiler's sysroot [specifically `<sysroot>/lib/<target>`]. This is quite coarse in comparison, | ||
and we loose all the advantages of the previous method: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
|
||
- Packages may link or not link against libs in that directory as they please, with Cargo being | ||
none the wiser. | ||
|
||
- Cargo-built crates with the same name as those in there will collide, as the sysroot libs don't | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't quite true, for example There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I'll need to update my code so I can see why the build is failing, or whether it does today. |
||
have their names frobbed. | ||
|
||
- Cross compiling may fail at build-time (as opposed to the much shorter | ||
"gather-dependencies-time") because of missing packages | ||
|
||
|
||
Cargo doesn't look inside the sysroot to see what is or isn't there, but it would hardly help if it | ||
did, because it doesn't know what any package needs. Assuming all packages need libstd, for example, | ||
means Cargo just flat-out won't build freestanding packages that just use libcore on a platform that | ||
doesn't support libstd. | ||
|
||
For an anecdote: in https://github.com/RustOS-Fork-Holding-Ground I tried to rig up Cargo to cross | ||
compile libstd for me. Since I needed to use an unstable compiler anyways, it was possible in | ||
principle to build absolutely everything I needed with the same `rustc` version. Because of some | ||
trouble with Cargo and target JSONs, I didn't use a custom target specification, and just used | ||
`x86_64-gnu-linux`, meaning that depending on platform I was compiling on, I may or may have been | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/x86_64-gnu-linux/x86_64-unknown-linux-gnu/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, thanks! |
||
cross-compiling. In the case where I wasn't, I couldn't complete the build because `rustc` | ||
complained about the libstd I was building overlapping with the libstd in the sysroot. | ||
|
||
For these reasons, most freestanding projects I know of avoid Cargo altogether, and just include | ||
submodule rust and run make in that. Cargo can still be used if one manages to get the requisite | ||
libraries in the sysroot. But this is a tedious operation that individual projects shouldn't need to | ||
reimplement, and one that has serious security implications if the normal libstd is modified. | ||
|
||
The fundamental plan proposed in this RFC is to make sure that anything Cargo builds never blindly | ||
links against libraries in the sysroot. This is achieved by making Cargo aware of all dependencies, | ||
including those libstd or its backing crates. That way, these problems are avoided. | ||
|
||
For the record, I first raised this issue [here](https://github.com/rust-lang/Cargo/issues/1096). | ||
|
||
|
||
# Detailed design | ||
|
||
The only new interface proposed is a boolean field in `Cargo.toml` specifying that the package does | ||
not depend on libstd by default. Note that this is technically orthogonal to Rust's `no_std`, as one | ||
might want to `use` their own build of libstd by default, or implicitly depend on it but not | ||
glob-import the prelude. To disambiguate, this field is called `implicit-deps`; please, go ahead and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at this from another angle, the only implicitly available crate that is available in stable Rust is Just a note that we have very few constraints today (just the name "std"), and we can do whatever we like with the other deps. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused, I think the semantics you are describing is exactly what I proposed. I went with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yes we're definitely thinking of the same thing, I was just wondering if the name I agree there are more crates that can be implicitly depended upon, but none of them are stable today, so we may not need to consider them. I think I was just somewhat startled at how this may imply that implicit dependencies are allowed from anywhere (when it's in fact just the sysroot) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. Somebody beginning Rust, or beginning just even unstablized Rust, has no idea what dependencies would normally be implicit. One the other hand Another option is to change it so that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another, more concise phrasing might be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not bad! I tried to think of a short name to capture all that, but came up short. |
||
bikeshead the name. `implicit-deps` is true by default to maintain compatibility with existing | ||
packages. When true, "std" will be implicitly appended to the list of dependencies. | ||
|
||
When Cargo sees a package name it cannot resolve, it will query `rustc` for the default sysroot, and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you clarify what this means for "a package name it cannot resolve"? For example Cargo does not attempt to resolve the name "std" in any way today, so I'm not sure where this sort of resolution failure will start from. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading a little more, my interpretation is that you're proposing that a crate explicitly declares that it depends on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically, I want it so implicit-deps = false;
[dependencies]
core = "*" # Or some more appropriate version specifier
alloc = "*"
# ...other crates behind the facade....
std = "*" and implicit-deps = true; and # implicit-deps is true by default all mean the same thing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm ok, your first snippet has a bit of a different interpretation because it means the dependencies like I think the reason I'm somewhat uneasy to add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmm, this RFC changes the way cargo works so that depended-on crates not on crates.io are looked for in the syroot instead, precisely so we can continue distributing those crates the same way for the time being. This allows:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see what this is saying now, although unfortunately I feel like that's a little too much magic going on under the hood. Cargo understands the "source" for any particular package, and it needs to understand if that source is crates.io or the sysroot ahead of time. Along those lines I think that this needs to have some new source syntax, such as: [dependencies]
std = { rustc-sysroot = true } There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While yes, that does make Cargo's life easier, does this information really belong in the package metadata? A package just cares what version std is, not how cargo obtained it. Also, if we switch to deploying these crates via crates.io before the end of 1.0, we wouldn't want packages to break because they mandated that std must come from the sysroot. That said, I'd still rather have that than the status quo. IIRC local packages (with cargo config) override no matter the version so this doesn't prohibit my third bullet point in my previous post. |
||
look inside to see if it can find a matching rlib. [It is necessary to query `rustc` because the | ||
`rustc` directory layout is not stabilized and `rustc` and Cargo are versioned independently. The | ||
same version issues make giving a Cargo a whitelist of potential standard library crate-names | ||
risky.] If a matching rlib is successful found, Cargo will copy it (or simlink it) into the | ||
project's build directly as if it built the rlib. Each rlib in the sysroot must be paired with some | ||
sort of manifest listing its dependencies, so Cargo can copy those too. | ||
|
||
`rustc` will have a new `--use-sysroot=<true|false>` flag. When Cargo builds a package, it will | ||
always pass `--use-sysroot=false` to `rustc`, as any rlibs it needs will have been copied to the | ||
build directory. Cargo can and will then pass those rlibs directly just as it does with normal Cargo | ||
deps. | ||
|
||
If Cargo cannot find the libraries it needs in the sysroot, or a library's dependency manifest is | ||
missing, it will complain that the standard libraries needed for the current job are missing and | ||
give up. | ||
|
||
## Future Compatibility | ||
|
||
In the future, rather than giving up if libraries are missing Cargo could attempt to download them | ||
from some build cache. In the farther future, the stdlib libraries may be Cargoized, and Cargo able | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah one point I forgot about previously, which is probably pretty relevant to this, is: the compiler can only link against libraries it previously built. This means that we would need a build cache per-revision of the compiler, which unfortunately makes this much more infeasible :( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, we over at NixOS maintain a build cache for Haskell and it works. If you only focus the slower release channels, and prioritize popular packages, it can still be useful. The idea of a stable ABI scares me, but if/when it happens, that problem goes away too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yeah we could definitely pre-cache builds of std for each official release of the compiler, for example, but it may want to be mentioned here as a potential downside. For example all custom builds of the compiler (e.g. nightlies) will not have access to pre-built archives. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still don't see the downside. Today, the compiler and library are built together, so we are already building and storing std for each prebuilt compiler. Whether or not std is downloaded with a prebuilt compiler or separately from a crates.io build cache, the build time and storage requirements are the same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Downloading std separately will stop those who have installed rust to look at it during their commute (case in point: me, a few weeks ago). Not everyone has a fast internet connection everywhere, so there may be other hidden downsides. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmm, once std is downloaded once, it doesn't in principle need to be downloaded again. Assuming a local build cache shared between projects is implemented at this point in the future, the compiler's install script could set it up and pre-populate it with std. That way nobody forgets std on their commute :). |
||
to query pre-built binaries for any arbitrary package. In that scenario, we can remove all code | ||
relating to falling back on the sysroot to look for rlibs. | ||
|
||
In the meantime, developers living dangerously with an unstable compiler can package the standard | ||
library themselves, and use their Cargo config file to get Cargo to cross compiler libstd for them. | ||
|
||
|
||
# Drawbacks | ||
|
||
Cargo does more work than is strictly necessary for rlibs installed in sysroot; some more metadata | ||
must be maintained by `rustc` or its installation. | ||
|
||
- But in a future where Cargo can build stdlib like any other, all this cruft goes away. | ||
|
||
|
||
# Alternatives | ||
|
||
- Simply have `implicit-deps = false` make Cargo pass `--use-sysroot=false` to `rustc`. | ||
|
||
- This doesn't by-itself make a way for package to depend on only some of the crates behind the | ||
facade. That, in turn, means Cargo is little better at cross compiling those than before. | ||
|
||
- While unstable compiler users can just package the standard library and depend on it as a | ||
normal crate, it would be weird to have freestanding projects coalesce around some bootleg | ||
libcore on crates.io. | ||
|
||
- Make it so all dependencies, even libstd, must be explicit. C.f. Cabal and base. Slightly | ||
simpler, but breaks nearly all existing packages. | ||
|
||
- Don't track stdlib depencies. Then, in the future when Cargo tries to obtain libs for cross | ||
compiling, stick them in the sysroot instead. Cargo either assumes package needs all of stdlib, | ||
or examines target to see what crates behind the facade are buildable and just goes for those. | ||
|
||
- Cargo does extra work if you need less of the stdlib | ||
|
||
- No nice migration into a world where Cargo can build stdlib without hacks. | ||
|
||
|
||
# Unresolved questions | ||
|
||
- There are multiple lists of dependencies for different things (e.g. tests), Should libstd be | ||
append to all of them in phases 2 and 3? | ||
|
||
- Should rlibs in the sysroot respect Cargo name-frobbing conventions? If they don't, should Cargo | ||
frob the name when it copies it (e.g. with `ld -i`)? | ||
|
||
- Just as make libstd a real dependency, we can make `rustc` a real dev dependency. The standard | ||
library can thus be built with Cargo by depending on the associated unstable compiler. There are | ||
some challenges to be overcome, including: | ||
|
||
- Teaching Cargo and its frobber an "x can build for y" relation for stable/unstable compiler | ||
compatibility, rather than simply assuming all distinct compilers are mutually incompatible. | ||
|
||
- Coalescing a "virtual package" out of many different packages with disjoint dependencies. This | ||
is needed because different `rustc` version has a different library implementation that | ||
present the same interface. | ||
|
||
This almost certainly is better addressed in a later RFC. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's important to spell out here that it's far from standard practice to dump libraries in the sysroot, and the only stable library in the sysroot today is libstd. We have been very hesitant to stabilize any more than precisely one library for many of these reasons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused what you'd like me to elaborate on. I already wanted to emphasize we just do this in the case of libstd and its dependencies---in other words that we are so close---just 1 library away!---from not linking with the sysroot at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm re-reading I'm not quite sure what I was thinking... It may have been from the aspect that "and its deps" isn't so relevant in stable Rust today as libstd is the only library that can be implicitly linked to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah OK. I'll clarify the situation for stable Rust.