






















This is a summary of what has been happening around Cargo development for the last 6 weeks which is approximately the merge window for Rust 1.86.
Cargo can't be everything to everyone, if for no other reason than the compatibility guarantees it must uphold. Plugins play an important part of the Cargo ecosystem and we want to celebrate them.
Our plugin for this cycle is cargo install-update (crate name cargo-update) which checks for and applies updates for cargo installed binaries. Built-in support for this is being tracked in #4101.
Thanks to Muscraft for the suggestion!
Please submit your suggestions for the next post.
Cargo's errors and warnings saw a surprising number of improvements recently.
New diagnostics include:
cargo check --workspace --package invalid errors just like cargo check --package invalid.Improvements to existing diagnostics:
cargo run with additional context to understand how to fix them.cargo package VCS dirty checksUpdate from 1.85
When investigating the performance regression in cargo package,
we found corner cases where a file packaged into a .crate could have uncommitted changes while being overlooked in the VCS dirty check.
After weihanglo
filled several holes last development cycle,
the remaining piece of
#14967
was workspace inheritance and related features.
If you have the workspace:
[workspace]
resolver = "3"
[workspace.package]
version = "10.0.0"
[profile]
codegen-units = 1
lto = true
debug = "line-tables-only"
and a package
[package]
name = "foo"
version.workspace = true
then the published version will be
[package]
name = "foo"
version = "10.0.0"
resolver = "3"
(see #8264 for a discussion on whether profile should be copied over)
However, the workspace Cargo.toml is not checked for dirty status.
There are a wide range of approaches we can take:
workspace.resolver copying will happenCargo.toml from a previous commit in git and diff the two (#15089)Cargo.toml to be relevantCargo.lock)After explaining these options to the team,
we decided to start with taking the middle road and checking if the workspace Cargo.toml or Cargo.lock files are dirty
as this balances complexity and brittleness against maintaining precision in what is checked.
Update from 1.84
Rustin170506
finished designing and implementing support for
Package ID Specifications
for cargo scripts.
Package ID Specifications aren't relevant for cargo scripts in most places they show up.
For example, cargo scripts can't be workspace members yet where the --package flag becomes important.
However, implementing support for this now is important because a package's Package ID Specification can show up in the output of cargo metadata
and we need to have the format defined before stabilization.
In a prior PR, we found a gap between rustc and cargo's shebang parsing. Shebang's are ambiguous with attributes. A normal shebang may look like:
#!/usr/bin/env cargo
#![allow(dead_code)]
Rustc considers a #! at the start of a file that is followed by a [ to be an attribute, rather than a shebang.
However, rustc allows "whitespace" between #! and [allow(dead_code)], like:
#!/usr/bin/env cargo
#! [allow(dead_code)]
So more precisely, the #! can have whitespace before the [ and still be considered an attribute instead of a shebang
What we overlooked is that the comments in rustc's lexer describing this meant to include comments with whitespace, so the following is a valid attribute and not a shebang followed by invalid syntax:
#!//usr/bin/env cargo
[allow(dead_code)]
Following rustc's rules for shebangs would require every frontmatter parser to also understand Rust's grammar for comments. As this is a new feature, we felt we had some flexibility in how closely we followed rustc and decided to punt on parsing comments which we documented in #15173. Note that decisions like this are not finalized, just implemented proposals, until stabilization.
epage posted rust#137193
which adds frontmatter syntax support to rustc thanks to the guidance of
bjorn3,
jyn514,
Noratrieb,
and ytmimi.
Once that PR is merged, the remaining tasks before stabilization are:
Along the way, epage refactored some code in #15168 and #15172 to make it less likely for contributors to make mistakes in the future.
epage also started a discussion on zulip on making Cargo's frontmatter parser reusable.
#[test]sUpdate from 1.80
With check-cfg stable,
Urgau started investigating a gap in the lint reported on
Users.
jplatte wanted to exclusively use integration tests over unit tests.
They set lib.test = false and hoped that rustc would report any unused
tests in their [lib] through the check-cfg lint.
However, test is considered an always-present, built-in cfg by rustc.
Several solutions were discussed,
including allowing people to mark built-in cfg's as unknown
rust#117778.
Urgau proposed in compiler-team#785 for the responsibility of marking test as a known config to be on the caller.
When rustc receives --test, it implies --cfg test.
This enables tests to run as #[test] expands to include #[cfg(test)].
In cargo when lib.harness = false,
--test is not passed to rustc and so cargo is responsible to pass --cfg test.
So the responsibility for --cfg test is shared by rustc and the caller.
This was implemented in rust#131729 and #15007.
This lead to false positives in core because it sets lib.test = false but includes code via #[path] with #[test]s (zulip).
This can be worked around by setting lints.rust.unexpected_cfgs.check-cfg = ['cfg(test)'].
Later on that thread and in #15131,
kpreid called out more significant false positives:
lib.test doesn't mean there are never tests but that tests aren't built and run by default.
In this case, it is fully valid to use #[test] or #[cfg(test)].
To give us more time, we decided to revert the change in #15132. This is being further discussed in #15131.
CARGO environment variableCargo passes the path for itself to child processes via CARGO in case they need to launch the same version of Cargo
(e.g. in a build.rs or an xtask).
Several years ago,
jonhoo ran into some corner cases with this:
A binary that uses cargo-the-library to launch child processes would set CARGO to this custom binary that may not be meant to act as a substitute for cargo-the-binary
(#10119).
Depending on the binary's intent,
there is either no correct answer for what CARGO should be for child processes or it should be the value of CARGO set on the binary.
When invoking cargo-the-binary through ld for tight control of the shared library path without propagating it, Cargo looks up its path and gets back the path to ld
(#10113).
The correct answer is in argv[0] but that has lower precedence than
current_exe.
Otherwise, there is no correct answer as the CARGO set on cargo-the-binary may or may not be related.
In #11285,
we gave higher precedence to passing along CARGO set on the current process over discovering the current process' path.
This is rightfully causing problems because
a process spawned by one toolchain that itself was spawned by another toolchain
will have CARGO set to the outer-most toolchain path,
rather than the inner-most (#15099).
Some options that were discussed on #15099 and among the team include:
cargo-the-binary opting in to always override CARGO but this misses the ld casecargo rustup proxy overriding CARGO to the path it is about to launch but this does not affect situations outside of rustup, like a cargo script invoking a deb build that invokes the Debian-built cargoCARGO when calling into a different toolchain version but this is non-obvious to do ahead of time and difficult to debug and determine to do when someone runs into this problem.The proposal we came away with is for cargo-the-binary to opt-in to overriding CARGO if current_exe is cargo{EXE_SUFFIX}.
While there are still corner cases this can run into problems with
(e.g. custom names for cargo-the-binary directly or through symlinks),
this at least shrinks the window for people to be hitting the corner cases.
Update from 1.85
Conversation on RFC #3759 was settling down, so we reviewed this as a team.
After some initial discussion on the options for names for the field, the conversation focused on use cases:
Build-target filtering (in-scope):
like required-features,
automatically filter out any build-target that is not compatible with the selected platform --target.
In the Cargo repo, we have cargo-credential-libsecret, cargo-credential-wincred, and cargo-credential-macos-keychain.
To allow cargo check --workspace, we've #[cfg]ed the implementation of each package so they build on all platforms.
If we had a required-target field, we could remove the #[cfg]s.
This is in-scope for the RFC as it has the smallest scope to design and implement on top of the definition of the field.
Error on incorrect use (deferred): Potential error cases include:
--target x86_64-unknown-linux-gnu but have a dependencies.windows-sys dependencyrequired-target = 'cfg(target_os="linux")' but have a dependencies.windows-sys dependencytarget.cfg(target_os="linux").dependencies.windows-sys dependency,
we could report this as an error.Say cargo-credential-wincred only had an implementation for #[cfg(windows)],
trying to use it on another platform would be reported with an error that WindowsCredential could not be found, with a hint that cfg(windows) is required.
That is not the friendliest error and would only be found if you validate every supported platform.
Instead, Cargo could report before building anything that this will fail on some of your supported platforms.
The first error case is easy to implement and has precedence for its behavior when building with a dependency with a package.rust-version that is older than the current toolchain.
The other two require some work to figure out the intersection of cfgs.
In either case, there has been less interest expressed in the use case and there is concern people will be over-zealous in applying the required-target field to their packages, preventing their use.
Deferring gives us more time to analyze that problem and more of a tradition of setting the field without extra connotations.
Vendor only relevant packages (deferred):
When you run cargo vendor, it saves the content of every .crate file in your Cargo.lock, even if its not needed for the platforms you build on.
When generating or validating Cargo.lock,
we could exclude any target.*.dependencies that is not compatible with your required-target, avoiding the need to vendor them.
Having required-target affect dependency resolution would allow a Linux application to not vendor windows-sys (#7058).
However, a new field would not be needed for a target.cfg(target_os= "linux").dependencies
to exclude any transitive dependencies in a target.cfg(target_os="windows").dependencies.
if we supported that,
someone could emulate required-target by putting all dependencies behind target.cfg(...).dependencies.
There has been a lot of interest in this idea but it adds a lot of complications, so we initially deferred it out so we could focus on a subset of the conversation.
Tracking all of the cfgs that lead to a dependency could get complicated in the dependency resolver.
A 70% solution is we only compare target.cfg(...).dependencies against required-target,
ignoring any target.cfg(...).dependencies along the way that could further narrow things.
This feature could also run up against a general rule of Cargo:
running cargo check from a different toolchain version should not modify your Cargo.lock:
cfgs, Cargo.lock can change it. We'd need to keep every implementation and have it selected by either the Cargo.lock version field or workspace.resolver.cfgs could change it. There is not much we can do about this.This use case is different than the others in that its mostly focused on application developers.
Vendoring of dependencies is specific to upstream pre-built binaries in which they know the full set of platform tuples being built for.
For downstream distributions,
they will already be decoupling the package from vendored dependencies and could disable this somehow.
If it is that specialized, maybe we should decouple cargo vendor / Cargo.lock filtering from required-target and have the user explicitly enumerate each platform tuple.
This bypasses the cfg set logic but still runs into changing definitions of platform tuples.
Presuming a more limited audience,
maybe that level of volatility will be acceptable to them.
This resolver.targets could be a config field.
The downside is that your Cargo.lock would be dependent on transient or context-sensitive settings but maybe that is fine with the more limited use case.
We might want to record the resolver.targets in Cargo.lock so that unexpected changes from missing or changed config are clear.
Maybe more important is that if this config were used when running cargo publish,
then cargo install --locked could end up failing.
Another direction we discussed was for required-target to be a subset of cfg functionality, like only target_os.
This might allow us to make some simplifying assumptions but we'd need to work closely with T-compiler to ensure those assumptions are upheld.
We didn't end up reaching a specific conclusion and will need to consider this further.
workspace.dependenciesWe discussed a proposal by CinchBlue to act as if workspace members were added to workspace.dependencies
(#13453).
Since Cargo already needs to discover the location of packages,
this would remove the need to give Cargo information it already has,
reducing friction when moving packages in a repo.
In effect, this would treat:
# Cargo.toml
[workspace]
members = ["crates/*"]
# crates/foo/Cargo.toml
[package]
name = "foo"
[dependencies]
bar.workspace = true
# crates/bar/Cargo.toml
[package]
name = "bar"
like
# Cargo.toml
[workspace]
members = ["crates/*"]
[workspace.dependencies]
foo.path = "crates/foo"
bar.path = "crates/bar"
# crates/foo/Cargo.toml
[package]
name = "foo"
[dependencies]
bar.workspace = true
# crates/bar/Cargo.toml
[package]
name = "bar"
The first challenge is determining what the workspace.dependencies entry should look like.
For example, the version field is generally required to publish a package.
One exception is when user intentionally leave off version for dev-dependencies to workaround
publish cycles.
However, we don't know if packages are intended to be published or not
because packages default to package.publish = true,
Tracking all of that to determine how to implicitly fill workspace.dependencies would also be complicated.
Likewise, we'd need to figure out how to handle registry and default-features fields.
Speaking of complicated designs, consider the following example:
# Cargo.toml
[workspace]
members = ["crates/*"]
[workspace.package]
version = "2.0.0"
[package]
name = "foo"
version.workspace = true
[dependencies]
bar.workspace = true
# crates/bar/Cargo.toml
[package]
name = "bar"
version.workspace = true
To load Cargo.toml, we'd need to
Cargo.tomlcrates/bar/Cargo.tomlCargo.tomls workspace.package to crates/bar/Cargo.toml to ensure version is set if neededcrates/bar/Cargo.toml to Cargo.tomls workspace.dependenciesCargo.tomls workspace.dependencies to Cargo.tomlOr put another way, we'd need to load the workspace members in multiple passes, ensuring we are only operating on initialized data in each pass.
Already we feel that we are at the limits of our complexity budget for parsing Cargo.toml and this would exceed that.
Weighing all of that against the workaround of manually populating workspace.dependencies,
the latter doesn't seem so bad.
We could even smooth that out by having cargo new inject new workspace members into workspace.dependencies (#15180).
A user would then be free to edit the entries to suit their needs.
With cargo new automatically populating workspace.dependencies and cargo add automatically using it,
this would shift the ecosystem over to using it and the question came up on whether workspace.dependencies is mature enough for this.
There are caveats with the feature itself, like known issues with default-features.
There are also caveats with the workflows around it, like tracking breaking changes.
Without workspace.dependencies,
you can look at every commit in a directory to look for breaking changes
(tools like cargo release changes help with that).
However, that won't be the case in the following scenario:
# Cargo.toml
[workspace]
members = ["crates/*"]
[workspace.dependencies]
dep = { path = "crates/dep", version = "1.0.0" }
# lib/Cargo.toml
cargo-features = ["public-dependency"]
[package]
name = "lib"
[dependencies]
dep = { workspace = true, public = true }
You could bump deps version to 2.0.0, breaking users of lib and not be able to tell by looking at git log lib/.
cargo semver-checks
might be able to help with this today but stabilization of public-dependency would make this trivial to add such a check but that is blocked on some bugs in the rustc lint (
rust#71043,
rust#119428
).
resolver.feature-unification = "workspace" was implemented in #15157 by aliu (update from 1.83)edition fields, like lib.edition build-dir from target-dir in #15104 (update from 1.82)These are areas of interest for Cargo team members with no reportable progress for this development-cycle.
Project goals in need of owners
Ready-to-develop:
Needs design and/or experimentation:
Planning:
features metadata
If you have ideas for improving cargo, we recommend first checking our backlog and then exploring the idea on Internals.
If there is a particular issue that you are wanting resolved that wasn't discussed here, some steps you can take to help move it along include:
Cargo.lock policy,
MSRV-aware resolver
)We are available to help mentor people for S-accepted issues on zulip and you can talk to us in real-time during Contributor Office Hours. If you are looking to help with one of the bigger projects mentioned here and are just starting out, fixing some issues will help familiarize yourself with the process and expectations, making things go more smoothly. If you'd like to tackle something without a mentor, the expectations will be higher on what you'll need to do on your own.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。