Add roc run to run even if there are build errors.

This commit is contained in:
Richard Feldman 2022-04-20 13:50:00 -04:00
parent a47b3be9c0
commit 62484d3890
No known key found for this signature in database
GPG key ID: 7E4127D1E4241798
4 changed files with 141 additions and 91 deletions

View file

@ -1,7 +1,7 @@
use bumpalo::Bump; use bumpalo::Bump;
use roc_build::{ use roc_build::{
link::{link, rebuild_host, LinkType}, link::{link, rebuild_host, LinkType},
program, program::{self, Problems},
}; };
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_load::LoadingProblem; use roc_load::LoadingProblem;
@ -21,25 +21,9 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
)); ));
} }
pub enum BuildOutcome {
NoProblems,
OnlyWarnings,
Errors,
}
impl BuildOutcome {
pub fn status_code(&self) -> i32 {
match self {
Self::NoProblems => 0,
Self::OnlyWarnings => 1,
Self::Errors => 2,
}
}
}
pub struct BuiltFile { pub struct BuiltFile {
pub binary_path: PathBuf, pub binary_path: PathBuf,
pub outcome: BuildOutcome, pub problems: Problems,
pub total_time: Duration, pub total_time: Duration,
} }
@ -184,7 +168,7 @@ pub fn build_file<'a>(
// This only needs to be mutable for report_problems. This can't be done // This only needs to be mutable for report_problems. This can't be done
// inside a nested scope without causing a borrow error! // inside a nested scope without causing a borrow error!
let mut loaded = loaded; let mut loaded = loaded;
program::report_problems_monomorphized(&mut loaded); let problems = program::report_problems_monomorphized(&mut loaded);
let loaded = loaded; let loaded = loaded;
let code_gen_timing = program::gen_from_mono_module( let code_gen_timing = program::gen_from_mono_module(
@ -243,7 +227,7 @@ pub fn build_file<'a>(
// Step 2: link the precompiled host and compiled app // Step 2: link the precompiled host and compiled app
let link_start = SystemTime::now(); let link_start = SystemTime::now();
let outcome = if surgically_link { let problems = if surgically_link {
roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path) roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path)
.map_err(|err| { .map_err(|err| {
todo!( todo!(
@ -251,12 +235,12 @@ pub fn build_file<'a>(
err err
); );
})?; })?;
BuildOutcome::NoProblems problems
} else if matches!(link_type, LinkType::None) { } else if matches!(link_type, LinkType::None) {
// Just copy the object file to the output folder. // Just copy the object file to the output folder.
binary_path.set_extension(app_extension); binary_path.set_extension(app_extension);
std::fs::copy(app_o_file, &binary_path).unwrap(); std::fs::copy(app_o_file, &binary_path).unwrap();
BuildOutcome::NoProblems problems
} else { } else {
let mut inputs = vec![ let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(), host_input_path.as_path().to_str().unwrap(),
@ -281,11 +265,15 @@ pub fn build_file<'a>(
todo!("gracefully handle error after `ld` spawned"); todo!("gracefully handle error after `ld` spawned");
})?; })?;
// TODO change this to report whether there were errors or warnings!
if exit_status.success() { if exit_status.success() {
BuildOutcome::NoProblems problems
} else { } else {
BuildOutcome::Errors let mut problems = problems;
// Add an error for `ld` failing
problems.errors += 1;
problems
} }
}; };
let linking_time = link_start.elapsed().unwrap(); let linking_time = link_start.elapsed().unwrap();
@ -298,7 +286,7 @@ pub fn build_file<'a>(
Ok(BuiltFile { Ok(BuiltFile {
binary_path, binary_path,
outcome, problems,
total_time, total_time,
}) })
} }
@ -350,10 +338,6 @@ fn spawn_rebuild_thread(
} }
let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
if !precompiled {
println!("Done!");
}
rebuild_host_end.as_millis() rebuild_host_end.as_millis()
}) })
} }
@ -364,7 +348,7 @@ pub fn check_file(
src_dir: PathBuf, src_dir: PathBuf,
roc_file_path: PathBuf, roc_file_path: PathBuf,
emit_timings: bool, emit_timings: bool,
) -> Result<usize, LoadingProblem> { ) -> Result<program::Problems, LoadingProblem> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
// only used for generating errors. We don't do code generation, so hardcoding should be fine // only used for generating errors. We don't do code generation, so hardcoding should be fine

View file

@ -1,7 +1,7 @@
#[macro_use] #[macro_use]
extern crate const_format; extern crate const_format;
use build::{BuildOutcome, BuiltFile}; use build::BuiltFile;
use bumpalo::Bump; use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches}; use clap::{App, AppSettings, Arg, ArgMatches};
use roc_build::link::LinkType; use roc_build::link::LinkType;
@ -24,6 +24,7 @@ mod format;
pub use format::format; pub use format::format;
pub const CMD_BUILD: &str = "build"; pub const CMD_BUILD: &str = "build";
pub const CMD_RUN: &str = "run";
pub const CMD_REPL: &str = "repl"; pub const CMD_REPL: &str = "repl";
pub const CMD_EDIT: &str = "edit"; pub const CMD_EDIT: &str = "edit";
pub const CMD_DOCS: &str = "docs"; pub const CMD_DOCS: &str = "docs";
@ -142,6 +143,14 @@ pub fn build_app<'a>() -> App<'a> {
.subcommand(App::new(CMD_REPL) .subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)") .about("Launch the interactive Read Eval Print Loop (REPL)")
) )
.subcommand(App::new(CMD_RUN)
.about("Run a .roc file even if it has build errors")
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to run")
.required(true),
)
)
.subcommand(App::new(CMD_FORMAT) .subcommand(App::new(CMD_FORMAT)
.about("Format a .roc file using standard Roc formatting") .about("Format a .roc file using standard Roc formatting")
.arg( .arg(
@ -168,7 +177,7 @@ pub fn build_app<'a>() -> App<'a> {
) )
.arg( .arg(
Arg::new(ROC_FILE) Arg::new(ROC_FILE)
.about("The .roc file of an app to run") .about("The .roc file of an app to check")
.required(true), .required(true),
) )
) )
@ -273,6 +282,7 @@ pub fn docs(files: Vec<PathBuf>) {
pub enum BuildConfig { pub enum BuildConfig {
BuildOnly, BuildOnly,
BuildAndRun { roc_file_arg_index: usize }, BuildAndRun { roc_file_arg_index: usize },
BuildAndRunIfNoErrors { roc_file_arg_index: usize },
} }
pub enum FormatMode { pub enum FormatMode {
@ -380,7 +390,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
match res_binary_path { match res_binary_path {
Ok(BuiltFile { Ok(BuiltFile {
binary_path, binary_path,
outcome, problems,
total_time, total_time,
}) => { }) => {
match config { match config {
@ -401,50 +411,26 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
); );
// Return a nonzero exit code if there were problems // Return a nonzero exit code if there were problems
Ok(outcome.status_code()) Ok(problems.exit_code())
} }
BuildAndRun { roc_file_arg_index } => { BuildAndRun { roc_file_arg_index } => roc_run(
let mut cmd = match triple.architecture { &arena,
Architecture::Wasm32 => { &original_cwd,
// If possible, report the generated executable name relative to the current dir. triple,
let generated_filename = binary_path roc_file_arg_index,
.strip_prefix(env::current_dir().unwrap()) &binary_path,
.unwrap_or(&binary_path); ),
BuildAndRunIfNoErrors { roc_file_arg_index } => {
// No need to waste time freeing this memory, if problems.errors == 0 {
// since the process is about to exit anyway. roc_run(
std::mem::forget(arena); &arena,
&original_cwd,
let args = std::env::args() triple,
.skip(roc_file_arg_index) roc_file_arg_index,
.collect::<Vec<_>>(); &binary_path,
)
run_with_wasmer(generated_filename, &args); } else {
return Ok(0); Ok(problems.exit_code())
}
_ => Command::new(&binary_path),
};
if let Architecture::Wasm32 = triple.architecture {
cmd.arg(binary_path);
}
// Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like:
//
// roc app.roc foo bar baz
//
// ...and have it so that app.roc will receive only `foo`,
// `bar`, and `baz` as its arguments.
for (index, arg) in std::env::args().enumerate() {
if index > roc_file_arg_index {
cmd.arg(arg);
}
}
match outcome {
BuildOutcome::Errors => Ok(outcome.status_code()),
_ => roc_run(cmd.current_dir(original_cwd)),
} }
} }
} }
@ -461,11 +447,55 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
} }
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
fn roc_run(cmd: &mut Command) -> io::Result<i32> { fn roc_run(
arena: &Bump,
cwd: &Path,
triple: Triple,
roc_file_arg_index: usize,
binary_path: &Path,
) -> io::Result<i32> {
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
let mut cmd = match triple.architecture {
Architecture::Wasm32 => {
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
// No need to waste time freeing this memory,
// since the process is about to exit anyway.
std::mem::forget(arena);
let args = std::env::args()
.skip(roc_file_arg_index)
.collect::<Vec<_>>();
run_with_wasmer(generated_filename, &args);
return Ok(0);
}
_ => Command::new(&binary_path),
};
if let Architecture::Wasm32 = triple.architecture {
cmd.arg(binary_path);
}
// Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like:
//
// roc app.roc foo bar baz
//
// ...and have it so that app.roc will receive only `foo`,
// `bar`, and `baz` as its arguments.
for (index, arg) in std::env::args().enumerate() {
if index > roc_file_arg_index {
cmd.arg(arg);
}
}
// This is much faster than spawning a subprocess if we're on a UNIX system! // This is much faster than spawning a subprocess if we're on a UNIX system!
let err = cmd.exec(); let err = cmd.current_dir(cwd).exec();
// If exec actually returned, it was definitely an error! (Otherwise, // If exec actually returned, it was definitely an error! (Otherwise,
// this process would have been replaced by the other one, and we'd // this process would have been replaced by the other one, and we'd

View file

@ -1,7 +1,8 @@
use roc_cli::build::check_file; use roc_cli::build::check_file;
use roc_cli::{ use roc_cli::{
build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT,
CMD_FORMAT, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, ROC_FILE, CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME,
ROC_FILE,
}; };
use roc_load::LoadingProblem; use roc_load::LoadingProblem;
use std::fs::{self, FileType}; use std::fs::{self, FileType};
@ -27,7 +28,10 @@ fn main() -> io::Result<()> {
Some(arg_index) => { Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index }) build(
&matches,
BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index },
)
} }
None => { None => {
@ -37,6 +41,21 @@ fn main() -> io::Result<()> {
} }
} }
} }
Some((CMD_RUN, matches)) => {
match matches.index_of(ROC_FILE) {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index })
}
None => {
eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command.");
Ok(1)
}
}
}
Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?), Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?),
Some((CMD_CHECK, matches)) => { Some((CMD_CHECK, matches)) => {
let arena = bumpalo::Bump::new(); let arena = bumpalo::Bump::new();
@ -47,10 +66,7 @@ fn main() -> io::Result<()> {
let src_dir = roc_file_path.parent().unwrap().to_owned(); let src_dir = roc_file_path.parent().unwrap().to_owned();
match check_file(&arena, src_dir, roc_file_path, emit_timings) { match check_file(&arena, src_dir, roc_file_path, emit_timings) {
Ok(number_of_errors) => { Ok(problems) => Ok(problems.exit_code()),
let exit_code = if number_of_errors != 0 { 1 } else { 0 };
Ok(exit_code)
}
Err(LoadingProblem::FormattedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {
print!("{}", report); print!("{}", report);

View file

@ -28,7 +28,7 @@ const LLVM_VERSION: &str = "12";
// them after type checking (like Elm does) so we can complete the entire // them after type checking (like Elm does) so we can complete the entire
// `roc check` process without needing to monomorphize. // `roc check` process without needing to monomorphize.
/// Returns the number of problems reported. /// Returns the number of problems reported.
pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize { pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> Problems {
report_problems_help( report_problems_help(
loaded.total_problems(), loaded.total_problems(),
&loaded.sources, &loaded.sources,
@ -39,7 +39,7 @@ pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize
) )
} }
pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> Problems {
report_problems_help( report_problems_help(
loaded.total_problems(), loaded.total_problems(),
&loaded.sources, &loaded.sources,
@ -50,6 +50,23 @@ pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize {
) )
} }
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Problems {
pub errors: usize,
pub warnings: usize,
}
impl Problems {
pub fn exit_code(&self) -> i32 {
// 0 means no problems, 1 means errors, 2 means warnings
if self.errors > 0 {
1
} else {
self.warnings.min(1) as i32
}
}
}
fn report_problems_help( fn report_problems_help(
total_problems: usize, total_problems: usize,
sources: &MutMap<ModuleId, (PathBuf, Box<str>)>, sources: &MutMap<ModuleId, (PathBuf, Box<str>)>,
@ -57,7 +74,7 @@ fn report_problems_help(
can_problems: &mut MutMap<ModuleId, Vec<roc_problem::can::Problem>>, can_problems: &mut MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
type_problems: &mut MutMap<ModuleId, Vec<roc_solve::solve::TypeError>>, type_problems: &mut MutMap<ModuleId, Vec<roc_solve::solve::TypeError>>,
mono_problems: &mut MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>, mono_problems: &mut MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
) -> usize { ) -> Problems {
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*, can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*,
DEFAULT_PALETTE, DEFAULT_PALETTE,
@ -144,13 +161,13 @@ fn report_problems_help(
if errors.is_empty() { if errors.is_empty() {
problems_reported = warnings.len(); problems_reported = warnings.len();
for warning in warnings { for warning in warnings.iter() {
println!("\n{}\n", warning); println!("\n{}\n", warning);
} }
} else { } else {
problems_reported = errors.len(); problems_reported = errors.len();
for error in errors { for error in errors.iter() {
println!("\n{}\n", error); println!("\n{}\n", error);
} }
} }
@ -165,7 +182,10 @@ fn report_problems_help(
println!("{}\u{001B}[0m\n", Report::horizontal_rule(&palette)); println!("{}\u{001B}[0m\n", Report::horizontal_rule(&palette));
} }
problems_reported Problems {
errors: errors.len(),
warnings: warnings.len(),
}
} }
#[cfg(not(feature = "llvm"))] #[cfg(not(feature = "llvm"))]