From 4ea391daaea07da20b30373d5d424d3165771c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Tue, 17 Mar 2026 12:34:27 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E8=87=B3=200.8.14=EF=BC=8C=E5=B9=B6=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E5=AF=B9=20someboot=20=E7=9A=84=E6=9E=84=E5=BB=BA=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=A3=80=E6=B5=8B=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- ostool/Cargo.toml | 2 +- ostool/src/build/cargo_builder.rs | 20 +-- ostool/src/build/someboot.rs | 196 +++++++++++++++++++++++++++++- ostool/src/ctx.rs | 7 +- 5 files changed, 214 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 765ed00..2eecc80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1910,7 +1910,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "ostool" -version = "0.8.13" +version = "0.8.14" dependencies = [ "anyhow", "byte-unit", diff --git a/ostool/Cargo.toml b/ostool/Cargo.toml index 908e3aa..ea94159 100644 --- a/ostool/Cargo.toml +++ b/ostool/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" name = "ostool" readme = "../README.md" repository = "https://github.com/drivercraft/ostool" -version = "0.8.13" +version = "0.8.14" [package.metadata.binstall] bin-dir = "{ name }-{ target }-v{ version }/{ bin }{ binary-ext }" diff --git a/ostool/src/build/cargo_builder.rs b/ostool/src/build/cargo_builder.rs index ae3f0ac..2dfc157 100644 --- a/ostool/src/build/cargo_builder.rs +++ b/ostool/src/build/cargo_builder.rs @@ -302,14 +302,18 @@ impl<'a> CargoBuilder<'a> { // Auto-detected args from someboot/build-info.toml let workspace_manifest = self.ctx.paths.workspace.join("Cargo.toml"); if workspace_manifest.exists() { - let detected_args = - someboot::detect_build_config(&workspace_manifest, &self.config.target) - .with_context(|| { - format!( - "failed to detect someboot build config from {}", - workspace_manifest.display() - ) - })?; + let detected_args = someboot::detect_build_config_for_package( + &workspace_manifest, + &self.config.package, + &features, + &self.config.target, + ) + .with_context(|| { + format!( + "failed to detect someboot build config from {}", + workspace_manifest.display() + ) + })?; for arg in detected_args { cmd.arg(arg); } diff --git a/ostool/src/build/someboot.rs b/ostool/src/build/someboot.rs index 8811236..b419f61 100644 --- a/ostool/src/build/someboot.rs +++ b/ostool/src/build/someboot.rs @@ -65,6 +65,62 @@ pub fn detect_build_config(manifest_path: &PathBuf, target: &str) -> anyhow::Res Ok(cargo_args) } +pub fn detect_build_config_for_package( + manifest_path: &PathBuf, + package: &str, + features: &[String], + target: &str, +) -> anyhow::Result> { + let mut cargo_args = Vec::new(); + + if !someboot_reachable_for_package(manifest_path, package, features, target)? { + return Ok(cargo_args); + } + + let meta = read_metadata(manifest_path, false)?; + let someboot_roots = collect_someboot_roots(&meta); + if someboot_roots.is_empty() { + return Ok(cargo_args); + } + + let build_info_path = someboot_roots + .into_iter() + .map(|root| root.join("build-info.toml")) + .find(|p| p.exists()); + + let Some(build_info_path) = build_info_path else { + return Ok(cargo_args); + }; + + let build_info_raw = std::fs::read_to_string(&build_info_path).with_context(|| { + format!( + "failed to read build-info.toml: {}", + build_info_path.display() + ) + })?; + + let build_info: HashMap = toml::from_str(&build_info_raw) + .with_context(|| { + format!( + "failed to parse build-info.toml at {}", + build_info_path.display() + ) + })?; + + let Some(matched) = pick_target_build_info(&build_info, target) else { + return Ok(cargo_args); + }; + + cargo_args.extend(matched.cargoargs.iter().cloned()); + + if !matched.rustflags.is_empty() { + cargo_args.push("--config".to_string()); + cargo_args.push(rustflags_to_cargo_override(target, &matched.rustflags)); + } + + Ok(cargo_args) +} + fn read_metadata( manifest_path: &PathBuf, no_deps: bool, @@ -110,6 +166,56 @@ fn collect_someboot_roots(meta: &cargo_metadata::Metadata) -> Vec { someboot_roots } +fn someboot_reachable_for_package( + manifest_path: &PathBuf, + package: &str, + features: &[String], + target: &str, +) -> anyhow::Result { + let mut cmd = std::process::Command::new("cargo"); + cmd.arg("tree"); + cmd.arg("--manifest-path"); + cmd.arg(manifest_path); + cmd.arg("-p"); + cmd.arg(package); + cmd.arg("--target"); + cmd.arg(target); + cmd.arg("-e"); + cmd.arg("normal,build"); + cmd.arg("--prefix"); + cmd.arg("none"); + cmd.arg("--format"); + cmd.arg("{p}"); + if !features.is_empty() { + cmd.arg("--features"); + cmd.arg(features.join(",")); + } + + let output = cmd.output().with_context(|| { + format!( + "failed to run `cargo tree` for package `{}` and target `{}`", + package, target + ) + })?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + anyhow::bail!( + "`cargo tree` failed for package `{}` and target `{}`: {}\nstderr:\n{}", + package, + target, + output.status, + stderr + ); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + Ok(stdout + .lines() + .map(str::trim) + .any(|line| line.starts_with("someboot v"))) +} + fn pick_target_build_info<'a>( build_info: &'a HashMap, target: &str, @@ -147,8 +253,12 @@ fn rustflags_to_cargo_override(target: &str, rustflags: &[String]) -> String { #[cfg(test)] mod tests { - use super::detect_build_config; - use std::path::PathBuf; + use super::{detect_build_config, detect_build_config_for_package}; + use std::{ + fs, + path::PathBuf, + time::{SystemTime, UNIX_EPOCH}, + }; #[test] fn test_local() { @@ -204,4 +314,86 @@ mod tests { .any(|arg| arg.starts_with("target.aarch64-unknown-none-softfloat.rustflags=")) ); } + + #[test] + fn detect_build_config_for_package_skips_unreachable_optional_someboot() { + let root = std::env::temp_dir().join(format!( + "ostool-someboot-test-{}-{}", + std::process::id(), + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be valid") + .as_nanos() + )); + + if root.exists() { + fs::remove_dir_all(&root).expect("failed to remove old temp dir"); + } + + fs::create_dir_all(root.join("app/src")).unwrap(); + fs::create_dir_all(root.join("helper/src")).unwrap(); + fs::create_dir_all(root.join("someboot/src")).unwrap(); + + fs::write( + root.join("Cargo.toml"), + "[workspace]\nmembers = [\"app\", \"helper\", \"someboot\"]\nresolver = \"2\"\n", + ) + .unwrap(); + + fs::write( + root.join("app/Cargo.toml"), + "[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\n\ + helper = { path = \"../helper\", default-features = false }\n\n[features]\n\ + with-someboot = [\"helper/use-someboot\"]\n", + ) + .unwrap(); + fs::write(root.join("app/src/main.rs"), "fn main() {}\n").unwrap(); + + fs::write( + root.join("helper/Cargo.toml"), + "[package]\nname = \"helper\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[features]\n\ + use-someboot = [\"dep:someboot\"]\n\n[dependencies]\n\ + someboot = { path = \"../someboot\", optional = true }\n", + ) + .unwrap(); + fs::write(root.join("helper/src/lib.rs"), "pub fn helper() {}\n").unwrap(); + + fs::write( + root.join("someboot/Cargo.toml"), + "[package]\nname = \"someboot\"\nversion = \"0.1.0\"\nedition = \"2021\"\n", + ) + .unwrap(); + fs::write(root.join("someboot/src/lib.rs"), "pub fn marker() {}\n").unwrap(); + fs::write( + root.join("someboot/build-info.toml"), + "[x86_64-unknown-none]\n\ + rustflags = [\"-C\", \"relocation-model=pic\"]\n\ + cargoargs = [\"-Z\", \"build-std=core,alloc\"]\n", + ) + .unwrap(); + + let manifest_path = root.join("Cargo.toml"); + + let without_optional = + detect_build_config_for_package(&manifest_path, "app", &[], "x86_64-unknown-none") + .unwrap(); + assert!(without_optional.is_empty()); + + let with_optional = detect_build_config_for_package( + &manifest_path, + "app", + &["app/with-someboot".to_string()], + "x86_64-unknown-none", + ) + .unwrap(); + assert_eq!(&with_optional[0..2], ["-Z", "build-std=core,alloc"]); + assert_eq!(with_optional[2], "--config"); + assert!( + with_optional + .iter() + .any(|arg| arg.starts_with("target.x86_64-unknown-none.rustflags=")) + ); + + fs::remove_dir_all(&root).expect("failed to remove temp workspace"); + } } diff --git a/ostool/src/ctx.rs b/ostool/src/ctx.rs index ee6844b..e650e24 100644 --- a/ostool/src/ctx.rs +++ b/ostool/src/ctx.rs @@ -356,7 +356,12 @@ impl AppContext { fn someboot_cargo_args(&self, cargo: &Cargo) -> anyhow::Result> { let manifest_path = self.paths.manifest.join("Cargo.toml"); let target = &cargo.target; - someboot::detect_build_config(&manifest_path, target) + someboot::detect_build_config_for_package( + &manifest_path, + &cargo.package, + &cargo.features, + target, + ) } /// Replaces variable placeholders in a string.