Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
78e7d26
typo: Fix previously added typo
adamgemmell Aug 10, 2026
5bfab35
test(unit-graph): Capture existing behaviour in unit-graph test
adamgemmell Aug 10, 2026
3b87c7c
test(resolver): Model builtin packages
adamgemmell Jul 21, 2026
1bee864
feat(source): Allow specifying builtin sources
adamgemmell Jul 21, 2026
e7473c1
feat(resolver): Allow creating builtin dependencies
adamgemmell Jul 22, 2026
c78fd63
test(resolver): Add negative resolver tests
adamgemmell Aug 7, 2026
c7483da
feat(sources): generate opaque summaries to satisfy builtin dependencies
adamgemmell Jul 28, 2026
038ad3a
feat(resolver): Add directive to enable injecting builtins
adamgemmell Aug 4, 2026
703e41d
test(resolver): Test for implicit builtin deps
adamgemmell Aug 7, 2026
984988b
feat(resolver): Inject builtin dependencies
adamgemmell Aug 5, 2026
f8cc6c0
test(resolve): Do not encode builtin packages in the lockfile
adamgemmell May 6, 2026
6e57738
test(package-diff): Do not emit builtin packages in status messages
adamgemmell May 6, 2026
a734a4f
feat(build-std): Enable implicit builtin dependencies and unit
adamgemmell Feb 25, 2026
8b48454
test(unit-generation): no-std build-std
adamgemmell Aug 6, 2026
e5fc4a9
test(build-std): Fix shared_std test
adamgemmell Aug 7, 2026
13f0c62
test(build-std): builtins as roots
adamgemmell Aug 7, 2026
c640bb9
feat(unit generation): Replace builtin roots with std roots
adamgemmell Aug 7, 2026
a48a9d0
test(metadata): Do not emit builtins with cargo metadata
adamgemmell Aug 7, 2026
a288440
fix(metadata): Filter out builtin packages
adamgemmell Aug 7, 2026
4e9354a
test(build-std): Do not emit builtins with cargo tree
adamgemmell Aug 7, 2026
6d6fe8f
fix(tree): Don't emit builtins
adamgemmell Aug 7, 2026
12d0b08
test(vendor): builtins shouldn't be vendored
adamgemmell Aug 7, 2026
08574b8
fix(vendor): Don't vendor builtins
adamgemmell Aug 7, 2026
cc1e02b
fixup: test(resolve): Do not encode builtin packages in the lockfile
adamgemmell Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ cargo-platform = { path = "crates/cargo-platform", version = "0.3.3" }
cargo-test-macro = { version = "0.4.14", path = "crates/cargo-test-macro" }
cargo-test-support = { version = "0.11.4", path = "crates/cargo-test-support" }
cargo-util = { version = "0.2.32", path = "crates/cargo-util" }
cargo-util-schemas = { version = "0.14.2", path = "crates/cargo-util-schemas" }
cargo-util-schemas = { version = "0.15.0", path = "crates/cargo-util-schemas" }
cargo-util-terminal = { version = "0.1.2", path = "crates/cargo-util-terminal" }
cargo_metadata = "0.23.1"
clap = "4.6.0"
Expand Down
2 changes: 1 addition & 1 deletion crates/cargo-util-schemas/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-util-schemas"
version = "0.14.3"
version = "0.15.0"
rust-version = "1.97" # MSRV:1
edition.workspace = true
license.workspace = true
Expand Down
5 changes: 5 additions & 0 deletions crates/cargo-util-schemas/src/core/package_id_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ fn strip_url_protocol(url: &Url) -> Url {

impl fmt::Display for PackageIdSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.kind() == Some(&SourceKind::Builtin) {
// Builtins have a very specific pkgid output
write!(f, "builtin://.#{}", self.name)?;

@bjorn3 bjorn3 Aug 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this get exposed? None of the tests contain builtin://. And is this supposed to be roundtripable? The parse code didn't get changed afaict.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #16675 (comment) for the parsing code. I was worried it could be exposed through error messages or something but I'm not able to find an example. This pkgid format is described in the RFC and I included it because I judged it as low-risk mainly, but I'm happy to move it to a more comprehensive PR if you'd rather.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with keeping it in this PR myself, but I don't know much about the cargo internals.

The pkgid current format seems to requires a single version of every standard library dependency. I added a tidy lint for it a while back, but I don't think it is a policy of the libs team to never ever add multiple versions of a crate. I'm guessing the only parts that would ever end up in the lockfile would be things like core, alloc and std for which I would be very surprised if there were ever two versions of in the same rustc version.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PackageIdSpec's get exposed via cargo pkgid, cargo metadata, json messages

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general yes, but in this PR builtin sourcekinds are not present in the unit-graph. They're replaced by paths from the std resolve. Most ways I found that view pkgids operate on the unit-graph, with some exceptions.

cargo pkgid actually requires a lockfile, and can't print builtins as we make sure they're not emitted in the lockfile.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about cargo metadata?

There are also the places where PackageIdSpecs can be passed in. The only one that I can think of off the top of my head that can take non-local packages is meaningless yet should work: cargo update.

in this PR builtin sourcekinds are not present in the unit-graph. They're replaced by paths from the std resolv

Is that the long term plan or just for this PR?

Potential impacts

  • Source needs to be non-local to get various optimizations because local sources are presumed to be mutable
  • Whether this impacts caching on CI
  • What should be done for trim paths

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that the long term plan or just for this PR?

I'm not currently sure. My plan for this PR is to ensure consistency with the current -Zbuild-std implementation where possible. Future PRs will address individual subcommands in a more detailed way. For example, cargo update -p std -Zbuild-std doesn't work before or after this PR (but succeeds if there's no lockfile as if -p wasn't passed).

Thanks for the list of impacts, I haven't considered these and have added them to our plan. The main factor I was previously weighing up was between wanting to hide/obscure builtins from the user, but still make them visible to tooling like rust-analyzer which only cares about the unit-graph really.

On Unit::is_local(), there's already an override for std present:

self.pkg.package_id().source_id().is_path() && !self.is_std

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As someone working on tooling to execute Cargo's build plans with other build systems, I absolutely want to get standard library deps in the unit graph for my purposes.

return Ok(());
}
let mut printed_name = false;
match self.url {
Some(ref url) => {
Expand Down
7 changes: 7 additions & 0 deletions crates/cargo-util-schemas/src/core/source_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub enum SourceKind {
LocalRegistry,
/// A directory-based registry.
Directory,
/// Package sources distributed with the rust toolchain
Builtin,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will impact the unique identifier for the packages from this source in cargo's json output when compiling, cargo metadata, cargo <cmd> -p, etc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll modify there too, and add a note to check the stdout in various use cases. The RFCs often make notes on what the output of various commands will be. Note that builtin doesn't actually appear in Units - they're all Path dependencies by that point.

An interesting point on cargo metadata is that we decided that we have an unresolved question regarding if deps of builtins should be shown on output, which will be a little hard here as they're not attached until unit generation.

@adamgemmell adamgemmell Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've opted to implement pkg spec input/output in a later PR. I've added tests for common output commands like metadata/tree. json output (from build --message-format=json) isn't impacted in this PR - builtins do not exist in the unit graph (see this test which isn't changed in the rest of the branch c1f8964). I plan to address that in a future PR but I'm not sure on the best way to do that at this stage.

}

// The hash here is important for what folder packages get downloaded into.
Expand All @@ -40,6 +42,7 @@ impl SourceKind {
SourceKind::SparseRegistry => None,
SourceKind::LocalRegistry => Some("local-registry"),
SourceKind::Directory => Some("directory"),
SourceKind::Builtin => Some("builtin"),
}
}
}
Expand Down Expand Up @@ -71,6 +74,10 @@ impl Ord for SourceKind {
(_, SourceKind::Directory) => Ordering::Greater,

(SourceKind::Git(a), SourceKind::Git(b)) => a.cmp(b),
(SourceKind::Git(_), _) => Ordering::Less,
(_, SourceKind::Git(_)) => Ordering::Greater,

(SourceKind::Builtin, SourceKind::Builtin) => Ordering::Equal,
}
}
}
Expand Down
77 changes: 77 additions & 0 deletions crates/resolver-tests/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::path::Path;
use std::sync::OnceLock;

use cargo::GlobalContext;
use cargo::compiler::standard_lib::detect_sysroot_src_path;
use cargo::util::IntoUrl;
use cargo::util::data_structures::HashMap;
use cargo::workspace::dependency::DepKind;
use cargo::workspace::{Dependency, GitReference, PackageId, SourceId, Summary};

Expand Down Expand Up @@ -87,6 +91,29 @@ impl<T: AsRef<str>, U: AsRef<str>> ToPkgId for (T, U) {
}
}

#[derive(Copy, Clone)]
pub struct BuiltinPid {
pub name: &'static str,
}

impl ToPkgId for BuiltinPid {
fn to_pkgid(&self) -> PackageId {
PackageId::try_new(self.name, "0.0.0", builtin_loc()).unwrap()
}
}

#[derive(Copy, Clone)]
pub struct BuiltinPidWithGctx<'a> {
pub name: &'static str,
pub gctx: &'a GlobalContext,
}

impl<'a> ToPkgId for BuiltinPidWithGctx<'a> {
fn to_pkgid(&self) -> PackageId {
PackageId::try_new(self.name, "0.0.0", builtin_loc_sysroot(self.gctx)).unwrap()
}
}

#[macro_export]
macro_rules! pkg {
($pkgid:expr => [$($deps:expr),* $(,)? ]) => ({
Expand All @@ -108,6 +135,46 @@ fn registry_loc() -> SourceId {
*example_dot
}

fn builtin_loc() -> SourceId {
static LOCAL_PATH: OnceLock<SourceId> = OnceLock::new();
let local_path = LOCAL_PATH.get_or_init(|| {
SourceId::for_builtin(Path::new(&std::env::current_dir().unwrap())).unwrap()
});
*local_path
}

fn builtin_loc_sysroot(gctx: &GlobalContext) -> SourceId {
static LOCAL_PATH: OnceLock<SourceId> = OnceLock::new();
let local_path = LOCAL_PATH.get_or_init(|| {
SourceId::for_builtin(&detect_sysroot_src_path(gctx, None).unwrap()).unwrap()
});
*local_path
}

pub fn gctx_for_build_std() -> GlobalContext {
let mut gctx = GlobalContext::default().unwrap();
gctx.nightly_features_allowed = true;
gctx.configure(
1,
false,
None,
false,
false,
false,
&None,
&["build-std=core".to_owned()],
&[],
)
.unwrap();
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/testsuite/mock-std/library");
let env = HashMap::from_iter([(
"__CARGO_TESTS_ONLY_SRC_ROOT".to_owned(),
root.into_os_string().into_string().unwrap(),
)]);
gctx.set_env(env);
gctx
}

pub fn pkg<T: ToPkgId>(name: T) -> Summary {
pkg_dep(name, Vec::new())
}
Expand Down Expand Up @@ -215,6 +282,10 @@ pub fn dep_loc(name: &str, location: &str) -> Dependency {
Dependency::parse(name, Some("1.0.0"), source_id).unwrap()
}

pub fn dep_builtin(name: &str) -> Dependency {
Dependency::parse(name, None, builtin_loc()).unwrap()
}

pub fn dep_kind(name: &str, kind: DepKind) -> Dependency {
let mut dep = dep(name);
dep.set_kind(kind);
Expand All @@ -235,6 +306,12 @@ pub fn names<P: ToPkgId>(names: &[P]) -> Vec<PackageId> {
names.iter().map(|name| name.to_pkgid()).collect()
}

/// For a set of name specifiers of varying types
#[macro_export]
macro_rules! names {
($($name:expr),* $(,)?) => {&vec![$($name.to_pkgid()),*]};
}

pub fn loc_names(names: &[(&'static str, &'static str)]) -> Vec<PackageId> {
names
.iter()
Expand Down
25 changes: 20 additions & 5 deletions crates/resolver-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ pub fn resolve_and_validated_raw(
root_pkg_id: PackageId,
sat_resolver: &mut SatResolver,
) -> CargoResult<Vec<(PackageId, Vec<InternedString>)>> {
let resolve = resolve_with_global_context_raw(
let resolve = resolve_with_raw(
deps.clone(),
registry,
root_pkg_id,
&GlobalContext::default().unwrap(),
ResolveOpts::everything(),
);

match resolve {
Expand Down Expand Up @@ -120,15 +121,31 @@ pub fn resolve_with_global_context(
registry: &[Summary],
gctx: &GlobalContext,
) -> CargoResult<Vec<(PackageId, Vec<InternedString>)>> {
let resolve = resolve_with_global_context_raw(deps, registry, pkg_id("root"), gctx)?;
let resolve = resolve_with_raw(
deps,
registry,
pkg_id("root"),
gctx,
ResolveOpts::everything(),
)?;
Ok(collect_features(&resolve))
}

pub fn resolve_with_global_context_raw(
pub fn resolve_with_gctx_opts(
deps: Vec<Dependency>,
registry: &[Summary],
gctx: &GlobalContext,
opts: ResolveOpts,
) -> CargoResult<Resolve> {
resolve_with_raw(deps, registry, pkg_id("root"), gctx, opts)
}

pub fn resolve_with_raw(
deps: Vec<Dependency>,
registry: &[Summary],
root_pkg_id: PackageId,
gctx: &GlobalContext,
opts: ResolveOpts,
) -> CargoResult<Resolve> {
struct MyRegistry<'a> {
list: &'a [Summary],
Expand Down Expand Up @@ -190,8 +207,6 @@ pub fn resolve_with_global_context_raw(
let root_summary =
Summary::new(root_pkg_id, deps, &BTreeMap::new(), None::<&String>, None).unwrap();

let opts = ResolveOpts::everything();

let start = Instant::now();
let mut version_prefs = VersionPreferences::default();
if gctx.cli_unstable().minimal_versions {
Expand Down
57 changes: 54 additions & 3 deletions crates/resolver-tests/tests/resolve.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
use cargo::resolver::ResolveOpts;
use cargo::util::GlobalContext;
use cargo::workspace::Dependency;
use cargo::workspace::dependency::DepKind;
use resolver_tests::helpers::gctx_for_build_std;
use resolver_tests::helpers::{BuiltinPidWithGctx, dep_builtin};
use snapbox::assert_data_eq;
use snapbox::str;

use resolver_tests::{
helpers::{
ToDep, ToPkgId, assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, loc_names,
names, pkg, pkg_dep, pkg_dep_with, pkg_id, pkg_loc, registry,
BuiltinPid, ToDep, ToPkgId, assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req,
loc_names, names, pkg, pkg_dep, pkg_dep_with, pkg_id, pkg_loc, registry,
},
pkg, resolve, resolve_with_global_context,
names, pkg, resolve, resolve_with_gctx_opts, resolve_with_global_context,
};

#[test]
Expand Down Expand Up @@ -1036,3 +1039,51 @@ failed to select a version for `F` which could resolve this conflict
"#]]
);
}

#[test]
fn test_builtin_dependency() {
let core = BuiltinPid { name: "core" };
let reg = registry(vec![pkg!(core)]);

let res = resolve(vec![dep_builtin("core")], &reg).unwrap();

assert_same(&res, &names!("root", core));
}

#[test]
fn normal_dependency_is_not_satisfied_by_builtin_package() {
let core = BuiltinPid { name: "core" };
let reg = registry(vec![pkg!(core)]);

assert!(resolve(vec![dep("core")], &reg).is_err());
}

#[test]
fn missing_builtin_dependency_errors() {
assert!(resolve(vec![dep_builtin("core")], &registry(vec![])).is_err());
}

#[test]
fn injects_builtins_when_required() {
let gctx = gctx_for_build_std();

let core = BuiltinPidWithGctx {
name: "core",
gctx: &gctx,
};
let compiler_builtins = BuiltinPidWithGctx {
name: "compiler_builtins",
gctx: &gctx,
};
let reg = registry(vec![pkg!(core), pkg!(compiler_builtins)]);

let mut opts = ResolveOpts::everything();
opts.inject_builtins = true;
let resolve = resolve_with_gctx_opts(Vec::new(), &reg, &gctx, opts).unwrap();

let root_deps = resolve
.deps(pkg_id("root"))
.map(|(pkg_id, _)| pkg_id)
.collect::<Vec<_>>();
assert_same(&root_deps, names!(core, compiler_builtins))
}
2 changes: 0 additions & 2 deletions src/compiler/build_context/target_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ impl TargetInfo {
/// invocation is cached by [`Rustc::cached_output`].
///
/// Search `Tricky` to learn why querying `rustc` several times is needed.
///
/// When a Workspace is provided,
#[tracing::instrument(skip_all)]
pub fn new(
gctx: &GlobalContext,
Expand Down
25 changes: 16 additions & 9 deletions src/compiler/standard_lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::ops::{self, Packages};
use crate::resolver::HasDevUnits;
use crate::resolver::Resolve;
use crate::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures};
use crate::util::errors::CargoResult;
use crate::util::{CargoResult, GlobalContext};
use crate::workspace::profiles::{Profiles, UnitFor};
use crate::workspace::{PackageId, PackageSet, Workspace};

Expand All @@ -16,7 +16,11 @@ use std::path::PathBuf;

use super::BuildConfig;

fn std_crates<'a>(crates: &'a [String], default: &'static str, units: &[Unit]) -> HashSet<&'a str> {
pub fn std_crates<'a>(
crates: &'a [String],
default: &'static str,
units: &[Unit],
) -> HashSet<&'a str> {
let mut crates = HashSet::from_iter(crates.iter().map(|s| s.as_str()));
// This is a temporary hack until there is a more principled way to
// declare dependencies in Cargo.toml.
Expand Down Expand Up @@ -54,12 +58,13 @@ pub fn resolve_std<'gctx>(
crates: &[String],
kinds: &[CompileKind],
) -> CargoResult<(PackageSet<'gctx>, Resolve, ResolvedFeatures)> {
let src_path = detect_sysroot_src_path(ws)?;
let src_path = detect_sysroot_src_path(ws.gctx(), Some(ws))?;
let std_ws_manifest_path = src_path.join("Cargo.toml");
let gctx = ws.gctx();
// TODO: Consider doing something to enforce --locked? Or to prevent the
// lock file from being written, such as setting ephemeral.
let mut std_ws = Workspace::new(&std_ws_manifest_path, gctx)?;
std_ws.set_is_std(true);
// Don't require optional dependencies in this workspace, aka std's own
// `[dev-dependencies]`. No need for us to generate a `Resolve` which has
// those included because we'll never use them anyway.
Expand Down Expand Up @@ -217,15 +222,17 @@ fn generate_roots(
Ok(())
}

fn detect_sysroot_src_path(ws: &Workspace<'_>) -> CargoResult<PathBuf> {
if let Some(s) = ws.gctx().get_env_os("__CARGO_TESTS_ONLY_SRC_ROOT") {
pub fn detect_sysroot_src_path(
gctx: &GlobalContext,
ws: Option<&Workspace<'_>>,
) -> CargoResult<PathBuf> {
if let Some(s) = gctx.get_env_os("__CARGO_TESTS_ONLY_SRC_ROOT") {
return Ok(s.into());
}

// NOTE: This is temporary until we figure out how to acquire the source.
let rustc = ws.gctx().load_global_rustc(Some(ws))?;
let src_path = ws
.gctx()
let rustc = gctx.load_global_rustc(ws)?;
let src_path = gctx
.get_sysroot(&rustc)
.expect("able to invoke rustc")
.join("lib")
Expand All @@ -240,7 +247,7 @@ fn detect_sysroot_src_path(ws: &Workspace<'_>) -> CargoResult<PathBuf> {
library, try:\n rustup component add rust-src",
lock
);
match ws.gctx().get_env("RUSTUP_TOOLCHAIN") {
match gctx.get_env("RUSTUP_TOOLCHAIN") {
Ok(rustup_toolchain) => {
anyhow::bail!("{} --toolchain {}", msg, rustup_toolchain);
}
Expand Down
Loading