Skip to content

Commit a7425e6

Browse files
committed
Rewrite after talking to @alexcrichton on IRC.
tl;dr: - Motivation is significantly expanded - Phase 1 is cut for being too half-assed
1 parent 630ac97 commit a7425e6

File tree

1 file changed

+118
-60
lines changed

1 file changed

+118
-60
lines changed

text/0000-cargo-libstd-awareness.md

+118-60
Original file line numberDiff line numberDiff line change
@@ -5,92 +5,150 @@
55

66
# Summary
77

8-
Currently, all packages implicitly depend on libstd. This makes Cargo unsuitable for packages that
9-
need a custom-built libstd, or otherwise depend on crates with the same names as libstd and the
10-
crates behind the facade. The proposed fixes also open the door to a future where libstd can be
11-
Cargoized.
8+
Currently, Cargo doesn't know whether packages depend on libstd. This makes Cargo unsuitable for
9+
packages that need a cross-compiled or custom libstd, or otherwise depend on crates with the same
10+
names as libstd and the crates behind the facade. The proposed fixes also open the door to a future
11+
where libstd can be Cargoized.
12+
1213

1314
# Motivation
1415

15-
Bare-metal work cannot use a standard build of libstd. But since any crate built with Cargo can link
16-
with a system-installed libstd if the target matches, using Cargo for such projects can be irksome
17-
or impossible.
16+
First some background. The current situation seems to be more of an accident of `rustc`'s pre-Cargo
17+
history than an explicit design decision. Cargo passes the location and name of all depended-on
18+
crates to `rustc`. This method is good for a number of reasons stemming from its fine granularity,
19+
such as:
1820

19-
Cargoizing libstd also generally simplifies the infrastructure, and makes cross compiling much
20-
slicker, but that is a separate discussion.
21+
- No undeclared dependencies can be used
2122

22-
Finally, I first raised this issue here: https://github.com/rust-lang/Cargo/issues/1096 Also, there
23-
are some (heavily bit-rotted) projects at https://github.com/RustOS-Fork-Holding-Ground that depend
24-
on each other in the way this RFC would make much more feasible.
23+
- Conversely, `rustc` can warn against *unused* declared dependencies
2524

26-
# Detailed design
25+
- Crate/symbol names are frobbed so that packages with the overlapping names don't conflict
26+
27+
28+
However rather than passing in libstd and its deps, Cargo lets the compiler look for them as need in
29+
the compiler's sysroot [specifically `<sysroot>/lib/<target>`]. This is quite coarse in comparison,
30+
and we loose all the advantages of the previous method:
31+
32+
- Packages may link or not link against libs in that directory as they please, with Cargo being
33+
none the wiser.
34+
35+
- Cargo-built crates with the same name as those in there will collide, as the sysroot libs don't
36+
have their names frobbed.
2737

28-
The current situation seems to be more of an accident of `rustc`'s pre-Cargo history than an
29-
explicit design decision. Cargo passes the location and name of all depended on crates to `rustc`.
30-
This is good because it means that that no undeclared dependencies on other Cargo packages can leak
31-
through. However, it also passes in `--sysroot /path/to/some/libdir`, the directory being were
32-
libstd is. This means packages are free to use libstd, the crates behind the facade, or none of the
33-
above, with Cargo being none the wiser.
38+
- Cross compiling may fail at build-time (as opposed to the much shorter
39+
"gather-dependencies-time") because of missing packages
3440

35-
The only new interface proposed is a boolean field to the package meta telling Cargo that the
36-
package does not depend on libstd by default. This need not imply Rust's `no_std`, as one might want
37-
to `use` their own build of libstd by default. To disambiguate, this field is called
38-
`implicit-deps`; please, go ahead and bikeshead the name. `implicit-deps` is true by default to
39-
maintain compatibility with existing packages.
4041

41-
The meaning of this flag is defined in 3 phases, where each phase extends the last. The idea being
42-
is that while earlier phases are easier to implement, later phases yield a more elegant system.
42+
Cargo doesn't look inside the sysroot to see what is or isn't there, but it would hardly help if it
43+
did, because it doesn't know what any package needs. Assuming all packages need libstd, for example,
44+
means Cargo just flat-out won't build freestanding packages that just use libcore on a platform that
45+
doesn't support libstd.
4346

44-
## Phase 1
47+
For an anecdote: in https://github.com/RustOS-Fork-Holding-Ground I tried to rig up Cargo to cross
48+
compile libstd for me. Since I needed to use an unstable compiler anyways, it was possible in
49+
principle to build absolutely everything I needed with the same `rustc` version. Because of some
50+
trouble with Cargo and target JSONs, I didn't use a custom target specification, and just used
51+
`x86_64-gnu-linux`, meaning that depending on platform I was compiling on, I may or may have been
52+
cross-compiling. In the case where I wasn't, I couldn't complete the build because `rustc`
53+
complained about the libstd I was building overlapping with the libstd in the sysroot.
4554

46-
Add a `--use-sysroot=<true|false>` flag to `rustc`, where true is the default. Make Cargo pass
47-
`--use-sysroot=false` to `rustc` is the case that `implicit-deps` is false.
55+
For these reasons, most freestanding projects I know of avoid Cargo altogether, and just include
56+
submodule rust and run make in that. Cargo can still be used if one manages to get the requisite
57+
libraries in the sysroot. But this is a tedious operation that individual projects shouldn't need to
58+
reimplement, and one that has serious security implications if the normal libstd is modified.
4859

49-
This hotfix is enough to allow us bare-metal devs to use Cargo for our own projects, but doesn't
50-
suffice for creating an ecosystem of packages that depend on crates behind the facade but not libstd
51-
itself. This is because the choices are all or nothing: Either one implicitly depends on libstd or
52-
the crates behind the facade, or they don't depend on them at all.
60+
The fundamental plan proposed in this RFC is to make sure that anything Cargo builds never blindly
61+
links against libraries in the sysroot. This is achieved by making Cargo aware of all dependencies,
62+
including those libstd or its backing crates. That way, these problems are avoided.
5363

54-
## Phase 2
64+
For the record, I first raised this issue [here](https://github.com/rust-lang/Cargo/issues/1096).
5565

56-
Since, passing in a directory of crates is inherently more fragile than passing in a crate itself,
57-
make Cargo use `--use-sysroot=false` in all cases.
5866

59-
Cargo would special case package names corresponding to the crates behind the facade, such that if
60-
the package don't exist, it would simply pass the corresponding system crate to `rustc`. I assume
61-
the names are blacklisted on crates.io already, so by default the packages won't exist. But users
62-
can use config files to extend the namespace so their own modded libstds can be used instead. Even
63-
if they don't want to change libstd but just cross-compile it, this is frankly the easiest way as
64-
Cargo will seemliest cross compile both their project and it's transitive dependencies.
67+
# Detailed design
68+
69+
The only new interface proposed is a boolean field in `Cargo.toml` specifying that the package does
70+
not depend on libstd by default. Note that this is technically orthogonal to Rust's `no_std`, as one
71+
might want to `use` their own build of libstd by default, or implicitly depend on it but not
72+
glob-import the prelude. To disambiguate, this field is called `implicit-deps`; please, go ahead and
73+
bikeshead the name. `implicit-deps` is true by default to maintain compatibility with existing
74+
packages. When true, "std" will be implicitly appended to the list of dependencies.
75+
76+
When Cargo sees a package name it cannot resolve, it will query `rustc` for the default sysroot, and
77+
look inside to see if it can find a matching rlib. [It is necessary to query `rustc` because the
78+
`rustc` directory layout is not stabilized and `rustc` and Cargo are versioned independently. The
79+
same version issues make giving a Cargo a whitelist of potential standard library crate-names
80+
risky.] If a matching rlib is successful found, Cargo will copy it (or simlink it) into the
81+
project's build directly as if it built the rlib. Each rlib in the sysroot must be paired with some
82+
sort of manifest listing its dependencies, so Cargo can copy those too.
6583

66-
In this way we can put packages on crates.io that depend on the crates behind the facade. Some
67-
packages that already exist, like liblog and libbitflags, should be given features that optionally
68-
allow them to avoid libstd and just depend directly on the crates behind the facade they really
69-
need.
84+
`rustc` will have a new `--use-sysroot=<true|false>` flag. When Cargo builds a package, it will
85+
always pass `--use-sysroot=false` to `rustc`, as any rlibs it needs will have been copied to the
86+
build directory. Cargo can and will then pass those rlibs directly just as it does with normal Cargo
87+
deps.
7088

71-
## Phase 3
89+
If Cargo cannot find the libraries it needs in the sysroot, or a library's dependency manifest is
90+
missing, it will complain that the standard libraries needed for the current job are missing and
91+
give up.
7292

73-
If/when the standard library is built with Cargo and put on crates.io, all the specially-cased
74-
package names can be treated normally,
93+
## Future Compatibility
7594

76-
The standard library is downloaded and built from crates.io. Or equivalently, Cargo comes with a
77-
cache of that build, as Cargo should be able cache builds between projects at this point. Just as in
78-
phase 2, `implicit-deps = false` just prevents libstd from implicitly being appended to the list of
79-
dependencies.
95+
In the future, rather than giving up if libraries are missing Cargo could attempt to download them
96+
from some build cache. In the farther future, the stdlib libraries may be Cargoized, and Cargo able
97+
to query pre-built binaries for any arbitrary package. In that scenario, we can remove all code
98+
relating to falling back on the sysroot to look for rlibs.
99+
100+
In the meantime, developers living dangerously with an unstable compiler can package the standard
101+
library themselves, and use their Cargo config file to get Cargo to cross compiler libstd for them.
80102

81-
Again, to make this as least controversial as possible, this RFC does not propose outright that the
82-
standard library should be Cargoized. This 3rd phases just describes how this feature would work
83-
were that to happen.
84103

85104
# Drawbacks
86105

87-
I really don't know of any. Development for hosted environments would hardly be very affected.
106+
Cargo does more work than is strictly necessary for rlibs installed in sysroot; some more metadata
107+
must be maintained by `rustc` or its installation.
108+
109+
- But in a future where Cargo can build stdlib like any other, all this cruft goes away.
110+
88111

89112
# Alternatives
90113

91-
Make it so all dependencies, even libstd, must be explicit. C.f. Cabal and base.
114+
- Simply have `implicit-deps = false` make Cargo pass `--use-sysroot=false` to `rustc`.
115+
116+
- This doesn't by-itself make a way for package to depend on only some of the crates behind the
117+
facade. That, in turn, means Cargo is little better at cross compiling those than before.
118+
119+
- While unstable compiler users can just package the standard library and depend on it as a
120+
normal crate, it would be weird to have freestanding projects coalesce around some bootleg
121+
libcore on crates.io.
122+
123+
- Make it so all dependencies, even libstd, must be explicit. C.f. Cabal and base. Slightly
124+
simpler, but breaks nearly all existing packages.
125+
126+
- Don't track stdlib depencies. Then, in the future when Cargo tries to obtain libs for cross
127+
compiling, stick them in the sysroot instead. Cargo either assumes package needs all of stdlib,
128+
or examines target to see what crates behind the facade are buildable and just goes for those.
129+
130+
- Cargo does extra work if you need less of the stdlib
131+
132+
- No nice migration into a world where Cargo can build stdlib without hacks.
133+
92134

93135
# Unresolved questions
94136

95-
There are multiple lists of dependencies for different things (e.g. tests), Should libstd be append
96-
to all of them in phases 2 and 3?
137+
- There are multiple lists of dependencies for different things (e.g. tests), Should libstd be
138+
append to all of them in phases 2 and 3?
139+
140+
- Should rlibs in the sysroot respect Cargo name-frobbing conventions? If they don't, should Cargo
141+
frob the name when it copies it (e.g. with `ld -i`)?
142+
143+
- Just as make libstd a real dependency, we can make `rustc` a real dev dependency. The standard
144+
library can thus be built with Cargo by depending on the associated unstable compiler. There are
145+
some challenges to be overcome, including:
146+
147+
- Teaching Cargo and its frobber an "x can build for y" relation for stable/unstable compiler
148+
compatibility, rather than simply assuming all distinct compilers are mutually incompatible.
149+
150+
- Coalescing a "virtual package" out of many different packages with disjoint dependencies. This
151+
is needed because different `rustc` version has a different library implementation that
152+
present the same interface.
153+
154+
This almost certainly is better addressed in a later RFC.

0 commit comments

Comments
 (0)