-
Notifications
You must be signed in to change notification settings - Fork 0
build-std: release candidate #4
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
base: master
Are you sure you want to change the base?
Conversation
While Rust's pre-built standard library has proven itself sufficient for the majority of use cases, there are a handful of use cases that are not well supported: 1. Rebuilding the standard library to match the user's profile 2. Rebuilding the standard library with ABI-modifying flags 3. Building the standard library for tier three targets This RFC proposes a handful of changes to Cargo, the compiler and standard library with the goal of defining a minimal build-std that has the potential of being stabilised: - Explicitly declaring support for the standard library in target specs - Explicit and implicit dependencies on the standard library in `Cargo.toml` - Re-building the standard library when the profile or target modifiers change Co-authored-by: Adam Gemmell <[email protected]>
text/0000-build-std/3-motivation.md
Outdated
- Cargo may also want a mechanism to build the standard library for | ||
build-std on a stable toolchain without relying on `RUSTC_BOOTSTRAP` |
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.
IMO we should drop this motivation -- it doesn't seem relevant to the goal:
- How is use of RUSTC_BOOTSTRAP to building without Cargo?
- Clearly this is with Cargo, so also not really related to item 1?
And in general BOOTSTRAP or some other mechanism feels like an implementation detail of build-std support, not a motivation.
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've reworded this motivation so it is hopefully clearer what we're trying to solve - see d3c2b1a
text/0000-build-std/3-motivation.md
Outdated
|
||
- While tangential to the core of build-std as a feature, projects like Rust | ||
for Linux want to be able to build an unmodified `core` from `rust-src` in | ||
the sysroot on a stable toolchain without Cargo |
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.
It seems like this isn't really clarifying why -- maybe that's done elsewhere, but it would be useful to have that.
Are we sure this isn't actually "Unblock stabilisation of ABI-modifying compiler flags"? What is the RfL use case for building std without Cargo and without that later motivation? It seems to me that if we assume they're building exactly as we would upstream, then they don't benefit from not using the pre-built artifacts, right?
(In practice I'd guess that without Cargo in the mix, it's easier to customize the build options to match across the board etc., so maybe without Cargo ABI-modifying flags are stabilizable earlier...?)
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.
See #4 (comment)
> rustc will be extended with the ability to determine whether an existing | ||
> `rlib` artefact is compatible with the flags passed to rustc | ||
> ([?][rationale-rustc-support]). | ||
> |
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'd like to soften the text here so it doesn't seem like this is specifying a specific solution. (per comment at #2 (comment)).
Maybe something like:
> | |
> | |
> The exact mechanism is not specified in this RFC. It may be a flag such as `--compatibility-with=core` or an `--emit` or `--print` flag. | |
> |
Or, we could take some time now and make a decision on that design. I don't think anything that involves passing a path to an rlib
is going to work well with cargo.
Additionally, there will be some interaction here with actually determining if the pre-built standard library is available, since again that's not something that I would feel comfortable with cargo being able to determine on its own. I'm wondering if all these queries can be combined? Preferably in a --print
flag so that cargo can include it with the other queries it sends to rustc.
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.
Fixed in 66a0f58. These queries probably could be combined together, a detail that we can definitely iterate on during implementation when we have a complete picture of all the information rustc will need to provide.
"core and alloc", and `core` for "core"). This does not prevent users from | ||
building more crates than the default, it is only intended to be a sensible | ||
default for the target that is probably what the user expects. |
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 little concerned about the user experience here when a user explicitly sets the crates with build-std-crates, or in the explicit-dependencies step. There are a few different failure modes:
- Build wants std for a no-std target that doesn't support std (compile errors):
- Today (no build-std, target installed): You get
error[E0463]: can't find crate for std
with a helpful note that the target may not support std, and to consider using#![no_std]
. - Today with
-Zbuild-std=std
(intended): You're supposed to get an error with this message that explains the target doesn't support std. - Today with
-Zbuild-std=std
(actual): It was broken a while back, and now you get a large number of errors building alloc, with various cfg and resolver errors. - With this proposal: I think you'll get the same behavior, with a large number of confusing errors.
- Today (no build-std, target installed): You get
- Build wants std for a no-std target that does compile successfully (due to the "unsupported" module):
- Today (no build-std, target installed): You get
error[E0463]: can't find crate for std
with a helpful note that the target may not support std, and to consider using#![no_std]
. - Today with
-Zbuild-std=std
(intended): You're supposed to get an error with this message that explains the target doesn't support std. - With this proposal: The target just builds without an error or warning that this configuration is not intended. This will implicitly stabilize the target's support beyond the tier that is intended.
- Today (no build-std, target installed): You get
Would it be possible to avoid these two failure modules with better behavior?
For both of these, I would expect a clear error message that the target doesn't support the given crate (alloc or std), without spitting out pages of other errors.
For the second one, we may need a nightly-only escape hatch. We've had a few targets that have incrementally migrated to supporting std. During that transition period, std is in various states of functionality, but the target itself has not yet stabilized or committed to supporting std. I'm not sure on whether or not that is actually necessary.
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 definitely open to making a change here - the very first early drafts had a much stricter check, and it sounds like we want something closer to that.
It'll require a bit of thought as to how to frame this because it involves a bunch of things - whether a crate should build for a given target depends on if we want to consider that crate "stable" for that target, if that depends on the target tier, or just completeness, as well as whether we do want some targets to default to only core but permit building alloc/std, etc.
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.
We discussed this in the sync call, I think I've wrote what we discussed in
9719228
> Cargo will resolves the dependencies of opaque dependencies, such as the | ||
> standard library, separately in their own workspaces. The root of such a | ||
> resolve will be the crates specified in `build-std-crates` or, if | ||
> [*Standard library dependencies*][deps] is implemented, the unified set of | ||
> packages that any crate in the dependency has a direct dependency on. A | ||
> dependency on the relevant roots are added to all crates in the main resolve. |
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.
Unsure what section this should be called out but we should specify that patch and replaces can't be used with these dependencies?
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.
Added some wording for this in cd627f5
As opaque dependencies, any Cargo command which accepts a package spec with `-p` | ||
will only additionally recognise `core`, `alloc` and `std` and none of their | ||
dependencies. Many of Cargo's subcommands will need modification to support | ||
build-std: |
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.
Should -p
support be left to "Explicit dependencies"? As is, this phase doesn't require any special integration of build-std into how we treat dependencies until we get to this part. Not saying it can't be done at this phase but feels like it should be left to the implementation while required for "Explicit dependencies".
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.
Makes sense to me, changed in 47e90e8
## What should the `build-std` configuration in `.cargo/config` be named? | ||
[unresolved-config-name]: #what-should-the-build-std-configuration-in-cargoconfig-be-named | ||
|
||
What should this configuration option be named? `build-std`? | ||
`rebuild-standard-library`? | ||
|
||
↩ [*Proposal*][proposal] | ||
|
||
## What should the "always" and "never" values of `build-std` be named? | ||
[unresolved-config-values]: #what-should-the-always-and-never-values-of-build-std-be-named | ||
|
||
What is the most intuitive name for the values of the `build-std` setting? | ||
`always`? `manual`? `unconditional`? | ||
|
||
↩ [*Proposal*][proposal] |
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.
Unsure if this had come up before but combining build-std
with always
, auto
, never
naming has the problem in how it reads: build-std = "always"
makes it sound like we'll always rebuild the standard library, rather than that we'll not use the pre-built versions and cache the build of it as needed
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 agree and I'm happy to change the name, maybe missing
- as in, "build the standard library when it is missing"?
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.
That sounds like auto
.
More so I commented on this not to figure it out now but that we should likely call this out in this section to help with the conversation.
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.
Added a mention of why "always" isn't a great name to this unresolved question in c25a29a
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.
Saying this is a problem with always
isn't quite correct; it is a problem with build-std
combined with always
. I intentionally highlighted both sections and called this out as a combination of them.
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.
Changed phrasing for this in b15595e
([?][rationale-unstable-builtin-crates]). If a builtin dependency on a unstable | ||
crate name exists but is not used due to cfgs, then the crate could still be | ||
compiled with a stable toolchain. |
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.
All dependency tables are always resolved. Its later phases that we trim out cfg
related dependencies. This is putting a large design constraint on where it is that we deal with validating the set of crate names.
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.
Changed this in 503a6d9 to flip the behaviour here to avoid that constraint on Cargo
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 could be worth calling this out as a trade off between how things are validated and forcing the use of nightly / MSRV bumps
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.
Added a future possibility for this in d890a6a
Crates without an explicit dependency on the standard library now have a | ||
implicit dependency ([?][rationale-no-migration]) on `std`, `alloc` and `core`. | ||
Any explicit `builtin` dependency present in the manifest will disable the | ||
implicit dependencies. |
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.
This sounds like it means "without an explicit dependency from any dependency table" but later you have an example of a target.*.dependencies
where the inferring is per-table.
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 expect my mental model of how Cargo works internally here is incorrect. My intention was that any explicit builtin dependency in any dependency table would remove the implicit dependencies, rather than each table having its own implicit dependencies. Clarified that in f436c76 but can change if that's just now how Cargo works.
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 not sure how a mental model can be wrong here as we're defining it.
And I feel like that commit didn't clarify my confusion.
Take the example I was referencing
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
# implicit deps on `core`, `alloc` and `std` unless target='x86_64-pc-windows-gnu'
[target.x86_64-pc-windows-gnu.dependencies]
alloc.builtin = true
According to the section its in, this works. According to the other text and your edits of it, this will fail because there is no core
dependency. We can either model that as "there is no core
dependency in dependencies
so it errors" or "when we build for non x86_64-pc-windows-gnu
targets, there is no core
dependency and we error". The latter one is likely easier to work with overall but it delays errors.
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 not sure how a mental model can be wrong here as we're defining it.
I just meant that I'm not especially familiar with how Cargo normally deals with multiple dependency tables - as in, does validation happen on [target.x86_64-pc-windows-gnu.dependencies]
in isolation or once all the dependencies from all the relevant tables are combined. Given however Cargo does this that today, is it more natural to think of each dependency table as having their own set of implicit and explicit dependencies or as that being global across all the dependency tables?
Take the example I was referencing
[package] name = "hello_world" version = "0.1.0" edition = "2024" [dependencies] # implicit deps on `core`, `alloc` and `std` unless target='x86_64-pc-windows-gnu' [target.x86_64-pc-windows-gnu.dependencies] alloc.builtin = trueAccording to the section its in, this works. According to the other text and your edits of it, this will fail because there is no core dependency. We can either model that as "there is no core dependency in dependencies so it errors" or "when we build for non x86_64-pc-windows-gnu targets, there is no core dependency and we error". The latter one is likely easier to work with overall but it delays errors.
My intention with this example was:
- On not-
x86_64-pc-windows-gnu
, there are no explicit dependencies, so you have implicit dependencies. As you have implicit dependencies, you have a dependency oncore
, so there's no error. - On
x86_64-pc-windows-gnu
, there is a explicitalloc
dependency, so you don't have implicit dependencies. As you have a explicit dependency onalloc
, you have a dependency oncore
, so there's no error.
I think the "no core, need to error" case only really comes up with optional
dependencies, not dependencies in other tables. I'll have a re-read of this section of the RFC to see if what I've written is ambiguous.
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.
How cargo views it depends on where you are at in the build process. When we parse, they are separate tables. For resolving of dependencies, we merge all of the tables together. When we resolve features, ... I'm not sure how we do it all. Then we eventually get to execution.
The later in the process we consider implicit vs explicit packages, the more hacks we have to put in to handle all of this. However, if we put it in too early, it either requires an MSRV bump just for existing (not allowed) or we have to hack up the Index generation and Cargo.toml
rewriting.
Implicit and explicit dependencies on the standard library are supported for | ||
`dev-dependencies` in the same way as regular `dependencies`. `dev-dependencies` | ||
continues to be a superset of `dependencies`, including with `builtin` | ||
dependencies. It is possible for `dev-dependencies` to have additional `builtin` | ||
dependencies that the `dependencies` section does not have (e.g. requiring `std` | ||
when the regular dependencies only require `core`). |
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.
So does inferring happen within dev-dependencies
or is it special cased to not have any inferring logic and "just do whatever depdendencies
does"?
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.
Addressed this in #4 (comment)
[`cargo tree`][cargo-tree] will show `std`, `alloc` and `core` at appropriate | ||
places in the tree of dependencies. `alloc` will always be shown as a dependency | ||
of `std`, and `core` a dependency of `alloc`. As opaque dependencies, none of | ||
the other dependencies of `std`, `alloc` or `core` will be shown. Neither `std`, | ||
`alloc` or `core` will have a version number. |
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.
Do we need to specify that the std
/alloc
/core
relationship will be expressed?
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.
Probably not, removed in 0897312
## Why default to public for standard library dependencies? | ||
[rationale-default-public]: #why-default-to-public-for-standard-library-dependencies | ||
|
||
There are crates building on stable which re-export from the standard library. | ||
If the standard library dependencies were not public then these crates would | ||
start to trigger the `exported_private_dependencies` lint when upgrading to a | ||
version of Cargo with a standard library dependency. | ||
|
||
↩ [*Public and private dependencies*][public-and-private-dependencies] |
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.
Not a big fan of this special casing and this is inconsistent within this RFC, like with "Why not use noprelude for explicit builtin dependencies?"
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've added a unresolved question about this special-casing and noting the inconsistency in 74bc861 - I think making the dependencies public by default is probably the right choice to avoid what I assume are quite common std re-exports triggering lints but it's important to acknowledge that is a trade-off.
## Why not use `noprelude` for explicit `builtin` dependencies? | ||
[rationale-explicit-noprelude]: #why-not-use-noprelude-for-explicit-builtin-dependencies | ||
|
||
Explicit builtin dependencies without the `noprelude` modifier behave more | ||
consistently with other dependencies specified in the Cargo manifest. | ||
|
||
This is a trade-off, as the behaviour will be subtly different than with | ||
implicit builtin dependencies (where `extern crate` is required). |
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.
To be clear, this is a consistency of UX at the cost of special casing in Cargo to handle implicit and explicit dependencies separately.
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've elaborated on this being the trade-off in be70a36
5. [build-std=always](./0000-build-std/4-build-std-always.md) | ||
|
||
- Proposes adding a `build-std = "always|never"` option to the Cargo | ||
configuration which will unconditionally re-build the standard library | ||
crates listed in a new `build-std-crates` option | ||
|
||
- [Proposal](./0000-build-std/4-build-std-always.md#proposal) | ||
|
||
- [Rationale and alternatives](./0000-build-std/4-build-std-always.md#rationale-and-alternatives) | ||
|
||
- [Unresolved questions](./0000-build-std/4-build-std-always.md#unresolved-questions) | ||
|
||
- [Future possibilities](./0000-build-std/4-build-std-always.md#future-possibilities) | ||
|
||
6. [Explicit dependencies](./0000-build-std/5-standard-library-dependencies.md) |
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.
This RFC includes two separate but related proposals
build-std=always
- Explicit dependencies
I am not seeing any rationale for why these are being conjoined into one RFC. Large RFCs are like large PRs. An area of focus draws everyone's attention, neglecting other topics. Once that area is "resolved enough", people move onto the next one. And so on until everyone is burnt out and we accept or reject it without reviewing important details. This will also make the conversation harder because of how much more there will be. This also pulls teams onto FCPs that aren't relevant.
Examples of splitting related RFCs
- cargo script
- feature metadata
- RFC: Rustdoc configuration via Cargo (includes feature descriptions) rust-lang/rfcs#3421 (the author's original motivation)
- RFC: Unblock Cargo feature metadata rust-lang/rfcs#3416 (the base case, yes we have an RFC for a single-item table)
- RFC: Cargo feature descriptions rust-lang/rfcs#3485 (add a field)
- RFC: Cargo feature deprecation rust-lang/rfcs#3486 (add a field)
- RFC: Cargo feature visibility (private/public) rust-lang/rfcs#3487 (add a field)
I would need to see a strong case made for why these should be kept in the same RFC as having them conjoined will make things harder for all parties involved and, at least for myself, will have less trust in the process.
I could even see splitting build-std=always
up further to be team focused, layered where at the bottom is infra and lib, then compiler, then cargo. I've not thought through the implications of doing such a split and have no opinion at the moment on what should be done.
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've kept both together as they address the same motivations, and share the history, prior art, etc sections. We've wrote all of these together to make sure that the proposals synergise and that everything included is presented in the context of the larger feature/vision it enables.
I'm open to splitting it into a handful of RFCs if we can work out how to partition the content up in a way that makes sense. My plan so far has been that by doing lots of iterations with smaller groups and representatives from each team that we should have addressed a majority of the major feedback we're likely to get and the RFC shouldn't be too surprising to any of the affected teams.
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.
The examples I gave are in a similar boat in terms of synergy and being written together. As for any shared content, I'm unsure how best to handle that.
My plan so far has been that by doing lots of iterations with smaller groups and representatives from each team that we should have addressed a majority of the major feedback we're likely to get and the RFC shouldn't be too surprising to any of the affected teams.
My response to some of what was discussed was effectively "let's document this and work it out during the RFC with all parties present". Getting feedback in the small is important for faster iteration but it does not replace having a team-wide, project-wide, and community-wide conversation.
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.
As for any shared content, I'm unsure how best to handle that.
I think the easiest way is probably to pick a part of the proposal to make first - probably explicit dependencies? - and then include all the context/history/etc with that, and then just reference that RFC's context/history/prior art sections from everything that follows.
My response to some of what was discussed was effectively "let's document this and work it out during the RFC with all parties present". Getting feedback in the small is important for faster iteration but it does not replace having a team-wide, project-wide, and community-wide conversation.
👍
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 the easiest way is probably to pick a part of the proposal to make first - probably explicit dependencies? - and then include all the context/history/etc with that, and then just reference that RFC's context/history/prior art sections from everything that follows.
I'd post both at once. I feel like "explicit dependencies" could even stand on its own separate from build-std.
Alternatively, we create another RFC that is laying down the build-std project constraints. Not quite a eRFC but not quite a behavior-specifying RFC, making it so there are (at least) three proposals in flight.
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.
Alternatively, we create another RFC that is laying down the build-std project constraints. Not quite a eRFC but not quite a behavior-specifying RFC, making it so there are (at least) three proposals in flight.
I really like that as an approach. Most of the sections that would make up that RFC are basically already ready and could be posted much sooner than the rest of the RFC, then we can just post everything else in whatever chunks make most sense.
7394a1b
to
9719228
Compare
Supercedes #2. This should be the last round of feedback before we post the RFC publicly.
build-std="always"
and explicit dependencies as two parts of the core RFC, and movingbuild-std="compatible"
andbuild-std="match-profile"
into separate files as they'll eventually be follow-up RFCs:0000a-build-std-compatible.md
and0000b-build-std-match-profile.md
aren't going to be included in the initial RFC upstream, but we'll link to them in read-only HackMDs in the PR description for context as to where we're going next with build-std.While Rust's pre-built standard library has proven itself sufficient for the majority of use cases, there are a handful of use cases that are not well supported:
This RFC proposes a handful of changes to Cargo, the compiler and standard library with the goal of defining a minimal build-std that has the potential of being stabilised:
Cargo.toml
Rendered