From b61aa778d00de4dcefa8c0e05c3b64c1ecb977e7 Mon Sep 17 00:00:00 2001 From: pantonshire Date: Mon, 7 Apr 2025 15:29:54 +0100 Subject: [PATCH] xtask to build stages 1-3 --- xtask/Cargo.toml | 2 + xtask/src/build.rs | 164 +++++++++++++++++++++++++++++++++++++++++++++ xtask/src/main.rs | 87 +++++++----------------- xtask/src/mkimg.rs | 10 +-- 4 files changed, 192 insertions(+), 71 deletions(-) create mode 100644 xtask/src/build.rs diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index e240aec..42c02a3 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +eyre = "0.6.12" +kdl = "6.3.4" diff --git a/xtask/src/build.rs b/xtask/src/build.rs new file mode 100644 index 0000000..295e476 --- /dev/null +++ b/xtask/src/build.rs @@ -0,0 +1,164 @@ +use std::{ + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output, Stdio}, +}; + +use eyre::{WrapErr, eyre}; + +use crate::Context; + +const NASM_COMMON_FLAGS: &[&str] = &["-werror"]; + +pub fn build(ctx: &Context) -> Result<(), eyre::Error> { + let stage_1_src_dir = ctx.workspace.join("stage_1"); + let stage_2_src_dir = ctx.workspace.join("stage_2"); + let stage_3_src_dir = ctx.workspace.join("stage_3"); + + let build_dir = ctx.workspace.join("build"); + let stage_1_build_dir = build_dir.join("stage_1"); + let stage_2_build_dir = build_dir.join("stage_2"); + + mkdir_if_missing(&build_dir)?; + mkdir_if_missing(&stage_1_build_dir)?; + mkdir_if_missing(&stage_2_build_dir)?; + + build_stage_1(ctx, &stage_1_build_dir, &stage_1_src_dir)?; + build_stage_2(ctx, &stage_2_build_dir, &stage_2_src_dir)?; + build_stage_3(ctx, &stage_3_src_dir)?; + + Ok(()) +} + +fn build_stage_1(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result<(), eyre::Error> { + let src_paths = ls_with_extension(src_dir, "s")?; + + let [src_path] = &*src_paths else { + return Err(eyre!( + "expected exactly one stage 1 source file, found {}", + src_paths.len() + )); + }; + + let out_path = build_dir.join("s1.bin"); + + let include_dir = ctx.workspace.join("include"); + + Command::new("nasm") + .args(["-f", "bin"]) + .args(NASM_COMMON_FLAGS) + .arg("-I") + .arg(&include_dir) + .arg("-o") + .arg(&out_path) + .arg(src_path) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .run_ok()?; + + Ok(()) +} + +fn build_stage_2(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result<(), eyre::Error> { + let include_dir = ctx.workspace.join("include"); + let src_paths = ls_with_extension(src_dir, "s")?; + let mut obj_paths = Vec::new(); + + for src_path in src_paths { + let Some(file_stem) = src_path.file_stem() else { + continue; + }; + + let obj_path = build_dir.join(file_stem).with_extension("o"); + + Command::new("nasm") + .args(["-f", "elf"]) + .args(NASM_COMMON_FLAGS) + .arg("-I") + .arg(&include_dir) + .arg("-o") + .arg(&obj_path) + .arg(src_path) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .run_ok()?; + + obj_paths.push(obj_path); + } + + let linker_file_path = src_dir.join("link.ld"); + let bin_path = build_dir.join("s2.bin"); + + Command::new("ld") + .args(["-m", "elf_i386"]) + .arg("-T") + .arg(&linker_file_path) + .arg("-o") + .arg(bin_path) + .args(&obj_paths) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .run_ok()?; + + Ok(()) +} + +fn build_stage_3(ctx: &Context, stage_3_src_dir: &Path) -> Result<(), eyre::Error> { + Command::new(ctx.cargo) + .arg("build") + .arg("--release") + .current_dir(stage_3_src_dir) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .run_ok()?; + + Ok(()) +} + +fn mkdir_if_missing(path: &Path) -> Result<(), eyre::Error> { + let exists = fs::exists(path) + .wrap_err_with(|| format!("failed to check if {} exists", path.to_string_lossy()))?; + + if !exists { + fs::create_dir(path) + .wrap_err_with(|| format!("failed to create {}", path.to_string_lossy()))?; + } + + Ok(()) +} + +fn ls_with_extension(path: &Path, ext: &str) -> Result, eyre::Error> { + let read_dir = fs::read_dir(path) + .wrap_err_with(|| format!("failed to read {}", path.to_string_lossy()))?; + + let mut paths = Vec::new(); + + for dir in read_dir { + let dir = dir.wrap_err_with(|| format!("failed to read {}", path.to_string_lossy()))?; + + if dir.path().extension() == Some(OsStr::new(ext)) { + paths.push(dir.path()); + } + } + + Ok(paths) +} + +trait CommandExt { + fn run_ok(&mut self) -> Result; +} + +impl CommandExt for Command { + fn run_ok(&mut self) -> Result { + let output = self + .output() + .wrap_err_with(|| format!("failed to run {:?}", self))?; + + if !output.status.success() { + return Err(eyre!("command failed: {:?}", self)); + } + + Ok(output) + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 378bdb8..9b51ec2 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,83 +1,44 @@ -use std::{env, error::Error, fmt, path::{Path, PathBuf}, process::ExitCode}; +use std::{ + env, + path::{Path, PathBuf}, +}; -mod mkimg; +use eyre::{ContextCompat, eyre}; -fn main() -> ExitCode { - match xtask() { - Ok(()) => ExitCode::SUCCESS, - Err(err) => { - eprintln!("{}", err); - ExitCode::FAILURE - } - } -} +mod build; +mod mkimg; -fn xtask() -> Result<(), XtaskError> { +fn main() -> Result<(), eyre::Error> { let mut args = env::args_os().skip(1); - let task = args - .next() - .ok_or_else(|| XtaskError::with_message("no task provided".to_owned()))?; + let task = args.next().context("no task provided")?; - let task = task.to_str().ok_or_else(|| { - XtaskError::with_message(format!("invalid utf-8 \"{}\"", task.to_string_lossy())) - })?; + let task = task + .to_str() + .with_context(|| format!("invalid utf-8 \"{}\"", task.to_string_lossy()))?; - let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").ok_or_else(|| { - XtaskError::with_message("CARGO_MANIFEST_DIR not set".to_owned()) - })?); + let manifest_dir = + PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").context("CARGO_MANIFEST_DIR not set")?); - let workspace_dir = manifest_dir.parent().ok_or_else(|| { - XtaskError::with_message("invalid CARGO_MANIFEST_DIR".to_owned()) - })?; + let workspace_dir = manifest_dir + .parent() + .context("invalid CARGO_MANIFEST_DIR")?; + + let cargo_path = PathBuf::from(env::var_os("CARGO").context("CARGO not set")?); let ctx = Context { workspace: workspace_dir, + cargo: &cargo_path, }; match task { - "mkimg" => mkimg::mkimg_bios_gpt(ctx), - _ => Err(XtaskError::with_message(format!("unknown task \"{}\"", task))), + "build" => build::build(&ctx), + "mkimg" => mkimg::mkimg_bios_gpt(&ctx), + _ => Err(eyre!("unknown task \"{}\"", task)), } } struct Context<'a> { workspace: &'a Path, + cargo: &'a Path, } - -#[derive(Debug)] -struct XtaskError { - message: String, - parent: Option>, -} - -impl XtaskError { - fn with_message(message: String) -> Self { - Self { - message, - parent: None, - } - } - - fn wrap_with_message(err: E, message: String) -> Self - where - E: Error + 'static, - { - Self { - message, - parent: Some(Box::new(err)), - } - } -} - -impl fmt::Display for XtaskError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.message)?; - if let Some(parent) = &self.parent { - write!(f, ": {}", parent)?; - } - Ok(()) - } -} - -impl Error for XtaskError {} diff --git a/xtask/src/mkimg.rs b/xtask/src/mkimg.rs index b0f65d8..8596002 100644 --- a/xtask/src/mkimg.rs +++ b/xtask/src/mkimg.rs @@ -1,12 +1,8 @@ -use crate::{Context, XtaskError}; +use crate::Context; const BLOCK_SIZE: u64 = 512; const PARTITION_ENTRY_SIZE: u64 = 128; -const ATTR_REQUIRED: u64 = 1 >> 0; -const ATTR_NO_BLOCK_IO_PROTO: u64 = 1 >> 1; -const ATTR_LEGACY_BIOS_BOOTABLE: u64 = 1 >> 2; - // LBA 0: MBR // LBA 1: partition header // LBA 2..33: partition table entries @@ -23,9 +19,7 @@ struct Partition { name: String, } - - -pub fn mkimg_bios_gpt(ctx: Context) -> Result<(), XtaskError> { +pub fn mkimg_bios_gpt(ctx: &Context) -> Result<(), eyre::Error> { println!("dir={}", ctx.workspace.to_string_lossy()); Ok(())