Browse Source

refactor(xtask): Restructure code and enhance error message clarity

- Simplified the `print_pmu_info` function to use separate logging for available and not available PMU extensions.
- Refactored the `run` function in `xtask/src/prototyper.rs` to enhance directory preparation and configuration file setup.
- Improved error handling and logging for build failures and file operations in both prototyper and test kernel tasks.

Signed-off-by: Zongyao Chen <solar1s@163.com>
chenzongyao200127 1 week ago
parent
commit
40b152fd11
5 changed files with 414 additions and 213 deletions
  1. 5 9
      prototyper/prototyper/src/platform/mod.rs
  2. 126 53
      xtask/src/bench.rs
  3. 34 13
      xtask/src/main.rs
  4. 126 85
      xtask/src/prototyper.rs
  5. 123 53
      xtask/src/test.rs

+ 5 - 9
prototyper/prototyper/src/platform/mod.rs

@@ -441,15 +441,11 @@ impl Platform {
 
     #[inline]
     fn print_pmu_info(&self) {
-        info!(
-            "{:<30}: {}",
-            "Platform PMU Extension",
-            if self.have_pmu() {
-                "Available"
-            } else {
-                "Not Available"
-            }
-        );
+        if self.have_pmu() {
+            info!("{:<30}: {}", "Platform PMU Extension", "Available");
+        } else {
+            warn!("{:<30}: {}", "Platform PMU Extension", "Not Available");
+        }
     }
 
     #[inline]

+ 126 - 53
xtask/src/bench.rs

@@ -1,5 +1,6 @@
 use std::{
     env, fs,
+    path::{Path, PathBuf},
     process::{Command, ExitStatus},
 };
 
@@ -9,72 +10,144 @@ use crate::utils::cargo;
 
 #[derive(Debug, Args, Clone)]
 pub struct BenchArg {
-    /// Package Prototyper and Test-Kernel
-    #[clap(long)]
+    /// Package Prototyper and bench kernel into a single image
+    #[clap(
+        long,
+        help = "Create a combined image with Prototyper and bench kernel"
+    )]
     pub pack: bool,
 }
 
+const ARCH: &str = "riscv64imac-unknown-none-elf";
+const BENCH_KERNEL_NAME: &str = "rustsbi-bench-kernel";
+const PROTOTYPER_BIN: &str = "rustsbi-prototyper.bin";
+
 #[must_use]
 pub fn run(arg: &BenchArg) -> Option<ExitStatus> {
-    let arch = "riscv64imac-unknown-none-elf";
-    let current_dir = env::current_dir();
-    let target_dir = current_dir
-        .as_ref()
-        .unwrap()
-        .join("target")
-        .join(arch)
-        .join("release");
+    let current_dir = env::current_dir().ok()?;
+    let target_dir = get_target_dir(&current_dir);
 
+    // Build the bench kernel
     info!("Building bench kernel");
-    cargo::Cargo::new("build")
-        .package("rustsbi-bench-kernel")
-        .target(arch)
-        .release()
-        .status()
-        .ok()?;
-
-    info!("Copy to binary");
-    let exit_status = Command::new("rust-objcopy")
-        .args(["-O", "binary"])
-        .arg("--binary-architecture=riscv64")
-        .arg(target_dir.join("rustsbi-bench-kernel"))
-        .arg(target_dir.join("rustsbi-bench-kernel.bin"))
-        .status()
-        .ok()?;
+    let build_status = build_bench_kernel()?;
+    if !build_status.success() {
+        error!("Failed to build bench kernel");
+        return Some(build_status);
+    }
+
+    // Convert to binary format
+    info!("Converting to binary format");
+    let exit_status = convert_to_binary(&target_dir)?;
+    if !exit_status.success() {
+        error!("Failed to convert bench kernel to binary format");
+        return Some(exit_status);
+    }
 
+    // Pack into image if requested
     if arg.pack {
-        info!("Pack to image");
-        match fs::exists(target_dir.join("rustsbi-prototyper.bin")) {
-            Ok(true) => {}
-            Ok(false) => {
-                panic!(
-                    " Couldn't open \"rustsbi-prototyper.bin\": No such file or directory. Please compile Prototyper first"
+        info!("Packing into image");
+        match pack_image(&current_dir, &target_dir) {
+            Ok(status) => {
+                info!(
+                    "Output image created at: {}",
+                    target_dir
+                        .join(format!("{}.itb", BENCH_KERNEL_NAME))
+                        .display()
                 );
+                return Some(status);
             }
-            Err(_) => {
-                panic!(
-                    "Can't check existence of file rustsbi-prototyper.bin, please compile Prototyper first"
-                );
+            Err(err_msg) => {
+                error!("{}", err_msg);
+                return Some(<ExitStatus as std::os::unix::process::ExitStatusExt>::from_raw(1));
             }
         }
-        fs::copy(
-            current_dir
-                .as_ref()
-                .unwrap()
-                .join("prototyper")
-                .join("bench-kernel")
-                .join("scripts")
-                .join("rustsbi-bench-kernel.its"),
-            target_dir.join("rustsbi-bench-kernel.its"),
-        )
-        .ok()?;
-        env::set_current_dir(&target_dir).ok()?;
-        Command::new("mkimage")
-            .args(["-f", "rustsbi-bench-kernel.its"])
-            .arg("rustsbi-bench-kernel.itb")
-            .status()
-            .ok()?;
-        fs::remove_file(env::current_dir().unwrap().join("rustsbi-bench-kernel.its")).ok()?;
+    } else {
+        info!(
+            "Output binary created at: {}",
+            target_dir
+                .join(format!("{}.bin", BENCH_KERNEL_NAME))
+                .display()
+        );
     }
+
     Some(exit_status)
 }
+
+fn get_target_dir(current_dir: &Path) -> PathBuf {
+    current_dir.join("target").join(ARCH).join("release")
+}
+
+fn build_bench_kernel() -> Option<ExitStatus> {
+    cargo::Cargo::new("build")
+        .package(BENCH_KERNEL_NAME)
+        .target(ARCH)
+        .release()
+        .status()
+        .ok()
+}
+
+fn convert_to_binary(target_dir: &Path) -> Option<ExitStatus> {
+    let kernel_path = target_dir.join(BENCH_KERNEL_NAME);
+    let bin_path = target_dir.join(format!("{}.bin", BENCH_KERNEL_NAME));
+
+    Command::new("rust-objcopy")
+        .args([
+            "-O",
+            "binary",
+            "--binary-architecture=riscv64",
+            &kernel_path.to_string_lossy(),
+            &bin_path.to_string_lossy(),
+        ])
+        .status()
+        .ok()
+}
+
+fn pack_image(current_dir: &Path, target_dir: &Path) -> Result<ExitStatus, String> {
+    // Check if prototyper binary exists
+    let prototyper_bin_path = target_dir.join(PROTOTYPER_BIN);
+    if !prototyper_bin_path.exists() {
+        return Err(format!(
+            "Error: Prototyper binary not found at '{}'\n\
+             Please run 'cargo prototyper' first to build the Prototyper binary.",
+            prototyper_bin_path.display()
+        ));
+    }
+
+    // Copy ITS file
+    let its_source = current_dir
+        .join("prototyper")
+        .join("bench-kernel")
+        .join("scripts")
+        .join(format!("{}.its", BENCH_KERNEL_NAME));
+
+    let its_dest = target_dir.join(format!("{}.its", BENCH_KERNEL_NAME));
+
+    fs::copy(&its_source, &its_dest).map_err(|e| format!("Failed to copy ITS file: {}", e))?;
+
+    // Change to target directory
+    let original_dir =
+        env::current_dir().map_err(|e| format!("Failed to get current directory: {}", e))?;
+
+    env::set_current_dir(target_dir)
+        .map_err(|e| format!("Failed to change directory to target: {}", e))?;
+
+    // Create image
+    let status = Command::new("mkimage")
+        .args([
+            "-f",
+            &format!("{}.its", BENCH_KERNEL_NAME),
+            &format!("{}.itb", BENCH_KERNEL_NAME),
+        ])
+        .status()
+        .map_err(|e| format!("Failed to execute mkimage command: {}", e))?;
+
+    // Clean up
+    fs::remove_file(format!("{}.its", BENCH_KERNEL_NAME))
+        .map_err(|e| format!("Failed to clean up ITS file: {}", e))?;
+
+    // Restore original directory
+    env::set_current_dir(original_dir)
+        .map_err(|e| format!("Failed to restore original directory: {}", e))?;
+
+    Ok(status)
+}

+ 34 - 13
xtask/src/main.rs

@@ -31,26 +31,47 @@ struct Cli {
 
 #[derive(Subcommand)]
 enum Cmd {
+    /// Build and configure the RustSBI Prototyper bootloader.
     Prototyper(PrototyperArg),
+    /// Build test-kernel for the RustSBI Prototyper.
     Test(TestArg),
+    /// Build bench-kernel for the RustSBI Prototyper.
     Bench(BenchArg),
 }
 
 fn main() -> ExitCode {
     let cli_args = Cli::parse();
-    logger::Logger::init(&cli_args).expect("Unable to init logger");
-
-    if let Some(code) = match cli_args.cmd {
-        Cmd::Prototyper(ref arg) => prototyper::run(arg),
-        Cmd::Test(ref arg) => test::run(arg),
-        Cmd::Bench(ref arg) => bench::run(arg),
-    } {
-        if code.success() {
-            info!("Finished");
-            return ExitCode::SUCCESS;
-        }
+    if let Err(e) = logger::Logger::init(&cli_args) {
+        eprintln!("Logger initialization failed: {}", e);
+        return ExitCode::FAILURE;
     }
 
-    error!("Failed to run task!");
-    ExitCode::FAILURE
+    // Execute the selected command
+    let result = match &cli_args.cmd {
+        Cmd::Prototyper(arg) => prototyper::run(arg),
+        Cmd::Test(arg) => test::run(arg),
+        Cmd::Bench(arg) => bench::run(arg),
+    };
+
+    match result {
+        Some(exit_status) if exit_status.success() => {
+            info!("Task completed successfully");
+            ExitCode::SUCCESS
+        }
+        Some(exit_status) => {
+            let cmd_name = match &cli_args.cmd {
+                Cmd::Prototyper(_) => "prototyper",
+                Cmd::Test(_) => "test",
+                Cmd::Bench(_) => "bench",
+            };
+            error!("Task '{}' failed with exit code: {}", cmd_name, exit_status);
+            ExitCode::FAILURE
+        }
+        None => {
+            error!(
+                "Task execution failed: operation was interrupted or encountered an unrecoverable error"
+            );
+            ExitCode::FAILURE
+        }
+    }
 }

+ 126 - 85
xtask/src/prototyper.rs

@@ -4,11 +4,9 @@ use std::{
     process::{Command, ExitStatus},
 };
 
+use crate::utils::{CmdOptional, cargo};
 use clap::Args;
 
-use crate::utils::CmdOptional;
-use crate::utils::cargo;
-
 #[derive(Debug, Args, Clone)]
 pub struct PrototyperArg {
     #[clap(long, short = 'f')]
@@ -27,117 +25,160 @@ pub struct PrototyperArg {
     pub config_file: Option<PathBuf>,
 }
 
+const ARCH: &str = "riscv64imac-unknown-none-elf";
+const PACKAGE_NAME: &str = "rustsbi-prototyper";
+
 #[must_use]
-#[rustfmt::skip] // "export_env!("PROTOTYPER_FDT_PATH" ?= fdt.unwrap());" is a macro, rustfmt will not format it correctly
 pub fn run(arg: &PrototyperArg) -> Option<ExitStatus> {
-    let arch = "riscv64imac-unknown-none-elf";
-    let fdt = arg.fdt.clone();
-    let payload = arg.payload.clone();
-    let jump = arg.jump;
-
-    let current_dir = env::current_dir();
-    let raw_target_dir = current_dir
-        .as_ref()
-        .unwrap()
-        .join("target");
-    let target_dir = raw_target_dir
-        .join(arch)
-        .join("release");
+    let dirs = prepare_directories()?;
+    setup_config_file(&dirs.target_config_toml, arg)?;
+
+    let exit_status = build_prototyper(arg)?;
+    if !exit_status.success() {
+        error!(
+            "Failed to execute rust-objcopy. Please ensure that cargo-binutils is installed and available in your system's PATH."
+        );
+        return Some(exit_status);
+    }
+
+    copy_output_files(&dirs.target_dir, arg)?;
+
+    Some(exit_status)
+}
+
+struct Directories {
+    target_dir: PathBuf,
+    target_config_toml: PathBuf,
+}
+
+fn prepare_directories() -> Option<Directories> {
+    let current_dir = env::current_dir().ok()?;
+    let raw_target_dir = current_dir.join("target");
+    let target_dir = raw_target_dir.join(ARCH).join("release");
     let target_config_toml = raw_target_dir.join("config.toml");
 
+    Some(Directories {
+        target_dir,
+        target_config_toml,
+    })
+}
+
+fn setup_config_file(target_config_toml: &PathBuf, arg: &PrototyperArg) -> Option<()> {
+    // Delete old config if exists
+    if fs::exists(target_config_toml).ok()? {
+        info!("Delete old config");
+        fs::remove_file(target_config_toml).ok()?;
+    }
+
+    // Determine config file path
+    let current_dir = env::current_dir().ok()?;
     let default_config_file = current_dir
-        .as_ref()
-        .unwrap()
         .join("prototyper")
         .join("prototyper")
         .join("config")
         .join("default.toml");
     let config_file = arg.config_file.clone().unwrap_or(default_config_file);
 
-    if fs::exists(&target_config_toml).ok()? {
-        info!("Delete old config");
-        fs::remove_file(&target_config_toml).ok()?;
-    }
+    // Copy config
+    info!("Copy config from: {}", config_file.display());
+    fs::copy(&config_file, target_config_toml).ok()?;
 
-    info!("Copy config");
-    fs::copy(
-        &config_file,
-        target_config_toml
-    ).ok()?;
-
-    info!("Building Protoyper");
-    cargo::Cargo::new("build")
-        .package("rustsbi-prototyper")
-        .target(arch)
-        .unstable("build-std", ["core","alloc"])
+    Some(())
+}
+
+fn build_prototyper(arg: &PrototyperArg) -> Option<ExitStatus> {
+    info!("Building Prototyper");
+
+    // Build the prototyper
+    let status = cargo::Cargo::new("build")
+        .package(PACKAGE_NAME)
+        .target(ARCH)
+        .unstable("build-std", ["core", "alloc"])
         .env("RUSTFLAGS", "-C relocation-model=pie -C link-arg=-pie")
         .features(&arg.features)
         .optional(arg.fdt.is_some(), |cargo| {
-            cargo.env("PROTOTYPER_FDT_PATH", fdt.as_ref().unwrap());
+            cargo.env("PROTOTYPER_FDT_PATH", arg.fdt.as_ref().unwrap());
             cargo.features(["fdt".to_string()])
         })
-        .optional(payload.is_some(), |cargo| {
-            cargo.env("PROTOTYPER_PAYLOAD_PATH", payload.as_ref().unwrap());
+        .optional(arg.payload.is_some(), |cargo| {
+            cargo.env("PROTOTYPER_PAYLOAD_PATH", arg.payload.as_ref().unwrap());
             cargo.features(["payload".to_string()])
         })
-        .optional(jump, |cargo| {
-            cargo.features(["jump".to_string()])
-        })
+        .optional(arg.jump, |cargo| cargo.features(["jump".to_string()]))
         .release()
         .status()
         .ok()?;
 
-    info!("Copy to binary");
-    let exit_status = Command::new("rust-objcopy")
-        .args(["-O", "binary"])
-        .arg("--binary-architecture=riscv64")
-        .arg(target_dir.join("rustsbi-prototyper"))
-        .arg(target_dir.join("rustsbi-prototyper.bin"))
+    if !status.success() {
+        error!(
+            "Failed to build prototyper. Please check the cargo output above for detailed error information."
+        );
+        return Some(status);
+    }
+
+    // Get target directory once instead of recreating it
+    let target_dir = prepare_directories()?.target_dir;
+    let elf_path = target_dir.join(PACKAGE_NAME);
+    let bin_path = target_dir.join(format!("{}.bin", PACKAGE_NAME));
+
+    // Create binary from ELF
+    info!("Converting ELF to binary with rust-objcopy");
+    let result = Command::new("rust-objcopy")
+        .args([
+            "-O",
+            "binary",
+            "--binary-architecture=riscv64",
+            &elf_path.to_string_lossy(),
+            &bin_path.to_string_lossy(),
+        ])
         .status()
-        .ok()?;
-    if !exit_status.success() {
-        error!("Failed to exec rust-objcopy, please check if cargo-binutils has been installed?");
-        return Some(exit_status);
+        .ok();
+
+    if result.is_none() {
+        error!(
+            "Failed to execute rust-objcopy. Command not found or failed to start.\n\
+             Source: {}\n\
+             Destination: {}\n\
+             Please install cargo-binutils with cmd: cargo install cargo-binutils",
+            elf_path.display(),
+            bin_path.display()
+        );
     }
 
-    if arg.payload.is_some() {
+    result
+}
+
+fn copy_output_files(target_dir: &PathBuf, arg: &PrototyperArg) -> Option<()> {
+    let mode_suffix = if arg.payload.is_some() {
         info!("Copy for payload mode");
-        fs::copy(
-            target_dir.join("rustsbi-prototyper"),
-            target_dir.join("rustsbi-prototyper-payload.elf"),
-        )
-        .ok()?;
-        fs::copy(
-            target_dir.join("rustsbi-prototyper.bin"),
-            target_dir.join("rustsbi-prototyper-payload.bin"),
-        )
-        .ok()?;
+        "payload"
     } else if arg.jump {
         info!("Copy for jump mode");
-        fs::copy(
-            target_dir.join("rustsbi-prototyper"),
-            target_dir.join("rustsbi-prototyper-jump.elf"),
-        )
-        .ok()?;
-        fs::copy(
-            target_dir.join("rustsbi-prototyper.bin"),
-            target_dir.join("rustsbi-prototyper-jump.bin"),
-        )
-        .ok()?;
+        "jump"
     } else {
         info!("Copy for dynamic mode");
-        fs::copy(
-            target_dir.join("rustsbi-prototyper"),
-            target_dir.join("rustsbi-prototyper-dynamic.elf"),
-        )
-        .ok()?;
-        fs::copy(
-            target_dir.join("rustsbi-prototyper.bin"),
-            target_dir.join("rustsbi-prototyper-dynamic.bin"),
-        )
-        .ok()?;
-
-    }
-
-    Some(exit_status)
+        "dynamic"
+    };
+
+    // Copy ELF file
+    let elf_source = target_dir.join(PACKAGE_NAME);
+    let elf_dest = target_dir.join(format!("{}-{}.elf", PACKAGE_NAME, mode_suffix));
+    info!(
+        "Copying ELF file: {} -> {}",
+        elf_source.display(),
+        elf_dest.display()
+    );
+    fs::copy(&elf_source, &elf_dest).ok()?;
+
+    // Copy binary file
+    let bin_source = target_dir.join(format!("{}.bin", PACKAGE_NAME));
+    let bin_dest = target_dir.join(format!("{}-{}.bin", PACKAGE_NAME, mode_suffix));
+    info!(
+        "Copying binary file: {} -> {}",
+        bin_source.display(),
+        bin_dest.display()
+    );
+    fs::copy(&bin_source, &bin_dest).ok()?;
+
+    Some(())
 }

+ 123 - 53
xtask/src/test.rs

@@ -1,5 +1,6 @@
 use std::{
     env, fs,
+    path::{Path, PathBuf},
     process::{Command, ExitStatus},
 };
 
@@ -9,72 +10,141 @@ use crate::utils::cargo;
 
 #[derive(Debug, Args, Clone)]
 pub struct TestArg {
-    /// Package Prototyper and Test-Kernel
-    #[clap(long)]
+    /// Package Prototyper and Test-Kernel into a single image
+    #[clap(long, help = "Create a combined image with Prototyper and test kernel")]
     pub pack: bool,
 }
 
+const ARCH: &str = "riscv64imac-unknown-none-elf";
+const TEST_KERNEL_NAME: &str = "rustsbi-test-kernel";
+const PROTOTYPER_BIN: &str = "rustsbi-prototyper.bin";
+
 #[must_use]
 pub fn run(arg: &TestArg) -> Option<ExitStatus> {
-    let arch = "riscv64imac-unknown-none-elf";
-    let current_dir = env::current_dir();
-    let target_dir = current_dir
-        .as_ref()
-        .unwrap()
-        .join("target")
-        .join(arch)
-        .join("release");
+    let current_dir = env::current_dir().ok()?;
+    let target_dir = get_target_dir(&current_dir);
 
+    // Build the test kernel
     info!("Building test kernel");
-    cargo::Cargo::new("build")
-        .package("rustsbi-test-kernel")
-        .target(arch)
-        .release()
-        .status()
-        .ok()?;
-
-    info!("Copy to binary");
-    let exit_status = Command::new("rust-objcopy")
-        .args(["-O", "binary"])
-        .arg("--binary-architecture=riscv64")
-        .arg(target_dir.join("rustsbi-test-kernel"))
-        .arg(target_dir.join("rustsbi-test-kernel.bin"))
-        .status()
-        .ok()?;
+    let build_status = build_test_kernel()?;
+    if !build_status.success() {
+        error!("Failed to build test kernel");
+        return Some(build_status);
+    }
+
+    // Convert to binary format
+    info!("Converting to binary format");
+    let exit_status = convert_to_binary(&target_dir)?;
+    if !exit_status.success() {
+        error!("Failed to convert test kernel to binary format");
+        return Some(exit_status);
+    }
 
+    // Pack into image if requested
     if arg.pack {
-        info!("Pack to image");
-        match fs::exists(target_dir.join("rustsbi-prototyper.bin")) {
-            Ok(true) => {}
-            Ok(false) => {
-                panic!(
-                    " Couldn't open \"rustsbi-prototyper.bin\": No such file or directory. Please compile Prototyper first"
+        info!("Packing into image");
+        match pack_image(&current_dir, &target_dir) {
+            Ok(status) => {
+                info!(
+                    "Output image created at: {}",
+                    target_dir
+                        .join(format!("{}.itb", TEST_KERNEL_NAME))
+                        .display()
                 );
+                return Some(status);
             }
-            Err(_) => {
-                panic!(
-                    "Can't check existence of file rustsbi-prototyper.bin, please compile Prototyper first"
-                );
+            Err(err_msg) => {
+                error!("{}", err_msg);
+                return Some(<ExitStatus as std::os::unix::process::ExitStatusExt>::from_raw(1));
             }
         }
-        fs::copy(
-            current_dir
-                .as_ref()
-                .unwrap()
-                .join("prototyper")
-                .join("test-kernel")
-                .join("scripts")
-                .join("rustsbi-test-kernel.its"),
-            target_dir.join("rustsbi-test-kernel.its"),
-        )
-        .ok()?;
-        env::set_current_dir(&target_dir).ok()?;
-        Command::new("mkimage")
-            .args(["-f", "rustsbi-test-kernel.its"])
-            .arg("rustsbi-test-kernel.itb")
-            .status()
-            .ok()?;
-        fs::remove_file(env::current_dir().unwrap().join("rustsbi-test-kernel.its")).ok()?;
+    } else {
+        info!(
+            "Output binary created at: {}",
+            target_dir
+                .join(format!("{}.bin", TEST_KERNEL_NAME))
+                .display()
+        );
     }
+
     Some(exit_status)
 }
+
+fn get_target_dir(current_dir: &Path) -> PathBuf {
+    current_dir.join("target").join(ARCH).join("release")
+}
+
+fn build_test_kernel() -> Option<ExitStatus> {
+    cargo::Cargo::new("build")
+        .package(TEST_KERNEL_NAME)
+        .target(ARCH)
+        .release()
+        .status()
+        .ok()
+}
+
+fn convert_to_binary(target_dir: &Path) -> Option<ExitStatus> {
+    let kernel_path = target_dir.join(TEST_KERNEL_NAME);
+    let bin_path = target_dir.join(format!("{}.bin", TEST_KERNEL_NAME));
+
+    Command::new("rust-objcopy")
+        .args([
+            "-O",
+            "binary",
+            "--binary-architecture=riscv64",
+            &kernel_path.to_string_lossy(),
+            &bin_path.to_string_lossy(),
+        ])
+        .status()
+        .ok()
+}
+
+fn pack_image(current_dir: &Path, target_dir: &Path) -> Result<ExitStatus, String> {
+    // Check if prototyper binary exists
+    let prototyper_bin_path = target_dir.join(PROTOTYPER_BIN);
+    if !prototyper_bin_path.exists() {
+        return Err(format!(
+            "Error: Prototyper binary not found at '{}'\n\
+             Please run 'cargo prototyper' first to build the Prototyper binary.",
+            prototyper_bin_path.display()
+        ));
+    }
+
+    // Copy ITS file
+    let its_source = current_dir
+        .join("prototyper")
+        .join("test-kernel")
+        .join("scripts")
+        .join(format!("{}.its", TEST_KERNEL_NAME));
+
+    let its_dest = target_dir.join(format!("{}.its", TEST_KERNEL_NAME));
+
+    fs::copy(&its_source, &its_dest).map_err(|e| format!("Failed to copy ITS file: {}", e))?;
+
+    // Change to target directory
+    let original_dir =
+        env::current_dir().map_err(|e| format!("Failed to get current directory: {}", e))?;
+
+    env::set_current_dir(target_dir)
+        .map_err(|e| format!("Failed to change directory to target: {}", e))?;
+
+    // Create image
+    let status = Command::new("mkimage")
+        .args([
+            "-f",
+            &format!("{}.its", TEST_KERNEL_NAME),
+            &format!("{}.itb", TEST_KERNEL_NAME),
+        ])
+        .status()
+        .map_err(|e| format!("Failed to execute mkimage command: {}", e))?;
+
+    // Clean up
+    fs::remove_file(format!("{}.its", TEST_KERNEL_NAME))
+        .map_err(|e| format!("Failed to clean up ITS file: {}", e))?;
+
+    // Restore original directory
+    env::set_current_dir(original_dir)
+        .map_err(|e| format!("Failed to restore original directory: {}", e))?;
+
+    Ok(status)
+}