Skip to content
Merged
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
13 changes: 8 additions & 5 deletions ostool/src/board/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,7 @@ pub async fn run_board(
) -> anyhow::Result<()> {
crate::build::prepare_runtime_artifacts(invocation, build_config, build_config_path, false)
.await?;
let scope = invocation.variable_scope()?;
run_prepared_board(invocation, board_config, options, &scope).await
run_prepared_board(invocation, board_config, options).await
}

/// Builds a Cargo artifact and runs it on a remote board.
Expand All @@ -267,16 +266,20 @@ pub async fn cargo_run_board(
.await
}

pub(crate) async fn run_prepared_board(
/// Runs already prepared runtime artifacts on a remote board.
///
/// The invocation must have runtime artifacts prepared by a previous build or by
/// `ostool::build::prepare_runtime_artifact`.
pub async fn run_prepared_board(
invocation: &mut Invocation,
board_config: &BoardRunConfig,
options: RunBoardOptions,
scope: &VariableScope,
) -> anyhow::Result<()> {
let scope = invocation.variable_scope()?;
let global_config = load_board_global_config_with_notice()?;
let mut board_config = board_config.clone();
board_config.apply_overrides(
scope,
&scope,
options.board_type.as_deref(),
options.server.as_deref(),
options.port,
Expand Down
115 changes: 113 additions & 2 deletions ostool/src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,39 @@ impl From<&CargoBuildOutcome> for CargoBuildOutput {
}
}

/// Input for preparing runtime artifacts from an already built ELF.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RuntimeArtifactInput {
elf_path: PathBuf,
to_bin: bool,
cargo_artifact_dir: Option<PathBuf>,
strip_elf: bool,
}

impl RuntimeArtifactInput {
/// Creates a runtime artifact input from an ELF path.
pub fn new(elf_path: impl Into<PathBuf>, to_bin: bool) -> Self {
Self {
elf_path: elf_path.into(),
to_bin,
cargo_artifact_dir: None,
strip_elf: false,
}
}

/// Associates the input ELF with the Cargo artifact directory that produced it.
pub fn with_cargo_artifact_dir(mut self, cargo_artifact_dir: impl Into<PathBuf>) -> Self {
self.cargo_artifact_dir = Some(cargo_artifact_dir.into());
self
}

/// Copies the input ELF into a stripped runtime `.elf` before preparing outputs.
pub fn strip_elf(mut self, strip_elf: bool) -> Self {
self.strip_elf = strip_elf;
self
}
}

/// Parameters for running a built Cargo artifact in QEMU.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CargoQemuRunnerArgs {
Expand Down Expand Up @@ -283,6 +316,31 @@ pub(crate) fn build_custom(invocation: &mut Invocation, config: &Custom) -> anyh
Ok(())
}

/// Prepares runtime ELF/BIN outputs from an already built artifact.
///
/// This is useful when a caller builds with [`cargo_build`], modifies the returned
/// ELF in place, and then wants QEMU, U-Boot, or board runners to consume the
/// updated artifact without rebuilding it.
pub fn prepare_runtime_artifact(
invocation: &mut Invocation,
input: RuntimeArtifactInput,
) -> anyhow::Result<()> {
let process_context = invocation.process_context()?;
let prepared = prepare_runtime_artifact_outputs(
&process_context,
RuntimeArtifactOptions {
elf_path: input.elf_path,
to_bin: input.to_bin,
bin_dir: invocation.bin_dir(),
debug: invocation.options().debug(),
cargo_artifact_dir: input.cargo_artifact_dir,
strip_elf: input.strip_elf,
},
)?;
invocation.apply_prepared_runtime_artifacts(prepared);
Ok(())
}

/// Builds the project using Cargo and returns the executable artifact selected from Cargo output.
///
/// `config_path` is the optional `.build.toml` source path for `config`.
Expand Down Expand Up @@ -470,8 +528,9 @@ mod tests {
};

use super::{
CargoBuildOutput, CargoSelector, activate_build_config, activate_build_context,
apply_cargo_build_outcome, build_with_config,
CargoBuildOutput, CargoSelector, RuntimeArtifactInput, activate_build_config,
activate_build_context, apply_cargo_build_outcome, build_with_config,
prepare_runtime_artifact,
};

#[test]
Expand Down Expand Up @@ -552,6 +611,58 @@ mod tests {
assert_eq!(output.cargo_artifact_dir(), cargo_artifact_dir.as_path());
}

#[test]
fn prepare_runtime_artifact_records_external_artifact_state() {
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 cargo_artifact_dir = temp.path().join("target/aarch64/debug");
fs::create_dir_all(&cargo_artifact_dir).unwrap();
let elf_path = cargo_artifact_dir.join("kernel");
fs::copy(std::env::current_exe().unwrap(), &elf_path).unwrap();

let mut invocation = Invocation::new(InvocationOptions::new(
Some(temp.path().to_path_buf()),
None,
None,
false,
))
.unwrap();

prepare_runtime_artifact(
&mut invocation,
RuntimeArtifactInput::new(&elf_path, false)
.with_cargo_artifact_dir(cargo_artifact_dir.clone()),
)
.unwrap();

let expected_elf = elf_path.canonicalize().unwrap();
assert_eq!(
invocation.runtime_artifacts().elf(),
Some(expected_elf.as_path())
);
assert!(invocation.runtime_artifacts().bin().is_none());
assert_eq!(
invocation.runtime_artifacts().cargo_artifact_dir(),
Some(cargo_artifact_dir.as_path())
);
assert_eq!(
invocation.runtime_artifacts().cargo_source_artifact_dir(),
Some(cargo_artifact_dir.as_path())
);
assert_eq!(
invocation.runtime_artifacts().cargo_source_elf(),
Some(expected_elf.as_path())
);
assert!(invocation.runtime_arch().is_some());
}

#[tokio::test]
async fn custom_build_only_does_not_prepare_runtime_artifacts() {
let temp = tempfile::tempdir().unwrap();
Expand Down
13 changes: 13 additions & 0 deletions ostool/tests/ui/pass_module_level_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use ostool::{
board::{self, config::BoardRunConfig},
build::{
self, CargoBuildOutput, CargoQemuRunnerArgs, CargoRunnerKind, CargoUbootRunnerArgs,
RuntimeArtifactInput,
config::{BuildConfig, BuildSystem, Cargo, Custom},
},
invocation::{Invocation, InvocationOptions},
Expand Down Expand Up @@ -60,6 +61,12 @@ fn main() {
let _ = build::build_with_config(&mut invocation, &custom_build, None).await;
let _: anyhow::Result<CargoBuildOutput> =
build::cargo_build(&mut invocation, &cargo, None).await;
let _ = build::prepare_runtime_artifact(
&mut invocation,
RuntimeArtifactInput::new("target/kernel", true)
.with_cargo_artifact_dir("target/aarch64/debug")
.strip_elf(false),
);
let _ = build::cargo_run(&mut invocation, &cargo, None, &qemu_runner).await;
let _ = build::cargo_run(&mut invocation, &cargo, None, &uboot_runner).await;

Expand Down Expand Up @@ -112,5 +119,11 @@ fn main() {
board::RunBoardOptions::default(),
)
.await;
let _ = board::run_prepared_board(
&mut invocation,
&board_config,
board::RunBoardOptions::default(),
)
.await;
};
}