diff --git a/ostool/src/artifact/state.rs b/ostool/src/artifact/state.rs index d3a61b1..f0beb7f 100644 --- a/ostool/src/artifact/state.rs +++ b/ostool/src/artifact/state.rs @@ -40,6 +40,14 @@ impl OutputArtifacts { self.runtime_artifact_dir.as_deref() } + /// Returns whether no runtime artifact has been recorded. + pub(crate) fn is_empty(&self) -> bool { + self.elf.is_none() + && self.bin.is_none() + && self.cargo_artifact_dir.is_none() + && self.runtime_artifact_dir.is_none() + } + /// Returns the preferred image path for runners that can load BIN or ELF. pub(crate) fn runtime_image(&self) -> Option<&Path> { self.bin().or_else(|| self.elf()) diff --git a/ostool/src/board/mod.rs b/ostool/src/board/mod.rs index 450f730..dc6d3d5 100644 --- a/ostool/src/board/mod.rs +++ b/ostool/src/board/mod.rs @@ -185,7 +185,7 @@ impl Tool { cargo: &Cargo, path: &Path, ) -> anyhow::Result { - self.sync_cargo_context(cargo); + self.sync_cargo_context(cargo)?; let scope = self.variable_scope()?; let path = variables::expand_path_variables(path, &scope)?; BoardRunConfig::read_from_path(&scope, path) @@ -196,7 +196,7 @@ impl Tool { cargo: &Cargo, dir: &Path, ) -> anyhow::Result { - self.sync_cargo_context(cargo); + self.sync_cargo_context(cargo)?; let scope = self.variable_scope()?; let dir = variables::expand_path_variables(dir, &scope)?; BoardRunConfig::load_or_create(&scope, Some(dir.join(".board.toml"))).await @@ -236,7 +236,7 @@ impl Tool { board_config: &BoardRunConfig, options: RunBoardOptions, ) -> anyhow::Result<()> { - self.sync_cargo_context(cargo); + self.sync_cargo_context(cargo)?; self.run_board_with_build_config( &BuildConfig { system: BuildSystem::Cargo(cargo.clone()), diff --git a/ostool/src/build/cargo_pipeline.rs b/ostool/src/build/cargo_pipeline.rs index ff97972..b37345f 100644 --- a/ostool/src/build/cargo_pipeline.rs +++ b/ostool/src/build/cargo_pipeline.rs @@ -16,7 +16,6 @@ use cargo_metadata::{Message, PackageId}; use colored::Colorize; use crate::{ - Tool, build::{ artifact_selector::{ CargoExecutableArtifact, ResolvedCargoArtifact, select_executable_artifact, @@ -24,9 +23,41 @@ use crate::{ config::{Cargo, CargoBuildProfile}, someboot, }, + process::ProcessContext, + project::{ProjectLayout, metadata}, utils::{Command, PathResultExt}, }; +#[derive(Debug, Clone)] +pub(super) struct CargoBuildInput { + project_layout: ProjectLayout, + process_context: ProcessContext, + build_dir: PathBuf, + config_path: Option, + debug: bool, + enable_someboot_build_config: bool, +} + +impl CargoBuildInput { + pub(super) fn new( + project_layout: ProjectLayout, + process_context: ProcessContext, + build_dir: PathBuf, + config_path: Option, + debug: bool, + enable_someboot_build_config: bool, + ) -> Self { + Self { + project_layout, + process_context, + build_dir, + config_path, + debug, + enable_someboot_build_config, + } + } +} + #[derive(Debug, Clone)] pub(crate) struct CargoBuildOutcome { resolved_artifact: ResolvedCargoArtifact, @@ -49,7 +80,7 @@ impl CargoBuildOutcome { /// /// This builder is an internal implementation detail used by [`Tool`]. pub struct CargoBuildPipeline<'a> { - tool: &'a mut Tool, + input: CargoBuildInput, config: &'a Cargo, cargo_program: PathBuf, command: String, @@ -57,7 +88,6 @@ pub struct CargoBuildPipeline<'a> { extra_envs: HashMap, skip_objcopy: bool, resolve_artifact_from_json: bool, - config_path: Option, } impl<'a> CargoBuildPipeline<'a> { @@ -65,12 +95,11 @@ impl<'a> CargoBuildPipeline<'a> { /// /// # Arguments /// - /// * `tool` - The tool context. + /// * `input` - Invocation-scoped Cargo build inputs. /// * `config` - The Cargo build configuration. - /// * `config_path` - Optional path to the configuration file. - pub fn build(tool: &'a mut Tool, config: &'a Cargo, config_path: Option) -> Self { + pub(super) fn build(input: CargoBuildInput, config: &'a Cargo) -> Self { Self { - tool, + input, config, cargo_program: PathBuf::from("cargo"), command: "build".to_string(), @@ -78,24 +107,9 @@ impl<'a> CargoBuildPipeline<'a> { extra_envs: HashMap::new(), skip_objcopy: false, resolve_artifact_from_json: true, - config_path, } } - /// Sets the debug mode for the build. - /// - /// When enabled, builds in debug mode and enables GDB server for QEMU. - pub fn debug(self, debug: bool) -> Self { - self.tool.config.debug = debug; - self - } - - /// Creates a build command using the context's stored config path. - pub fn build_auto(tool: &'a mut Tool, config: &'a Cargo) -> Self { - let config_path = tool.ctx.build_config_path.clone(); - Self::build(tool, config, config_path) - } - /// Sets whether to skip the objcopy step after building. pub fn skip_objcopy(mut self, skip: bool) -> Self { self.skip_objcopy = skip; @@ -132,9 +146,8 @@ impl<'a> CargoBuildPipeline<'a> { } fn run_pre_build_cmds(&mut self) -> anyhow::Result<()> { - let process_context = self.tool.process_context()?; for cmd in &self.config.pre_build_cmds { - crate::process::shell_run_cmd(&process_context, cmd)?; + crate::process::shell_run_cmd(&self.input.process_context, cmd)?; } Ok(()) } @@ -216,8 +229,8 @@ impl<'a> CargoBuildPipeline<'a> { } async fn build_cargo_command(&mut self) -> anyhow::Result { - let process_context = self.tool.process_context()?; - let mut cmd = crate::process::command(self.cargo_program.as_os_str(), &process_context); + let mut cmd = + crate::process::command(self.cargo_program.as_os_str(), &self.input.process_context); cmd.arg(&self.command); @@ -249,7 +262,7 @@ impl<'a> CargoBuildPipeline<'a> { cmd.arg("unstable-options"); cmd.arg("--target-dir"); - cmd.arg(self.tool.build_dir().display().to_string()); + cmd.arg(self.input.build_dir.display().to_string()); // Features let features = self.build_features(); @@ -264,8 +277,8 @@ impl<'a> CargoBuildPipeline<'a> { } // Auto-detected args from someboot/build-info.toml - let workspace_manifest = self.tool.workspace_dir().join("Cargo.toml"); - if self.tool.someboot_build_config_enabled(self.config) && workspace_manifest.exists() { + let workspace_manifest = self.input.project_layout.workspace_dir().join("Cargo.toml"); + if self.input.enable_someboot_build_config && workspace_manifest.exists() { let detected_args = someboot::detect_build_config_for_package( &workspace_manifest, &self.config.package, @@ -300,7 +313,7 @@ impl<'a> CargoBuildPipeline<'a> { } fn target_package_info(&self) -> anyhow::Result<(PackageId, Option)> { - let metadata = self.tool.metadata()?; + let metadata = metadata::cargo_metadata(&self.input.project_layout)?; let Some(package) = metadata .packages .iter() @@ -309,7 +322,7 @@ impl<'a> CargoBuildPipeline<'a> { bail!( "package '{}' not found in cargo metadata under {}", self.config.package, - self.tool.manifest_dir().display() + self.input.project_layout.manifest_dir().display() ); }; Ok((package.id.clone(), package.default_run.clone())) @@ -324,19 +337,18 @@ impl<'a> CargoBuildPipeline<'a> { } fn effective_profile(&self) -> CargoBuildProfile { - self.config.profile.unwrap_or_else(|| { - if self.tool.debug_enabled() { - CargoBuildProfile::Debug - } else { - CargoBuildProfile::Release - } - }) + let default_profile = if self.input.debug { + CargoBuildProfile::Debug + } else { + CargoBuildProfile::Release + }; + self.config.profile.unwrap_or(default_profile) } fn log_level_feature(&self) -> Option { let level = self.config.log.clone()?; - let meta = self.tool.metadata().ok()?; + let meta = metadata::cargo_metadata(&self.input.project_layout).ok()?; let pkg = meta .packages .iter() @@ -384,7 +396,7 @@ impl<'a> CargoBuildPipeline<'a> { let extra = Path::new(s); if extra.is_relative() { - if let Some(ref config_path) = self.config_path { + if let Some(ref config_path) = self.input.config_path { let combined = config_path .parent() .ok_or_else(|| { @@ -538,12 +550,13 @@ mod tests { ..Default::default() }; - let mut tool = Tool::new(ToolConfig { + let tool = Tool::new(ToolConfig { manifest: Some(temp.path().to_path_buf()), ..Default::default() }) .unwrap(); - let mut builder = CargoBuildPipeline::build(&mut tool, &config, None).skip_objcopy(true); + let input = tool.cargo_build_input(&config, false).unwrap(); + let mut builder = CargoBuildPipeline::build(input, &config).skip_objcopy(true); let cmd = builder.build_cargo_command().await.unwrap(); let args: Vec = cmd .get_args() @@ -580,7 +593,7 @@ mod tests { ..Default::default() }; - let mut tool = Tool::new(ToolConfig { + let tool = Tool::new(ToolConfig { manifest: Some(temp.path().to_path_buf()), ..Default::default() }) @@ -612,7 +625,8 @@ mod tests { fs::set_permissions(&cargo_bin, permissions).unwrap(); } - let outcome = CargoBuildPipeline::build(&mut tool, &config, None) + let input = tool.cargo_build_input(&config, false).unwrap(); + let outcome = CargoBuildPipeline::build(input, &config) .skip_objcopy(true) .cargo_program(&cargo_bin) .execute() diff --git a/ostool/src/build/mod.rs b/ostool/src/build/mod.rs index b6be85d..eb9ddbc 100644 --- a/ostool/src/build/mod.rs +++ b/ostool/src/build/mod.rs @@ -20,13 +20,17 @@ use std::path::{Path, PathBuf}; +use anyhow::bail; + use crate::{ Tool, artifact::runtime::{RuntimeArtifactOptions, prepare_runtime_artifacts}, build::{ - cargo_pipeline::{CargoBuildOutcome, CargoBuildPipeline}, - config::{Cargo, Custom}, + cargo_pipeline::{CargoBuildInput, CargoBuildOutcome, CargoBuildPipeline}, + config::{BuildConfig, BuildSystem, Cargo, Custom}, }, + invocation::{ActiveBuildContext, ActiveCargoBuild, ActiveCustomBuild}, + project::{ProjectLayout, metadata, variables::VariableScope}, run::{ qemu::{QemuConfig, RunQemuOptions}, uboot::{RunUbootOptions, UbootConfig}, @@ -88,6 +92,77 @@ impl CargoRunnerKind { } } +/// CLI overrides for Cargo package and binary target selection. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct CargoSelector { + package: Option, + bin: Option, +} + +impl CargoSelector { + /// Creates a Cargo selector from optional CLI overrides. + #[cfg(test)] + pub(crate) fn new(package: Option, bin: Option) -> Self { + Self { package, bin } + } + + /// Returns whether no selector override was supplied. + fn is_empty(&self) -> bool { + self.package.is_none() && self.bin.is_none() + } +} + +/// Applies Cargo selector overrides to a build configuration. +fn apply_cargo_selector( + config: &mut config::BuildConfig, + selector: &CargoSelector, +) -> anyhow::Result<()> { + if selector.is_empty() { + return Ok(()); + } + + let config::BuildSystem::Cargo(cargo_config) = &mut config.system else { + bail!("--package/--bin can only be used with system.Cargo build configs"); + }; + + if let Some(package) = &selector.package { + cargo_config.package = package.clone(); + } + if let Some(bin) = &selector.bin { + cargo_config.bin = Some(bin.clone()); + } + Ok(()) +} + +pub(crate) fn activate_build_context( + layout: &ProjectLayout, + mut config: BuildConfig, + config_path: Option, + selector: &CargoSelector, +) -> anyhow::Result { + apply_cargo_selector(&mut config, selector)?; + match config.system { + BuildSystem::Cargo(cargo) => { + let package_dir = metadata::package_manifest_dir(layout, &cargo.package)?; + let variable_scope = VariableScope::for_package(layout, package_dir.clone()); + Ok(ActiveBuildContext::Cargo(Box::new(ActiveCargoBuild::new( + cargo, + config_path, + variable_scope, + )))) + } + BuildSystem::Custom(custom) => { + let variable_scope = + VariableScope::for_package(layout, layout.manifest_dir().to_path_buf()); + Ok(ActiveBuildContext::Custom(ActiveCustomBuild::new( + custom, + config_path, + variable_scope, + ))) + } + } +} + impl Tool { /// Returns the default build configuration template. pub fn default_build_config(&self) -> config::BuildConfig { @@ -124,6 +199,7 @@ impl Tool { /// /// Returns an error if the build process fails. pub async fn build_with_config(&mut self, config: &config::BuildConfig) -> anyhow::Result<()> { + self.sync_build_context(config)?; match &config.system { config::BuildSystem::Custom(custom) => self.build_custom(custom)?, config::BuildSystem::Cargo(cargo) => { @@ -153,11 +229,11 @@ impl Tool { /// /// Returns an error if the Cargo build fails. pub async fn cargo_build(&mut self, config: &Cargo) -> anyhow::Result<()> { - self.sync_cargo_context(config); - let outcome = cargo_pipeline::CargoBuildPipeline::build_auto(self, config) - .execute() - .await?; - self.apply_cargo_build_outcome(config, &outcome, false)?; + self.sync_cargo_context(config)?; + let debug = self.debug_enabled(); + let input = self.cargo_build_input(config, debug)?; + let outcome = CargoBuildPipeline::build(input, config).execute().await?; + self.apply_cargo_build_outcome(config, &outcome, false, debug)?; self.run_cargo_post_build_cmds(config)?; Ok(()) } @@ -168,6 +244,7 @@ impl Tool { config: &config::BuildConfig, debug: bool, ) -> anyhow::Result<()> { + self.sync_build_context(config)?; match &config.system { config::BuildSystem::Custom(custom) => { self.prepare_custom_runtime_artifacts(custom).await @@ -189,14 +266,14 @@ impl Tool { config: &Cargo, debug: bool, ) -> anyhow::Result<()> { - let build_config_path = self.ctx.build_config_path.clone(); - let outcome = CargoBuildPipeline::build(self, config, build_config_path) - .debug(debug) + self.config.debug = debug; + let input = self.cargo_build_input(config, debug)?; + let outcome = CargoBuildPipeline::build(input, config) .skip_objcopy(true) .resolve_artifact_from_json(true) .execute() .await?; - self.apply_cargo_build_outcome(config, &outcome, true)?; + self.apply_cargo_build_outcome(config, &outcome, true, debug)?; self.run_cargo_post_build_cmds(config)?; Ok(()) } @@ -216,18 +293,18 @@ impl Tool { config: &Cargo, runner: &CargoRunnerKind, ) -> anyhow::Result<()> { - self.sync_cargo_context(config); - let build_config_path = self.ctx.build_config_path.clone(); + self.sync_cargo_context(config)?; let debug = matches!(runner, CargoRunnerKind::Qemu(args) if args.debug); + self.config.debug = debug; - let outcome = CargoBuildPipeline::build(self, config, build_config_path) - .debug(debug) + let input = self.cargo_build_input(config, debug)?; + let outcome = CargoBuildPipeline::build(input, config) .skip_objcopy(true) .resolve_artifact_from_json(true) .execute() .await?; - self.apply_cargo_build_outcome(config, &outcome, true)?; + self.apply_cargo_build_outcome(config, &outcome, true, debug)?; self.run_cargo_post_build_cmds(config)?; match runner { @@ -263,11 +340,23 @@ impl Tool { Ok(()) } + fn cargo_build_input(&self, config: &Cargo, debug: bool) -> anyhow::Result { + Ok(CargoBuildInput::new( + self.project_layout(), + self.process_context()?, + self.build_dir(), + self.ctx.build_config_path.clone(), + debug, + self.someboot_build_config_enabled(config), + )) + } + fn apply_cargo_build_outcome( &mut self, config: &Cargo, outcome: &CargoBuildOutcome, skip_objcopy: bool, + debug: bool, ) -> anyhow::Result<()> { let resolved = outcome.resolved_artifact(); let process_context = self.process_context()?; @@ -277,7 +366,7 @@ impl Tool { elf_path: resolved.elf_path().to_path_buf(), to_bin: config.to_bin && !skip_objcopy, bin_dir: self.bin_dir(), - debug: self.debug_enabled(), + debug, cargo_artifact_dir: Some(resolved.cargo_artifact_dir().to_path_buf()), strip_elf: false, objcopy_program: PathBuf::from("rust-objcopy"), @@ -305,10 +394,13 @@ mod tests { build::{ artifact_selector::ResolvedCargoArtifact, cargo_pipeline::CargoBuildOutcome, - config::{Cargo, CargoBuildProfile}, + config::{BuildConfig, BuildSystem, Cargo, CargoBuildProfile, Custom}, }, + project::resolve_project_layout, }; + use super::{CargoSelector, activate_build_context}; + #[test] fn apply_cargo_build_outcome_records_runtime_artifact_state() { let temp = tempfile::tempdir().unwrap(); @@ -342,7 +434,7 @@ mod tests { cargo_artifact_dir.clone(), )); - tool.apply_cargo_build_outcome(&config, &outcome, true) + tool.apply_cargo_build_outcome(&config, &outcome, true, false) .unwrap(); let expected_elf = elf_path.canonicalize().unwrap(); @@ -358,4 +450,74 @@ mod tests { ); assert!(tool.ctx.arch.is_some()); } + + #[test] + fn activate_build_context_applies_cargo_selector_and_scope() { + let temp = tempfile::tempdir().unwrap(); + fs::write( + temp.path().join("Cargo.toml"), + "[package]\nname = \"kernel\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + ) + .unwrap(); + fs::create_dir_all(temp.path().join("src/bin")).unwrap(); + fs::write(temp.path().join("src/main.rs"), "fn main() {}\n").unwrap(); + fs::write(temp.path().join("src/bin/kernel-qemu.rs"), "fn main() {}\n").unwrap(); + let layout = resolve_project_layout(Some(temp.path().to_path_buf())).unwrap(); + let config_path = temp.path().join(".build.toml"); + let config = BuildConfig { + system: BuildSystem::Cargo(Cargo { + package: "placeholder".into(), + ..Default::default() + }), + }; + + let active = activate_build_context( + &layout, + config, + Some(config_path.clone()), + &CargoSelector::new(Some("kernel".into()), Some("kernel-qemu".into())), + ) + .unwrap(); + + let crate::invocation::ActiveBuildContext::Cargo(active) = active else { + panic!("expected active Cargo build"); + }; + assert_eq!(active.config().package, "kernel"); + assert_eq!(active.config().bin.as_deref(), Some("kernel-qemu")); + assert_eq!(active.config_path(), Some(config_path.as_path())); + assert_eq!(active.variable_scope().package_dir(), temp.path()); + } + + #[test] + fn activate_build_context_rejects_selector_for_custom_build() { + let temp = tempfile::tempdir().unwrap(); + fs::write( + temp.path().join("Cargo.toml"), + "[package]\nname = \"kernel\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + ) + .unwrap(); + fs::create_dir_all(temp.path().join("src")).unwrap(); + fs::write(temp.path().join("src/main.rs"), "fn main() {}\n").unwrap(); + let layout = resolve_project_layout(Some(temp.path().to_path_buf())).unwrap(); + let config = BuildConfig { + system: BuildSystem::Custom(Custom { + build_cmd: "make".into(), + elf_path: "target/kernel.elf".into(), + to_bin: true, + }), + }; + + let err = activate_build_context( + &layout, + config, + None, + &CargoSelector::new(Some("kernel".into()), None), + ) + .unwrap_err(); + + assert!( + err.to_string() + .contains("--package/--bin can only be used with system.Cargo") + ); + } } diff --git a/ostool/src/invocation.rs b/ostool/src/invocation.rs index 725fd7d..f3a788a 100644 --- a/ostool/src/invocation.rs +++ b/ostool/src/invocation.rs @@ -1,8 +1,14 @@ -//! Invocation options and project layout shared by CLI and library entrypoints. +//! Invocation options, project layout, and runtime state shared by entrypoints. use std::path::{Path, PathBuf}; -use crate::project::{ProjectLayout, resolve_project_layout}; +use object::Architecture; + +use crate::{ + artifact::{runtime::PreparedRuntimeArtifacts, state::OutputArtifacts}, + build::config::{BuildConfig, BuildSystem, Cargo, Custom}, + project::{ProjectLayout, resolve_project_layout, variables::VariableScope}, +}; /// Static inputs for one CLI or library invocation. #[derive(Clone, Debug, Default)] @@ -55,6 +61,7 @@ impl InvocationOptions { pub struct Invocation { options: InvocationOptions, project_layout: ProjectLayout, + state: InvocationState, } impl Invocation { @@ -64,6 +71,7 @@ impl Invocation { Ok(Self { options, project_layout, + state: InvocationState::default(), }) } @@ -87,7 +95,255 @@ impl Invocation { self.project_layout.workspace_dir() } - pub(crate) fn into_parts(self) -> (InvocationOptions, ProjectLayout) { - (self.options, self.project_layout) + /// Returns the resolved project layout for this invocation. + pub fn project_layout(&self) -> &ProjectLayout { + &self.project_layout + } + + pub(crate) fn into_parts(self) -> (InvocationOptions, ProjectLayout, InvocationState) { + (self.options, self.project_layout, self.state) + } +} + +/// Mutable runtime state produced while one invocation is executing. +#[derive(Clone, Debug, Default)] +pub(crate) struct InvocationState { + arch: Option, + active_build: Option, + build_config_path: Option, + artifacts: OutputArtifacts, +} + +impl InvocationState { + /// Returns the detected runtime artifact architecture. + pub(crate) fn arch(&self) -> Option { + self.arch + } + + /// Returns the activated build context, if a build config has been loaded. + pub(crate) fn active_build(&self) -> Option<&ActiveBuildContext> { + self.active_build.as_ref() + } + + /// Replaces the currently active build context. + pub(crate) fn set_active_build(&mut self, active_build: ActiveBuildContext) { + self.build_config_path = active_build.config_path().map(PathBuf::from); + self.active_build = Some(active_build); + } + + /// Returns the path used to load the active build configuration. + pub(crate) fn build_config_path(&self) -> Option<&Path> { + self.build_config_path.as_deref() + } + + /// Records the path used to load the active build configuration. + pub(crate) fn set_build_config_path(&mut self, path: Option) { + self.build_config_path = path; + } + + /// Returns prepared runtime artifacts. + pub(crate) fn artifacts(&self) -> &OutputArtifacts { + &self.artifacts + } + + /// Records prepared runtime artifacts and their detected architecture. + pub(crate) fn apply_prepared_runtime_artifacts(&mut self, prepared: &PreparedRuntimeArtifacts) { + self.artifacts.apply_prepared_runtime_artifacts(prepared); + self.arch = prepared.arch(); + } +} + +/// Build configuration after CLI overrides and package scope resolution. +#[derive(Clone, Debug)] +pub(crate) enum ActiveBuildContext { + /// Cargo build configuration selected for this invocation. + Cargo(Box), + /// Custom shell build configuration selected for this invocation. + Custom(ActiveCustomBuild), +} + +impl ActiveBuildContext { + /// Returns the path used to load this build context. + pub(crate) fn config_path(&self) -> Option<&Path> { + match self { + Self::Cargo(active) => active.config_path(), + Self::Custom(active) => active.config_path(), + } + } + + /// Returns the variable scope used for this active build. + pub(crate) fn variable_scope(&self) -> &VariableScope { + match self { + Self::Cargo(active) => active.variable_scope(), + Self::Custom(active) => active.variable_scope(), + } + } + + /// Returns the activated build configuration. + pub(crate) fn build_config(&self) -> BuildConfig { + match self { + Self::Cargo(active) => BuildConfig { + system: BuildSystem::Cargo(active.config().clone()), + }, + Self::Custom(active) => BuildConfig { + system: BuildSystem::Custom(active.config().clone()), + }, + } + } +} + +/// Activated Cargo build configuration. +#[derive(Clone, Debug)] +pub(crate) struct ActiveCargoBuild { + config: Cargo, + config_path: Option, + variable_scope: VariableScope, +} + +impl ActiveCargoBuild { + /// Creates an activated Cargo build context. + pub(crate) fn new( + config: Cargo, + config_path: Option, + variable_scope: VariableScope, + ) -> Self { + Self { + config, + config_path, + variable_scope, + } + } + + /// Returns the final Cargo config after CLI selector overrides. + pub(crate) fn config(&self) -> &Cargo { + &self.config + } + + /// Returns the build config path, if known. + pub(crate) fn config_path(&self) -> Option<&Path> { + self.config_path.as_deref() + } + + /// Returns the variable scope derived from the selected package. + pub(crate) fn variable_scope(&self) -> &VariableScope { + &self.variable_scope + } +} + +/// Activated custom build configuration. +#[derive(Clone, Debug)] +pub(crate) struct ActiveCustomBuild { + config: Custom, + config_path: Option, + variable_scope: VariableScope, +} + +impl ActiveCustomBuild { + /// Creates an activated custom build context. + pub(crate) fn new( + config: Custom, + config_path: Option, + variable_scope: VariableScope, + ) -> Self { + Self { + config, + config_path, + variable_scope, + } + } + + /// Returns the final custom build config. + pub(crate) fn config(&self) -> &Custom { + &self.config + } + + /// Returns the build config path, if known. + pub(crate) fn config_path(&self) -> Option<&Path> { + self.config_path.as_deref() + } + + /// Returns the variable scope used by this custom build. + pub(crate) fn variable_scope(&self) -> &VariableScope { + &self.variable_scope + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + use crate::{ + build::config::{Cargo, CargoBuildProfile}, + invocation::{ActiveBuildContext, ActiveCargoBuild, Invocation, InvocationOptions}, + project::variables::VariableScope, + }; + + fn write_package(root: &std::path::Path) { + fs::write( + root.join("Cargo.toml"), + "[package]\nname = \"kernel\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + ) + .unwrap(); + fs::create_dir_all(root.join("src")).unwrap(); + fs::write(root.join("src/main.rs"), "fn main() {}\n").unwrap(); + } + + #[test] + fn invocation_starts_with_empty_runtime_state() { + let temp = tempfile::tempdir().unwrap(); + write_package(temp.path()); + + let invocation = Invocation::new(InvocationOptions::new( + Some(temp.path().to_path_buf()), + None, + None, + false, + )) + .unwrap(); + + assert!(invocation.state.active_build().is_none()); + assert!(invocation.state.build_config_path().is_none()); + assert!(invocation.state.artifacts().elf().is_none()); + assert!(invocation.state.arch().is_none()); + } + + #[test] + fn invocation_state_records_active_cargo_build() { + let temp = tempfile::tempdir().unwrap(); + write_package(temp.path()); + let mut invocation = Invocation::new(InvocationOptions::new( + Some(temp.path().to_path_buf()), + None, + None, + false, + )) + .unwrap(); + let config_path = temp.path().join(".build.toml"); + let config = Cargo { + target: "x86_64-unknown-none".into(), + package: "kernel".into(), + profile: Some(CargoBuildProfile::Debug), + ..Default::default() + }; + let package_dir = invocation.manifest_dir().to_path_buf(); + let scope = VariableScope::for_package(invocation.project_layout(), package_dir.clone()); + + invocation + .state + .set_active_build(ActiveBuildContext::Cargo(Box::new(ActiveCargoBuild::new( + config.clone(), + Some(config_path.clone()), + scope, + )))); + + assert_eq!( + invocation.state.build_config_path(), + Some(config_path.as_path()) + ); + let Some(ActiveBuildContext::Cargo(active)) = invocation.state.active_build() else { + panic!("active Cargo build missing"); + }; + assert_eq!(active.config(), &config); + assert_eq!(active.variable_scope().package_dir(), package_dir.as_path()); } } diff --git a/ostool/src/legacy_context.rs b/ostool/src/legacy_context.rs new file mode 100644 index 0000000..43835da --- /dev/null +++ b/ostool/src/legacy_context.rs @@ -0,0 +1,72 @@ +//! Temporary compatibility bridge between `InvocationState` and legacy `AppContext`. +//! +//! `InvocationState` is the source of truth. `AppContext` is kept as a legacy +//! mirror for existing `Tool::ctx`, `Tool::ctx_mut`, and `Tool::into_context` +//! callers. Remove this module once those compatibility APIs are retired. + +use std::path::PathBuf; + +use object::Architecture; + +use crate::{ + artifact::{runtime::PreparedRuntimeArtifacts, state::OutputArtifacts}, + ctx::AppContext, + invocation::{ActiveBuildContext, InvocationState}, +}; + +pub(crate) fn runtime_artifacts<'a>( + state: &'a InvocationState, + ctx: &'a AppContext, +) -> &'a OutputArtifacts { + if state.artifacts().is_empty() { + &ctx.artifacts + } else { + state.artifacts() + } +} + +pub(crate) fn runtime_arch(state: &InvocationState, ctx: &AppContext) -> Option { + state.arch().or(ctx.arch) +} + +pub(crate) fn set_build_config_path( + state: &mut InvocationState, + ctx: &mut AppContext, + path: Option, +) { + state.set_build_config_path(path.clone()); + ctx.build_config_path = path; +} + +pub(crate) fn set_active_build( + state: &mut InvocationState, + ctx: &mut AppContext, + active_build: &ActiveBuildContext, +) { + state.set_active_build(active_build.clone()); + sync_build_context_from_state(ctx, state); +} + +pub(crate) fn apply_prepared_runtime_artifacts( + state: &mut InvocationState, + ctx: &mut AppContext, + prepared: &PreparedRuntimeArtifacts, +) { + state.apply_prepared_runtime_artifacts(prepared); + sync_runtime_context_from_state(ctx, state); +} + +pub(crate) fn sync_context_from_state(ctx: &mut AppContext, state: &InvocationState) { + sync_build_context_from_state(ctx, state); + sync_runtime_context_from_state(ctx, state); +} + +fn sync_build_context_from_state(ctx: &mut AppContext, state: &InvocationState) { + ctx.build_config_path = state.build_config_path().map(PathBuf::from); + ctx.build_config = state.active_build().map(ActiveBuildContext::build_config); +} + +fn sync_runtime_context_from_state(ctx: &mut AppContext, state: &InvocationState) { + ctx.arch = state.arch(); + ctx.artifacts = state.artifacts().clone(); +} diff --git a/ostool/src/lib.rs b/ostool/src/lib.rs index 65418eb..de6c019 100644 --- a/ostool/src/lib.rs +++ b/ostool/src/lib.rs @@ -46,9 +46,11 @@ pub mod board; /// Application context and state management. pub mod ctx; -/// Invocation inputs and mutable runtime state. +/// Invocation inputs and resolved project layout. pub mod invocation; +mod legacy_context; + /// Custom file logger for ostool. /// /// Provides a file-based logger that writes all log output to diff --git a/ostool/src/main.rs b/ostool/src/main.rs index f20fa8a..472e477 100644 --- a/ostool/src/main.rs +++ b/ostool/src/main.rs @@ -388,6 +388,7 @@ fn apply_cargo_selector( if let Some(bin) = &selector.bin { cargo_config.bin = Some(bin.clone()); } + tool.ctx_mut().build_config = Some(build_config.clone()); Ok(()) } @@ -451,11 +452,11 @@ mod tests { use std::fs; use clap::Parser; - use ostool::{Tool, ToolConfig}; + use ostool::{Tool, ToolConfig, resolve_manifest_context}; use super::{ BoardArgs, BoardSubCommands, CargoSelectorArgs, Cli, RunSubCommands, SubCommands, - apply_cargo_selector, build, + apply_cargo_selector, build, load_board_config, }; /// Verifies build parsing accepts manifest, config, package, and bin overrides. @@ -784,6 +785,72 @@ mod tests { ); } + #[tokio::test] + async fn cargo_selector_updates_scope_before_board_config_load() { + let temp = tempfile::tempdir().unwrap(); + fs::write( + temp.path().join("Cargo.toml"), + "[workspace]\nmembers = [\"app\", \"kernel\"]\nresolver = \"3\"\n", + ) + .unwrap(); + + let app_dir = temp.path().join("app"); + fs::create_dir_all(app_dir.join("src")).unwrap(); + fs::write( + app_dir.join("Cargo.toml"), + "[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + ) + .unwrap(); + fs::write(app_dir.join("src/main.rs"), "fn main() {}\n").unwrap(); + + let kernel_dir = temp.path().join("kernel"); + fs::create_dir_all(kernel_dir.join("src/bin")).unwrap(); + fs::write( + kernel_dir.join("Cargo.toml"), + "[package]\nname = \"kernel\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + ) + .unwrap(); + fs::write(kernel_dir.join("src/main.rs"), "fn main() {}\n").unwrap(); + fs::write(kernel_dir.join("src/bin/kernel-board.rs"), "fn main() {}\n").unwrap(); + + fs::write( + temp.path().join(".board.toml"), + r#" +board_type = "kernel-board" +dtb_file = "${package}/board.dtb" +"#, + ) + .unwrap(); + + let mut tool = Tool::new(ToolConfig { + manifest: Some(app_dir.clone()), + ..Default::default() + }) + .unwrap(); + let manifest = resolve_manifest_context(Some(app_dir)).unwrap(); + let mut build_config = build::config::BuildConfig { + system: build::config::BuildSystem::Cargo(build::config::Cargo { + package: "app".into(), + target: "aarch64-unknown-none".into(), + ..Default::default() + }), + }; + + apply_cargo_selector( + &mut tool, + &mut build_config, + &CargoSelectorArgs { + package: Some("kernel".into()), + bin: Some("kernel-board".into()), + }, + ) + .unwrap(); + let board_config = load_board_config(&mut tool, &manifest, None).await.unwrap(); + + let expected = kernel_dir.join("board.dtb").display().to_string(); + assert_eq!(board_config.dtb_file.as_deref(), Some(expected.as_str())); + } + fn test_tool() -> (tempfile::TempDir, Tool) { let temp = tempfile::tempdir().unwrap(); fs::write( diff --git a/ostool/src/menuconfig.rs b/ostool/src/menuconfig.rs index 50393a1..6a47662 100644 --- a/ostool/src/menuconfig.rs +++ b/ostool/src/menuconfig.rs @@ -61,7 +61,7 @@ impl MenuConfigHandler { async fn handle_default_config(tool: &mut Tool) -> Result<()> { let config_path = config_loader::resolve_build_config_path(tool.workspace_dir(), None); - tool.ctx_mut().build_config_path = Some(config_path.clone()); + tool.set_build_config_path(Some(config_path.clone())); let hooks = config_hooks::build_config_hooks(tool.workspace_dir()); let config = jkconfig::run::(config_path.clone(), true, &hooks) @@ -69,7 +69,7 @@ impl MenuConfigHandler { .with_context(|| format!("failed to load build config: {}", config_path.display()))?; if let Some(config) = config { - tool.ctx_mut().build_config = Some(config); + Self::record_default_build_config_edit(tool, config); } else { println!("\n未更改构建配置"); } @@ -77,6 +77,10 @@ impl MenuConfigHandler { Ok(()) } + fn record_default_build_config_edit(tool: &mut Tool, config: BuildConfig) { + tool.ctx_mut().build_config = Some(config); + } + async fn handle_qemu_config(tool: &mut Tool) -> Result<()> { info!("配置 QEMU 运行参数"); @@ -140,7 +144,13 @@ impl MenuConfigHandler { #[cfg(test)] mod tests { - use crate::build::config::BuildConfig; + use std::fs; + + use crate::{ + Tool, ToolConfig, + build::config::{BuildConfig, BuildSystem, Cargo}, + menuconfig::MenuConfigHandler, + }; use jkconfig::data::menu::MenuRoot; use jkconfig::data::types::ElementType; use schemars::schema_for; @@ -171,4 +181,33 @@ mod tests { other => panic!("log should be Item(Enum), got: {:?}", other), } } + + #[test] + fn default_config_edit_records_config_without_validating_cargo_package() { + let temp = tempfile::tempdir().unwrap(); + fs::write( + temp.path().join("Cargo.toml"), + "[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + ) + .unwrap(); + fs::create_dir_all(temp.path().join("src")).unwrap(); + fs::write(temp.path().join("src/main.rs"), "fn main() {}\n").unwrap(); + + let mut tool = Tool::new(ToolConfig { + manifest: Some(temp.path().to_path_buf()), + ..Default::default() + }) + .unwrap(); + let config = BuildConfig { + system: BuildSystem::Cargo(Cargo { + package: "missing".into(), + target: "x86_64-unknown-none".into(), + ..Default::default() + }), + }; + + MenuConfigHandler::record_default_build_config_edit(&mut tool, config.clone()); + + assert_eq!(tool.ctx().build_config.as_ref(), Some(&config)); + } } diff --git a/ostool/src/run/qemu.rs b/ostool/src/run/qemu.rs index 23080df..ccf9603 100644 --- a/ostool/src/run/qemu.rs +++ b/ostool/src/run/qemu.rs @@ -168,7 +168,7 @@ impl Tool { cargo: &Cargo, path: &Path, ) -> anyhow::Result { - self.sync_cargo_context(cargo); + self.sync_cargo_context(cargo)?; let scope = self.variable_scope()?; let config_path = variables::expand_path_variables(path, &scope)?; read_qemu_config_at_path(&scope, config_path).await @@ -178,7 +178,7 @@ impl Tool { &mut self, cargo: &Cargo, ) -> anyhow::Result { - self.sync_cargo_context(cargo); + self.sync_cargo_context(cargo)?; let package_dir = self.resolve_package_manifest_dir(&cargo.package)?; let arch = infer_target_arch(&cargo.target).or(self.runtime_arch()); let config_path = resolve_qemu_config_path_in_dir(&package_dir, arch, None)?; @@ -192,7 +192,7 @@ impl Tool { cargo: &Cargo, dir: &Path, ) -> anyhow::Result { - self.sync_cargo_context(cargo); + self.sync_cargo_context(cargo)?; let scope = self.variable_scope()?; let dir = variables::expand_path_variables(dir, &scope)?; let arch = infer_target_arch(&cargo.target).or(self.runtime_arch()); diff --git a/ostool/src/run/uboot.rs b/ostool/src/run/uboot.rs index 5400c45..97729e5 100644 --- a/ostool/src/run/uboot.rs +++ b/ostool/src/run/uboot.rs @@ -321,7 +321,7 @@ impl Tool { cargo: &crate::build::config::Cargo, path: &Path, ) -> anyhow::Result { - self.sync_cargo_context(cargo); + self.sync_cargo_context(cargo)?; let scope = self.variable_scope()?; let config_path = variables::expand_path_variables(path, &scope)?; read_uboot_config_at_path(&scope, config_path).await @@ -331,7 +331,7 @@ impl Tool { &mut self, cargo: &crate::build::config::Cargo, ) -> anyhow::Result { - self.sync_cargo_context(cargo); + self.sync_cargo_context(cargo)?; let workspace_dir = self.workspace_dir().clone(); self.ensure_uboot_config_in_dir_for_cargo(cargo, &workspace_dir) .await @@ -342,7 +342,7 @@ impl Tool { cargo: &crate::build::config::Cargo, dir: &Path, ) -> anyhow::Result { - self.sync_cargo_context(cargo); + self.sync_cargo_context(cargo)?; let scope = self.variable_scope()?; let dir = variables::expand_path_variables(dir, &scope)?; ensure_uboot_config_at_path(&scope, dir.join(".uboot.toml"), self.default_uboot_config()) diff --git a/ostool/src/tool.rs b/ostool/src/tool.rs index 75c5bac..a8f04ab 100644 --- a/ostool/src/tool.rs +++ b/ostool/src/tool.rs @@ -13,11 +13,13 @@ use crate::{ state::OutputArtifacts, }, build::{ + CargoSelector, activate_build_context, config::{BuildConfig, BuildSystem, Cargo}, config_hooks, config_loader, }, ctx::AppContext, - invocation::Invocation, + invocation::{ActiveBuildContext, Invocation, InvocationState}, + legacy_context, process::ProcessContext, project::{ProjectLayout, metadata, resolve_project_layout, variables::VariableScope}, }; @@ -44,6 +46,7 @@ pub struct Tool { pub(crate) manifest_path: PathBuf, pub(crate) manifest_dir: PathBuf, pub(crate) workspace_dir: PathBuf, + pub(crate) state: InvocationState, pub(crate) ctx: AppContext, } @@ -87,7 +90,7 @@ impl Tool { /// Someboot build-config injection uses the same default as `Tool::new` and can /// be changed afterward with `set_someboot_build_config_enabled`. pub fn from_invocation(invocation: Invocation) -> Self { - let (options, layout) = invocation.into_parts(); + let (options, layout, state) = invocation.into_parts(); let config = ToolConfig { manifest: Some(layout.manifest_path().to_path_buf()), build_dir: options.build_dir().map(PathBuf::from), @@ -95,7 +98,10 @@ impl Tool { debug: options.debug(), ..Default::default() }; - Self::from_project_layout(config, layout) + let mut tool = Self::from_project_layout(config, layout); + tool.state = state; + legacy_context::sync_context_from_state(&mut tool.ctx, &tool.state); + tool } pub(crate) fn from_project_layout(config: ToolConfig, layout: ProjectLayout) -> Self { @@ -104,6 +110,7 @@ impl Tool { manifest_path: layout.manifest_path().to_path_buf(), manifest_dir: layout.manifest_dir().to_path_buf(), workspace_dir: layout.workspace_dir().to_path_buf(), + state: InvocationState::default(), ctx: AppContext::default(), } } @@ -112,22 +119,26 @@ impl Tool { &self.ctx } + /// Returns mutable legacy context for compatibility callers. + /// + /// Prefer state-aware `Tool` methods for new code. Direct mutations here do + /// not update `InvocationState`, which is the current source of truth. pub fn ctx_mut(&mut self) -> &mut AppContext { &mut self.ctx } /// Returns the currently prepared runtime artifacts. pub(crate) fn runtime_artifacts(&self) -> &OutputArtifacts { - &self.ctx.artifacts + legacy_context::runtime_artifacts(&self.state, &self.ctx) } /// Returns the architecture detected from the current runtime artifact. pub(crate) fn runtime_arch(&self) -> Option { - self.ctx.arch + legacy_context::runtime_arch(&self.state, &self.ctx) } pub fn set_build_config_path(&mut self, path: Option) { - self.ctx.build_config_path = path; + legacy_context::set_build_config_path(&mut self.state, &mut self.ctx, path); } /// Enables or disables automatic Cargo argument injection from someboot build metadata. @@ -147,10 +158,28 @@ impl Tool { self.config.debug } - pub(crate) fn sync_cargo_context(&mut self, cargo: &Cargo) { - self.ctx.build_config = Some(BuildConfig { + pub(crate) fn sync_build_context(&mut self, build_config: &BuildConfig) -> anyhow::Result<()> { + let active_build = activate_build_context( + &self.project_layout(), + build_config.clone(), + self.state + .build_config_path() + .map(PathBuf::from) + .or_else(|| self.ctx.build_config_path.clone()), + &CargoSelector::default(), + )?; + self.apply_active_build_context(&active_build); + Ok(()) + } + + pub(crate) fn sync_cargo_context(&mut self, cargo: &Cargo) -> anyhow::Result<()> { + self.sync_build_context(&BuildConfig { system: BuildSystem::Cargo(cargo.clone()), - }); + }) + } + + pub(crate) fn apply_active_build_context(&mut self, active_build: &ActiveBuildContext) { + legacy_context::set_active_build(&mut self.state, &mut self.ctx, active_build); } pub(crate) fn manifest_dir(&self) -> &PathBuf { @@ -236,14 +265,13 @@ impl Tool { /// Converts the ELF file to raw binary format. fn objcopy_output_bin(&mut self) -> anyhow::Result { - if let Some(bin) = self.ctx.artifacts.bin() { + if let Some(bin) = self.runtime_artifacts().bin() { debug!("BIN file already exists: {:?}", bin); return Ok(bin.to_path_buf()); } let elf_path = self - .ctx - .artifacts + .runtime_artifacts() .elf() .ok_or_else(|| anyhow!("elf not exist"))?; let process_context = self.process_context()?; @@ -254,7 +282,10 @@ impl Tool { to_bin: true, bin_dir: self.bin_dir(), debug: self.debug_enabled(), - cargo_artifact_dir: self.ctx.artifacts.cargo_artifact_dir().map(PathBuf::from), + cargo_artifact_dir: self + .runtime_artifacts() + .cargo_artifact_dir() + .map(PathBuf::from), strip_elf: false, objcopy_program: PathBuf::from("rust-objcopy"), }, @@ -269,10 +300,7 @@ impl Tool { /// Applies prepared runtime artifacts to legacy context state. pub(crate) fn apply_prepared_runtime_artifacts(&mut self, prepared: PreparedRuntimeArtifacts) { - self.ctx - .artifacts - .apply_prepared_runtime_artifacts(&prepared); - self.ctx.arch = prepared.arch(); + legacy_context::apply_prepared_runtime_artifacts(&mut self.state, &mut self.ctx, &prepared); } /// Loads and prepares the build configuration. @@ -291,13 +319,17 @@ impl Tool { ) .await?; - self.ctx.build_config_path = Some(loaded.path().to_path_buf()); + self.set_build_config_path(Some(loaded.path().to_path_buf())); let config = loaded.into_config(); self.ctx.build_config = Some(config.clone()); Ok(config) } fn package_root_for_variables(&self) -> anyhow::Result { + if let Some(active_build) = self.state.active_build() { + return Ok(active_build.variable_scope().package_dir().to_path_buf()); + } + if let Some(BuildConfig { system: BuildSystem::Cargo(cargo), }) = &self.ctx.build_config @@ -308,7 +340,7 @@ impl Tool { Ok(self.manifest_dir.clone()) } - fn project_layout(&self) -> ProjectLayout { + pub(crate) fn project_layout(&self) -> ProjectLayout { ProjectLayout::from_manifest_parts( self.manifest_path.clone(), self.manifest_dir.clone(), @@ -317,6 +349,10 @@ impl Tool { } pub(crate) fn variable_scope(&self) -> anyhow::Result { + if let Some(active_build) = self.state.active_build() { + return Ok(active_build.variable_scope().clone()); + } + let package_dir = self.package_root_for_variables()?; Ok(VariableScope::for_package( &self.project_layout(), @@ -329,7 +365,7 @@ impl Tool { self.manifest_dir.clone(), self.workspace_dir.clone(), self.variable_scope()?, - self.ctx.artifacts.elf().map(PathBuf::from), + self.runtime_artifacts().elf().map(PathBuf::from), )) } @@ -395,6 +431,8 @@ mod tests { let expected_elf = copied.canonicalize().unwrap(); let expected_dir = expected_elf.parent().unwrap().to_path_buf(); + assert_eq!(tool.state.artifacts().elf(), Some(expected_elf.as_path())); + assert_eq!(tool.state.arch(), tool.ctx.arch); assert_eq!(tool.ctx.artifacts.elf(), Some(expected_elf.as_path())); assert_eq!( tool.ctx.artifacts.cargo_artifact_dir(), @@ -470,6 +508,67 @@ mod tests { assert_eq!(resolved, kernel_dir); } + #[test] + fn sync_build_context_records_invocation_state_and_legacy_context() { + let temp = tempfile::tempdir().unwrap(); + std::fs::write( + temp.path().join("Cargo.toml"), + "[workspace]\nmembers = [\"app\", \"kernel\"]\nresolver = \"3\"\n", + ) + .unwrap(); + + let app_dir = temp.path().join("app"); + std::fs::create_dir_all(app_dir.join("src")).unwrap(); + std::fs::write( + app_dir.join("Cargo.toml"), + "[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + ) + .unwrap(); + std::fs::write(app_dir.join("src/main.rs"), "fn main() {}\n").unwrap(); + + let kernel_dir = temp.path().join("kernel"); + std::fs::create_dir_all(kernel_dir.join("src/bin")).unwrap(); + std::fs::write( + kernel_dir.join("Cargo.toml"), + "[package]\nname = \"kernel\"\nversion = \"0.1.0\"\nedition = \"2024\"\n", + ) + .unwrap(); + std::fs::write(kernel_dir.join("src/main.rs"), "fn main() {}\n").unwrap(); + std::fs::write(kernel_dir.join("src/bin/kernel-qemu.rs"), "fn main() {}\n").unwrap(); + + let config_path = temp.path().join(".build.toml"); + let mut tool = Tool::new(ToolConfig { + manifest: Some(app_dir), + ..Default::default() + }) + .unwrap(); + tool.set_build_config_path(Some(config_path.clone())); + let build_config = BuildConfig { + system: BuildSystem::Cargo(Cargo { + package: "kernel".into(), + bin: Some("kernel-qemu".into()), + target: "aarch64-unknown-none".into(), + ..Default::default() + }), + }; + + tool.sync_build_context(&build_config).unwrap(); + + let Some(crate::invocation::ActiveBuildContext::Cargo(active)) = tool.state.active_build() + else { + panic!("active Cargo build missing"); + }; + assert_eq!(active.config().package, "kernel"); + assert_eq!(active.config().bin.as_deref(), Some("kernel-qemu")); + assert_eq!(active.config_path(), Some(config_path.as_path())); + assert_eq!(active.variable_scope().package_dir(), kernel_dir.as_path()); + assert_eq!(tool.ctx.build_config.as_ref(), Some(&build_config)); + assert_eq!( + tool.variable_scope().unwrap().package_dir(), + kernel_dir.as_path() + ); + } + #[test] fn cargo_qemu_config_resolution_prefers_package_dir_over_workspace_root() { let temp = tempfile::tempdir().unwrap(); diff --git a/ostool/tests/public_api.rs b/ostool/tests/public_api.rs index 16005d7..85d4209 100644 --- a/ostool/tests/public_api.rs +++ b/ostool/tests/public_api.rs @@ -3,6 +3,8 @@ fn public_api_matches_tool_centered_runtime_construction() { let t = trybuild::TestCases::new(); t.pass("tests/ui/pass_tool_configs.rs"); t.compile_fail("tests/ui/fail_cargo_pipeline.rs"); + t.compile_fail("tests/ui/fail_build_activation_api.rs"); + t.compile_fail("tests/ui/fail_invocation_state.rs"); t.compile_fail("tests/ui/fail_qemu_override.rs"); t.compile_fail("tests/ui/fail_runner_path_field.rs"); } diff --git a/ostool/tests/ui/fail_build_activation_api.rs b/ostool/tests/ui/fail_build_activation_api.rs new file mode 100644 index 0000000..da690d8 --- /dev/null +++ b/ostool/tests/ui/fail_build_activation_api.rs @@ -0,0 +1,18 @@ +use ostool::{ + Tool, ToolConfig, + build::{ + CargoSelector, apply_cargo_selector, + config::{BuildConfig, BuildSystem, Cargo}, + }, +}; + +fn main() { + let mut config = BuildConfig { + system: BuildSystem::Cargo(Cargo::default()), + }; + let selector = CargoSelector::new(Some("kernel".to_owned()), None); + apply_cargo_selector(&mut config, &selector).unwrap(); + + let mut tool = Tool::new(ToolConfig::default()).unwrap(); + tool.activate_build_config(&mut config, &selector).unwrap(); +} diff --git a/ostool/tests/ui/fail_build_activation_api.stderr b/ostool/tests/ui/fail_build_activation_api.stderr new file mode 100644 index 0000000..b6b679c --- /dev/null +++ b/ostool/tests/ui/fail_build_activation_api.stderr @@ -0,0 +1,38 @@ +error[E0603]: struct `CargoSelector` is private + --> tests/ui/fail_build_activation_api.rs:4:9 + | +4 | CargoSelector, apply_cargo_selector, + | ^^^^^^^^^^^^^ private struct + | +note: the struct `CargoSelector` is defined here + --> src/build/mod.rs + | + | pub(crate) struct CargoSelector { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0603]: function `apply_cargo_selector` is private + --> tests/ui/fail_build_activation_api.rs:4:24 + | +4 | CargoSelector, apply_cargo_selector, + | ^^^^^^^^^^^^^^^^^^^^ private function + | +note: the function `apply_cargo_selector` is defined here + --> src/build/mod.rs + | + | / fn apply_cargo_selector( + | | config: &mut config::BuildConfig, + | | selector: &CargoSelector, + | | ) -> anyhow::Result<()> { + | |_______________________^ + +error[E0599]: no method named `activate_build_config` found for struct `Tool` in the current scope + --> tests/ui/fail_build_activation_api.rs:17:10 + | +17 | tool.activate_build_config(&mut config, &selector).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: there is a method `default_build_config` with a similar name, but with different arguments + --> src/build/mod.rs + | + | pub fn default_build_config(&self) -> config::BuildConfig { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/ostool/tests/ui/fail_invocation_state.rs b/ostool/tests/ui/fail_invocation_state.rs new file mode 100644 index 0000000..f01a0a3 --- /dev/null +++ b/ostool/tests/ui/fail_invocation_state.rs @@ -0,0 +1,6 @@ +use ostool::invocation::{ActiveBuildContext, InvocationState}; + +fn main() { + let _ = core::mem::size_of::(); + let _ = core::mem::size_of::(); +} diff --git a/ostool/tests/ui/fail_invocation_state.stderr b/ostool/tests/ui/fail_invocation_state.stderr new file mode 100644 index 0000000..cb4c8c5 --- /dev/null +++ b/ostool/tests/ui/fail_invocation_state.stderr @@ -0,0 +1,23 @@ +error[E0603]: enum `ActiveBuildContext` is private + --> tests/ui/fail_invocation_state.rs:1:26 + | +1 | use ostool::invocation::{ActiveBuildContext, InvocationState}; + | ^^^^^^^^^^^^^^^^^^ private enum + | +note: the enum `ActiveBuildContext` is defined here + --> src/invocation.rs + | + | pub(crate) enum ActiveBuildContext { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0603]: struct `InvocationState` is private + --> tests/ui/fail_invocation_state.rs:1:46 + | +1 | use ostool::invocation::{ActiveBuildContext, InvocationState}; + | ^^^^^^^^^^^^^^^ private struct + | +note: the struct `InvocationState` is defined here + --> src/invocation.rs + | + | pub(crate) struct InvocationState { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^