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 {}",