diff --git a/bootstrap/src/clean.rs b/bootstrap/src/clean.rs index e48c519..8a508fa 100644 --- a/bootstrap/src/clean.rs +++ b/bootstrap/src/clean.rs @@ -5,11 +5,23 @@ use crate::Run; /// Clean the build directory #[derive(Args, Debug)] -pub struct CleanCommand {} +pub struct CleanCommand { + #[arg(short, long)] + pub verbose: bool, +} impl Run for CleanCommand { + const STEP_DISPLAY_NAME: &'static str = "clean"; + fn run(&self, manifest: &Manifest) { + self.log_action_start("cleaning", "build directory"); let _ = std::fs::remove_dir_all("crates/target"); + self.log_action_context("rm", "crates/target"); let _ = std::fs::remove_dir_all(&manifest.out_dir); + self.log_action_context("rm", &manifest.out_dir.display()); + } + + fn verbose(&self) -> bool { + self.verbose } } diff --git a/bootstrap/src/fmt.rs b/bootstrap/src/fmt.rs index 33553ff..fe0fc0a 100644 --- a/bootstrap/src/fmt.rs +++ b/bootstrap/src/fmt.rs @@ -10,9 +10,14 @@ use crate::Run; pub struct FmtCommand { #[arg(short, long)] pub check: bool, + + #[arg(short, long)] + pub verbose: bool, } impl Run for FmtCommand { + const STEP_DISPLAY_NAME: &'static str = "FMT"; + fn run(&self, _manifest: &crate::manifest::Manifest) { self.perform( Command::new("cargo").arg("fmt").args(["--manifest-path", "bootstrap/Cargo.toml"]), @@ -30,6 +35,10 @@ impl Run for FmtCommand { self.perform(Command::new("rustfmt").args(["--edition", "2021"]).arg(file.unwrap())); } } + + fn verbose(&self) -> bool { + self.verbose + } } impl FmtCommand { @@ -37,7 +46,7 @@ impl FmtCommand { if self.check { command.arg("--check"); } - log::debug!("running {:?}", command); - assert!(command.status().unwrap().success(), "failed to run {:?}", command); + + self.command_status("format code", command); } } diff --git a/bootstrap/src/main.rs b/bootstrap/src/main.rs index c8a1e5d..b8528f2 100644 --- a/bootstrap/src/main.rs +++ b/bootstrap/src/main.rs @@ -1,4 +1,8 @@ +use std::fmt::Display; +use std::process::{self, ExitStatus}; + use clap::{Parser, Subcommand}; +use color_print::cprintln; use crate::manifest::Manifest; @@ -37,7 +41,65 @@ pub enum Command { } trait Run { + // The name like "BUILD" or "TEST" for logs. + const STEP_DISPLAY_NAME: &'static str; fn run(&self, manifest: &Manifest); + + /// True if verbose output should be enabled. + fn verbose(&self) -> bool; + + /// Record that the step has started a new action. + fn log_action_start(&self, action: &str, item: impl Display) { + let name = Self::STEP_DISPLAY_NAME; + cprintln!("[{name}] {action} {item}"); + } + + /// Record context associated with the current action. Only use if there has been a preceding + /// call to `log_action_start`. + fn log_action_context(&self, key: impl Display, value: impl Display) { + if self.verbose() { + cprintln!(" {key}: {value}"); + } + } + + /// Run a command and ensure it succeeds, capturing output. + fn command_output(&self, action: &str, command: &mut process::Command) -> process::Output { + if self.verbose() { + cprintln!(" {action}: {command:?}"); + } + + match command.output() { + // Command ran and completed successfully + Ok(output) if output.status.success() => { + if self.verbose() { + cprintln!(" success"); + } + output + } + // Command ran but did not complete + Ok(output) => panic!("command failed: {output:?}"), + Err(e) => panic!("command failed: {e:?}"), + } + } + + /// Run a command and ensure it succeeds. + fn command_status(&self, action: &str, command: &mut process::Command) -> ExitStatus { + if self.verbose() { + cprintln!(" {}: {}", action, format!("{:?}", command).replace('"', "")); + } + match command.status() { + // Command ran and completed successfully + Ok(status) if status.success() => { + if self.verbose() { + cprintln!(" success"); + } + status + } + // Command ran but did not complete + Ok(status) => panic!("command failed: {status:?}"), + Err(e) => panic!("command failed: {e:?}"), + } + } } fn main() { @@ -49,10 +111,23 @@ fn main() { release: cli.release, out_dir: cli.out_dir.unwrap_or("build".to_string()).into(), }; + match cli.command { - Command::Test(test) => test.run(&manifest), - Command::Clean(clean) => clean.run(&manifest), - Command::Rustc(rustc) => rustc.run(&manifest), - Command::Fmt(fmt) => fmt.run(&manifest), + Command::Test(mut test) => { + test.verbose |= cli.verbose; + test.run(&manifest) + } + Command::Clean(mut clean) => { + clean.verbose |= cli.verbose; + clean.run(&manifest) + } + Command::Rustc(mut rustc) => { + rustc.verbose |= cli.verbose; + rustc.run(&manifest) + } + Command::Fmt(mut fmt) => { + fmt.verbose |= cli.verbose; + fmt.run(&manifest) + } } } diff --git a/bootstrap/src/manifest.rs b/bootstrap/src/manifest.rs index 7f9eb99..90c6541 100644 --- a/bootstrap/src/manifest.rs +++ b/bootstrap/src/manifest.rs @@ -1,8 +1,12 @@ -use anstream::eprintln as println; -use color_print::cprintln; use std::path::{Path, PathBuf}; use std::process::Command; +use anstream::eprintln as println; +use color_print::cprintln; + +use crate::Run; + +#[derive(Debug)] pub struct Manifest { pub verbose: bool, pub release: bool, @@ -12,36 +16,8 @@ pub struct Manifest { impl Manifest { /// Builds the rustc codegen c library pub fn prepare(&self) { - cprintln!("[BUILD] codegen backend"); - let mut command = Command::new("cargo"); - command.arg("build").args(["--manifest-path", "crates/Cargo.toml"]); - if self.verbose { - command.args(["-F", "debug"]); - } - if self.release { - command.arg("--release"); - } - log::debug!("running {:?}", command); - command.status().unwrap(); - - cprintln!("[BUILD] librust_runtime"); - std::fs::create_dir_all(&self.out_dir).unwrap(); - let cc = std::env::var("CC").unwrap_or("clang".to_string()); - let mut command = Command::new(&cc); - command - .arg("rust_runtime/rust_runtime.c") - .arg("-o") - .arg(self.out_dir.join("rust_runtime.o")) - .arg("-c"); - log::debug!("running {:?}", command); - command.status().unwrap(); - let mut command = Command::new("ar"); - command - .arg("rcs") - .arg(self.out_dir.join("librust_runtime.a")) - .arg(self.out_dir.join("rust_runtime.o")); - log::debug!("running {:?}", command); - command.status().unwrap(); + let prepare = PrepareAction { verbose: self.verbose }; + prepare.run(&self); } /// The path to the rustc codegen c library @@ -72,3 +48,60 @@ impl Manifest { command } } + +struct PrepareAction { + verbose: bool, +} + +impl Run for PrepareAction { + const STEP_DISPLAY_NAME: &'static str = "prepare"; + + fn run(&self, manifest: &Manifest) { + // action: Build codegen backend + self.log_action_start("building", "codegen backend"); + self.log_action_context("target", manifest.codegen_backend().display()); + + let mut command = Command::new("cargo"); + command.arg("build").args(["--manifest-path", "crates/Cargo.toml"]); + if manifest.verbose { + command.args(["-v"]); + } + if manifest.release { + command.arg("--release"); + } + self.command_status("build", &mut command); + + // action: Build runtime library + self.log_action_start("building", "librust_runtime"); + self.log_action_context("output dir", &manifest.out_dir.to_path_buf().display()); + + // cmd: Create output directory + if let Err(e) = std::fs::create_dir_all(&manifest.out_dir) { + cprintln!(" failed to create output directory: {}", e); + std::process::exit(1); + } + + let cc = std::env::var("CC").unwrap_or("clang".to_string()); + + // cmd: Compile runtime.c + let mut command = Command::new(&cc); + command + .arg("rust_runtime/rust_runtime.c") + .arg("-o") + .arg(manifest.out_dir.join("rust_runtime.o")) + .arg("-c"); + self.command_status("build", &mut command); + + // cmd: Create static library + let mut command = Command::new("ar"); + command + .arg("rcs") + .arg(manifest.out_dir.join("librust_runtime.a")) + .arg(manifest.out_dir.join("rust_runtime.o")); + self.command_status("archive", &mut command); + } + + fn verbose(&self) -> bool { + self.verbose + } +} diff --git a/bootstrap/src/rustc.rs b/bootstrap/src/rustc.rs index b1fb144..6b82ebc 100644 --- a/bootstrap/src/rustc.rs +++ b/bootstrap/src/rustc.rs @@ -12,9 +12,14 @@ pub struct RustcCommand { #[arg(last = true)] slop: Vec, + + #[arg(short, long)] + pub verbose: bool, } impl Run for RustcCommand { + const STEP_DISPLAY_NAME: &'static str = "RUSTC"; + fn run(&self, manifest: &Manifest) { manifest.prepare(); @@ -25,7 +30,14 @@ impl Run for RustcCommand { .arg("--out-dir") .arg(&manifest.out_dir) .args(&self.slop); - log::debug!("running {:?}", command); - command.status().unwrap(); + if self.verbose { + command.env("RUST_BACKTRACE", "full"); + } + + self.command_status("rustc", &mut command); + } + + fn verbose(&self) -> bool { + self.verbose } } diff --git a/bootstrap/src/test.rs b/bootstrap/src/test.rs index 90d9d68..da18f58 100644 --- a/bootstrap/src/test.rs +++ b/bootstrap/src/test.rs @@ -15,11 +15,17 @@ use crate::Run; #[derive(Args, Debug)] pub struct TestCommand { /// Update the blessed output - #[clap(long)] + #[arg(long)] pub bless: bool, + + /// Whether to show verbose output + #[arg(short, long)] + pub verbose: bool, } impl Run for TestCommand { + const STEP_DISPLAY_NAME: &'static str = "TEST"; + fn run(&self, manifest: &Manifest) { manifest.prepare(); @@ -27,53 +33,68 @@ impl Run for TestCommand { cprintln!("Test failed: {}", info); })); - cprintln!("[TEST] running cargo test"); + // action: Run cargo test + self.log_action_start("running", "cargo test"); let mut command = std::process::Command::new("cargo"); command.args(["test", "--manifest-path", "crates/Cargo.toml"]); - log::debug!("running {:?}", command); - assert!(command.status().unwrap().success(), "failed to run {:?}", command); + self.command_status("cargo", &mut command); let testcases = self.collect_testcases(manifest); - cprintln!("[TEST] found {} testcases", testcases.len()); + self.log_action_start(&format!("found {} testcases", testcases.len()), ""); + testcases.iter().for_each(|t| self.log_action_context(t.test.as_str(), t.name.as_str())); - let filechecker = FileChecker::new(); + let filechecker = FileChecker::new(self.verbose); for testcase in testcases { match testcase.test { TestType::FileCheck => { - cprint!("File checking {}...", testcase.name); + self.log_action_start("TEST file checking", &testcase.name); + self.log_action_context("source", &testcase.source.display()); + self.log_action_context("output", &testcase.output_file.display()); testcase.build(manifest); - filechecker.run(&testcase); + filechecker.check_testcase(&testcase); } TestType::Bless => { - cprint!("Blessing {}...", testcase.name); + self.log_action_start("TEST Bless", &testcase.name); + self.log_action_context("source", &testcase.source.display()); + self.log_action_context("output", &testcase.output_file.display()); testcase.build(manifest); bless(self.bless, &testcase); } TestType::Compile => { - cprint!("Compiling {}...", testcase.name); + self.log_action_start("TEST Compile", &testcase.name); + self.log_action_context("source", &testcase.source.display()); + self.log_action_context("output", &testcase.output_file.display()); testcase.build(manifest); } TestType::CompileLib => { - cprint!("Compiling lib {}...", testcase.name); + self.log_action_start("TEST CompileLib", &testcase.name); + self.log_action_context("source", &testcase.source.display()); + self.log_action_context("output", &testcase.output_file.display()); testcase.build_lib(manifest); } } - cprintln!("OK"); } } + + fn verbose(&self) -> bool { + self.verbose + } } impl TestCommand { pub fn collect_testcases(&self, manifest: &Manifest) -> Vec { let mut tests = vec![]; + let verbose = self.verbose; + // Examples for case in glob("examples/*.rs").unwrap() { let case = case.unwrap(); let filename = case.file_stem().unwrap(); let name = format!("examples/{}", filename.to_string_lossy()); let output_file = manifest.out_dir.join("examples").join(filename); - tests.push(TestCase { name, source: case, output_file, test: TestType::Compile }) + let testcase = TestCase::new(name, case, output_file, TestType::Compile, verbose); + tests.push(testcase); } // Codegen tests @@ -82,7 +103,8 @@ impl TestCommand { let filename = case.file_stem().unwrap(); let name = format!("codegen/{}", filename.to_string_lossy()); let output_file = manifest.out_dir.join("tests/codegen").join(filename); - tests.push(TestCase { name, source: case, output_file, test: TestType::FileCheck }) + let testcase = TestCase::new(name, case, output_file, TestType::FileCheck, verbose); + tests.push(testcase); } // Bless tests - the output should be the same as the last run @@ -91,7 +113,8 @@ impl TestCommand { let filename = case.file_stem().unwrap(); let name = format!("bless/{}", filename.to_string_lossy()); let output_file = manifest.out_dir.join("tests/bless").join(filename); - tests.push(TestCase { name, source: case, output_file, test: TestType::Bless }) + let testcase = TestCase::new(name, case, output_file, TestType::Bless, verbose); + tests.push(testcase); } // Collect test-auxiliary @@ -105,7 +128,9 @@ impl TestCommand { let filename = source.file_stem().unwrap(); let name = format!("auxiliary/{}", filename.to_string_lossy()); let output_file = manifest.out_dir.join(filename); // aux files are output to the base directory - auxiliary.push(TestCase { name, source, output_file, test: TestType::CompileLib }) + let testcase = + TestCase::new(name, source, output_file, TestType::CompileLib, verbose); + auxiliary.push(testcase); } } @@ -116,6 +141,7 @@ impl TestCommand { } } +#[derive(Debug)] pub enum TestType { /// Test an executable can be compiled Compile, @@ -126,15 +152,47 @@ pub enum TestType { /// Bless test - the output should be the same as the last run Bless, } +impl TestType { + pub fn as_str(&self) -> &'static str { + match self { + TestType::Compile => "compile", + TestType::CompileLib => "compile-lib", + TestType::FileCheck => "filecheck", + TestType::Bless => "bless", + } + } +} pub struct TestCase { pub name: String, pub source: PathBuf, pub output_file: PathBuf, pub test: TestType, + pub verbose: bool, +} + +impl Run for TestCase { + const STEP_DISPLAY_NAME: &'static str = "TESTCASE"; + fn run(&self, manifest: &Manifest) { + self.build(manifest); + } + + fn verbose(&self) -> bool { + self.verbose + } } impl TestCase { + pub fn new( + name: String, + source: PathBuf, + output_file: PathBuf, + test: TestType, + verbose: bool, + ) -> Self { + Self { name, source, output_file, test, verbose } + } + pub fn build(&self, manifest: &Manifest) { let output_dir = self.output_file.parent().unwrap(); std::fs::create_dir_all(output_dir).unwrap(); @@ -145,8 +203,7 @@ impl TestCase { .arg(&self.source) .arg("-o") .arg(&self.output_file); - log::debug!("running {:?}", command); - command.status().unwrap(); + self.command_status("compile", &mut command); } pub fn build_lib(&self, manifest: &Manifest) { @@ -157,10 +214,9 @@ impl TestCase { .args(["--crate-type", "lib"]) .arg("-O") .arg(&self.source) - .arg("--out-dir") // we use `--out-dir` to integrate with the default name convention - .arg(output_dir); // so here we ignore the filename and just use the directory - log::debug!("running {:?}", command); - command.status().unwrap(); + .arg("--out-dir") + .arg(output_dir); + self.command_status("compile lib", &mut command); } /// Get the generated C file f @@ -182,10 +238,21 @@ impl TestCase { struct FileChecker { filecheck: PathBuf, + verbose: bool, +} + +impl Run for FileChecker { + const STEP_DISPLAY_NAME: &'static str = "FILECHECK"; + + fn run(&self, _manifest: &Manifest) {} + + fn verbose(&self) -> bool { + self.verbose + } } impl FileChecker { - pub fn new() -> Self { + pub fn new(verbose: bool) -> Self { let filecheck = [ "FileCheck-18", "FileCheck-17", @@ -198,15 +265,14 @@ impl FileChecker { .find_map(|filecheck| which(filecheck).ok()) .expect("`FileCheck` not found"); - Self { filecheck } + Self { filecheck, verbose } } - fn run(&self, case: &TestCase) { + fn check_testcase(&self, case: &TestCase) { let generated = File::open(case.generated()).unwrap(); let mut command = std::process::Command::new(&self.filecheck); command.arg(&case.source).stdin(generated); - log::debug!("running {:?}", command); - let output = command.output().unwrap(); + let output = self.command_output("filecheck", &mut command); assert!( output.status.success(), "failed to run FileCheck on {}",