Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 34 additions & 14 deletions src/cargo/core/compiler/standard_lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::core::compiler::UnitInterner;
use crate::core::compiler::unit_dependencies::IsArtifact;
use crate::core::compiler::{CompileKind, CompileMode, RustcTargetData, Unit};
use crate::core::profiles::{Profiles, UnitFor};
use crate::core::profiles::{PanicStrategy, Profiles, UnitFor};
use crate::core::resolver::HasDevUnits;
use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures};
use crate::core::{PackageId, PackageSet, Resolve, Workspace};
Expand All @@ -15,7 +15,12 @@ use std::path::PathBuf;

use super::BuildConfig;

fn std_crates<'a>(crates: &'a [String], default: &'static str, units: &[Unit]) -> HashSet<&'a str> {
fn std_crates<'a>(
crates: &'a [String],
default: &'static str,
units: &[Unit],
profiles: &Profiles,
) -> 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 All @@ -26,7 +31,7 @@ fn std_crates<'a>(crates: &'a [String], default: &'static str, units: &[Unit]) -
crates.insert("core");
crates.insert("alloc");
crates.insert("proc_macro");
crates.insert("panic_unwind");
crates.insert("panic_abort");

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 should be built by default. The reason the strategies are roots is that rustc won't pick them up unless they're passed in via --extern. We have a section in the build-std=always RFC for this, but it's not next to the other section on panic strategies (sorry!)

rustc loads panic runtimes in a different way to most dependencies, and without looking in the sysroot they will fail to load correctly unless passed in with --extern. rustc will need to be patched to be able to load panic runtimes from -L dependency= paths in line with other transitive dependencies.

This doesn't need to block this PR though as the change to this patch to support that will be quite small, and this function will change a lot in the future anyway when the UI changes.

crates.insert("compiler_builtins");
// Only build libtest if it looks like it is needed (libtest depends on libstd)
// If we know what units we're building, we can filter for libtest depending on the jobs.
Expand All @@ -36,6 +41,13 @@ fn std_crates<'a>(crates: &'a [String], default: &'static str, units: &[Unit]) -
{
crates.insert("test");
}
// Conditionally build `panic_unwind` based on the user's profile settings.
// NOTE: Base profile is enough here since `panic` profile cannot be
// overridden. See `validate_profile_override`.
let profile = profiles.base_profile();
if profile.panic == PanicStrategy::Unwind {
crates.insert("panic_unwind");

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.

Should be enabled via a feature on sysroot eventually once the rustc loading behaviour has been changed. Again, this doesn't need to block this PR.

}
Comment on lines +47 to +50

@ehuss ehuss Jul 16, 2026

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 doesn't seem to handle the situation for cargo test which forces the unwind strategy to be "unwind" even when the strategy is "abort". Thus, when running cargo test, it will end up with the same duplicate lang item problem since it tries to load the wrong unwind.

There's also some complexities when running host tests (proc-macro tests), which I think are also AlwaysUnwind.

I'm a little concerned that trying to deal with these complexities will make this optimization difficult to support. For abort-only targets, the unwind crate should be empty and essentially have no cost.

View changes since the review

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
Author

Choose a reason for hiding this comment

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

it turned out that the test is incorrect due to:

panic_unwind = { path = "../panic_unwind" }

Today I plan to open a PR in rustc to address #17185 (comment) (load panic runtime with -L dependency) and then I'll come back to this concern.

} else if crates.contains("core") {
crates.insert("compiler_builtins");
}
Expand Down Expand Up @@ -63,29 +75,37 @@ pub fn resolve_std<'gctx>(
// `[dev-dependencies]`. No need for us to generate a `Resolve` which has
// those included because we'll never use them anyway.
std_ws.set_require_optional_deps(false);
let specs = {
let profiles = Profiles::new(ws, build_config.requested_profile)?;

@ehuss ehuss Jul 16, 2026

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.

Just a minor nit, but I worry that this function has the possibility to be expensive to call. Would it be possible to pass it in?

View changes since the review

let (specs, build_panic_unwind) = {
// If there is anything looks like needing std, resolve with it.
// If not, we assume only `core` maye be needed, as `core the most fundamental crate.
// If not, we assume only `core` may be needed, as `core` is the most fundamental crate.
//
// This may need a UI overhaul if `build-std` wants to fully support multi-targets.
let maybe_std = kinds
.iter()
.any(|kind| target_data.info(*kind).maybe_support_std());
let mut crates = std_crates(crates, if maybe_std { "std" } else { "core" }, &[]);
let mut crates = std_crates(
crates,
if maybe_std { "std" } else { "core" },
&[],
&profiles,
);
let build_panic_unwind = crates.contains("panic_unwind");
// `sysroot` is not in the default set because it is optional, but it needs
// to be part of the resolve in case we do need it or `libtest`.
crates.insert("sysroot");
let specs = Packages::Packages(crates.into_iter().map(Into::into).collect());
specs.to_package_id_specs(&std_ws)?
(specs.to_package_id_specs(&std_ws)?, build_panic_unwind)
};
let features = match &gctx.cli_unstable().build_std_features {
let mut features = match &gctx.cli_unstable().build_std_features {
Some(list) => list.clone(),
None => vec![
"panic-unwind".to_string(),
"backtrace".to_string(),
"default".to_string(),
],
None => vec!["backtrace".to_string(), "default".to_string()],
};

if build_panic_unwind {
features.push("panic-unwind".to_string());
}

let cli_features = CliFeatures::from_command_line(
&features, /*all_features*/ false, /*uses_default_features*/ false,
)?;
Expand Down Expand Up @@ -167,7 +187,7 @@ fn generate_roots(
profiles: &Profiles,
target_data: &RustcTargetData<'_>,
) -> CargoResult<()> {
let std_ids = std_crates(crates, default, units)
let std_ids = std_crates(crates, default, units, profiles)
.iter()
.map(|crate_name| std_resolve.query(crate_name))
.collect::<CargoResult<Vec<PackageId>>>()?;
Expand Down
22 changes: 22 additions & 0 deletions tests/build-std/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,28 @@ fn default_features_still_included_with_extra_build_std_features() {
.run();
}

#[cargo_test(build_std_real)]
fn duplicate_lang_item_with_panic_abort() {
// This is a regression test to ensure that rustc doesn't load `panic_abort`
// from the sysroot. See rust-lang/cargo#7359
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2021"

[profile.dev]
panic = 'abort'
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

p.cargo("check").build_std_arg("std").run();
}

pub trait CargoProjectExt {
/// Creates a `ProcessBuilder` to run cargo.
///
Expand Down
8 changes: 8 additions & 0 deletions tests/testsuite/mock-std/library/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
cargo-features = ["profile-rustflags"]

[workspace]
resolver = "1"
members = [
"std",
"sysroot",
]

[profile.dev.package.panic_abort]
rustflags = ["-Cpanic=abort"]

[profile.release.package.panic_abort]
rustflags = ["-Cpanic=abort"]

[patch.crates-io]
rustc-std-workspace-core = { path = 'rustc-std-workspace-core' }
rustc-std-workspace-alloc = { path = 'rustc-std-workspace-alloc' }
Expand Down
6 changes: 6 additions & 0 deletions tests/testsuite/mock-std/library/panic_abort/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "panic_abort"
version = "0.1.0"
edition = "2018"

[dependencies]
5 changes: 5 additions & 0 deletions tests/testsuite/mock-std/library/panic_abort/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![feature(panic_abort, panic_runtime)]
#![panic_runtime]
#![no_std]

extern crate panic_abort;
4 changes: 4 additions & 0 deletions tests/testsuite/mock-std/library/std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ edition = "2018"

[dependencies]
registry-dep-using-alloc = { version = "*", features = ['mockbuild'] }
panic_unwind = { path = "../panic_unwind", optional = true }
panic_abort = { path = "../panic_abort" }
dep_test = { path = "../../dep_test" }

[features]
default = []
panic-unwind = ["dep:panic_unwind"]
feature1 = []
2 changes: 1 addition & 1 deletion tests/testsuite/mock-std/library/sysroot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ std = { path = "../std" }
test = { path = "../test" }

[features]
panic-unwind = []
panic-unwind = ["std/panic-unwind"]
backtrace = []
feature1 = ["std/feature1"]
default = []
186 changes: 186 additions & 0 deletions tests/testsuite/standard_lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ fn build_std_with_no_arg_for_core_only_target() {
[COMPILING] dep_test v0.1.0 ([..]/dep_test)
[COMPILING] compiler_builtins v0.1.0 ([..]/library/compiler_builtins)
[COMPILING] proc_macro v0.1.0 ([..]/library/proc_macro)
[COMPILING] panic_abort v0.1.0 ([..]/library/panic_abort)
[COMPILING] panic_unwind v0.1.0 ([..]/library/panic_unwind)
[COMPILING] rustc-std-workspace-core v1.9.0 ([..]/library/rustc-std-workspace-core)
[COMPILING] foo v0.0.1 ([ROOT]/foo)
Expand All @@ -467,6 +468,7 @@ fn build_std_with_no_arg_for_core_only_target() {
[RUNNING] `[..]rustc --crate-name core [..]--target [HOST_TARGET][..]`
[RUNNING] `[..]rustc --crate-name dep_test [..]--target [HOST_TARGET][..]`
[RUNNING] `[..]rustc --crate-name proc_macro [..]--target [HOST_TARGET][..]`
[RUNNING] `[..]rustc --crate-name panic_abort [..]--target [HOST_TARGET][..]`
[RUNNING] `[..]rustc --crate-name panic_unwind [..]--target [HOST_TARGET][..]`
[RUNNING] `[..]rustc --crate-name compiler_builtins [..]--target [HOST_TARGET][..]`
[RUNNING] `[..]rustc --crate-name rustc_std_workspace_core [..]--target [HOST_TARGET][..]`
Expand Down Expand Up @@ -890,3 +892,187 @@ fn std_build_script_metadata_propagate_to_user() {

p.cargo("check").build_std(&setup).target_host().run();
}

#[cargo_test(build_std_mock)]
fn panic_unwind_no_std_build() {
// If panic is set to unwind and Cargo is not building std
// 1) neither of the panic runtimes is built
// 2) rustc throw an error (for targets that aren't rlibs)
let setup = setup();

let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2024"
[lib]
crate-type = ["dylib"]
[profile.dev]
panic = 'unwind'
"#,
)
.file("src/lib.rs", "#![no_std]")
.build();

p.cargo("check")
.build_std_arg(&setup, "core")
.target_host()
.with_stderr_does_not_contain("[COMPILING] panic_abort [..]")
.with_stderr_does_not_contain("[COMPILING] panic_unwind [..]")
.with_stderr_contains("[ERROR] unwinding panics are not supported without std")
.with_status(101)
.run();
}

#[cargo_test(build_std_mock)]
fn panic_not_set_build_std() {
// If Cargo is building std and panic is not specified in the profile
// 1) enable the `panic-unwind` feature to build `panic_unwind`
// 2) build `panic_abort`
let setup = setup();

let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2024"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

p.cargo("check -v")
.build_std_arg(&setup, "std")
.with_stderr_contains("[COMPILING] panic_abort [..]")
.with_stderr_contains("[COMPILING] panic_unwind [..]")
.with_stderr_contains(r#"[RUNNING] `[..]rustc --crate-name std [..]panic-unwind[..]`"#)
.with_status(0)
.run();
}

#[cargo_test(build_std_mock)]
fn panic_unwind_build_std() {
// If Cargo is building std and panic is set to unwind
// 1) enable the `panic-unwind` feature to build `panic_unwind`
// 2) build `panic_abort`
let setup = setup();

let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2024"
[profile.dev]
panic = 'unwind'
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

p.cargo("check -v")
.build_std_arg(&setup, "std")
.with_stderr_contains("[COMPILING] panic_abort [..]")
.with_stderr_contains("[COMPILING] panic_unwind [..]")
.with_stderr_contains(r#"[RUNNING] `[..]rustc --crate-name std [..]panic-unwind[..]`"#)
.run();
}

#[cargo_test(build_std_mock)]
fn panic_abort_build_std() {
// If Cargo is building std and panic is set to abort
// 1) build `panic_abort` only
// 2) pass `-Cpanic=abort`
let setup = setup();

let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2024"
[profile.dev]
panic = 'abort'
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

p.cargo("check -v")
.build_std_arg(&setup, "std")
.target_host()
.with_stderr_contains("[COMPILING] panic_abort [..]")
.with_stderr_does_not_contain("[COMPILING] panic_unwind [..]")
.with_stderr_contains("[RUNNING] `[..]rustc --crate-name foo [..] -C panic=abort [..]")
.run();
}

#[cargo_test(build_std_mock)]
fn panic_immediate_abort_build_std() {
// If Cargo is building std and panic is set to immediate-abort
// 1) build `panic_abort` only
// 2) pass `-Cpanic=immediate-abort`
let setup = setup();

let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["panic-immediate-abort"]
[package]
name = "foo"
edition = "2024"
[profile.dev]
panic = 'immediate-abort'
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

p.cargo("check -v")
.build_std_arg(&setup, "std")
.target_host()
.with_stderr_contains("[COMPILING] panic_abort [..]")
.with_stderr_does_not_contain("[COMPILING] panic_unwind [..]")
.with_stderr_contains(
"[RUNNING] `[..]rustc --crate-name foo [..] -C panic=immediate-abort [..]",
)
.run();
}

#[cargo_test(build_std_mock)]
fn panic_abort_test_bench() {
let setup = setup();

let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2024"
[profile.test]
panic = 'abort'
[profile.bench]
panic = "abort"
"#,
)
.file("src/lib.rs", "")
.build();

p.cargo("build")
.build_std_arg(&setup, "std")
.target_host()
.with_stderr_contains("[COMPILING] panic_unwind [..]")
.with_stderr_contains(
"[WARNING] Cargo.toml: `panic` setting is ignored for `bench` profile",
)
.with_stderr_contains("[WARNING] Cargo.toml: `panic` setting is ignored for `test` profile")
.run();
}