roc run passes through arguments and exit code

This commit is contained in:
Richard Feldman 2021-04-15 18:27:59 -04:00
parent 962de95492
commit da7dffe0e9
4 changed files with 178 additions and 70 deletions

View file

@ -7,7 +7,6 @@ use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use target_lexicon::Triple; use target_lexicon::Triple;
@ -21,6 +20,18 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
)); ));
} }
pub enum BuildOutcome {
NoProblems,
OnlyWarnings,
Errors,
}
pub struct BuiltFile {
pub binary_path: PathBuf,
pub outcome: BuildOutcome,
pub total_time: Duration,
}
pub fn build_file<'a>( pub fn build_file<'a>(
arena: &'a Bump, arena: &'a Bump,
target: &Triple, target: &Triple,
@ -29,7 +40,7 @@ pub fn build_file<'a>(
opt_level: OptLevel, opt_level: OptLevel,
emit_debug_info: bool, emit_debug_info: bool,
link_type: LinkType, link_type: LinkType,
) -> Result<PathBuf, LoadingProblem<'a>> { ) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
@ -184,26 +195,23 @@ pub fn build_file<'a>(
todo!("gracefully handle error after `rustc` spawned"); todo!("gracefully handle error after `rustc` spawned");
}); });
let link_end = link_start.elapsed().unwrap(); let linking_time = link_start.elapsed().unwrap();
if emit_debug_info { if emit_debug_info {
println!("Finished linking in {} ms\n", link_end.as_millis()); println!("Finished linking in {} ms\n", linking_time.as_millis());
} }
let total_time = compilation_start.elapsed().unwrap();
// If the cmd errored out, return the Err. // If the cmd errored out, return the Err.
cmd_result?; cmd_result?;
// If possible, report the generated executable name relative to the current dir. // TODO change this to report whether there were errors or warnings!
let generated_filename = binary_path let outcome = BuildOutcome::NoProblems;
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
let total_end = compilation_start.elapsed().unwrap(); Ok(BuiltFile {
binary_path,
println!( total_time,
"🎉 Built {} in {} ms", outcome,
generated_filename.to_str().unwrap(), })
total_end.as_millis()
);
Ok(binary_path)
} }

View file

@ -1,12 +1,13 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use build::{build_file, BuildOutcome, BuiltFile};
use bumpalo::Bump; use bumpalo::Bump;
use clap::ArgMatches; use clap::{App, AppSettings, Arg, ArgMatches};
use clap::{App, Arg};
use roc_build::link::LinkType; use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
use std::env;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process; use std::process;
@ -16,18 +17,25 @@ use target_lexicon::Triple;
pub mod build; pub mod build;
pub mod repl; pub mod repl;
pub static FLAG_DEBUG: &str = "debug"; pub const CMD_RUN: &str = "run";
pub static FLAG_OPTIMIZE: &str = "optimize"; pub const CMD_BUILD: &str = "build";
pub static FLAG_ROC_FILE: &str = "ROC_FILE"; pub const CMD_REPL: &str = "repl";
pub static DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const CMD_EDIT: &str = "edit";
pub const CMD_DOCS: &str = "docs";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const ROC_FILE: &str = "ROC_FILE";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
pub fn build_app<'a>() -> App<'a> { pub fn build_app<'a>() -> App<'a> {
App::new("roc") App::new("roc")
.version(crate_version!()) .version(crate_version!())
.subcommand(App::new("build") .subcommand(App::new(CMD_BUILD)
.about("Build a program") .about("Build a program")
.arg( .arg(
Arg::with_name(FLAG_ROC_FILE) Arg::with_name(ROC_FILE)
.help("The .roc file to build") .help("The .roc file to build")
.required(true), .required(true),
) )
@ -44,13 +52,9 @@ pub fn build_app<'a>() -> App<'a> {
.required(false), .required(false),
) )
) )
.subcommand(App::new("run") .subcommand(App::new(CMD_RUN)
.about("Build and run a program") .about("Build and run a program")
.arg( .setting(AppSettings::TrailingVarArg)
Arg::with_name(FLAG_ROC_FILE)
.help("The .roc file to build and run")
.required(true),
)
.arg( .arg(
Arg::with_name(FLAG_OPTIMIZE) Arg::with_name(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE) .long(FLAG_OPTIMIZE)
@ -63,11 +67,21 @@ pub fn build_app<'a>() -> App<'a> {
.help("Store LLVM debug information in the generated program") .help("Store LLVM debug information in the generated program")
.required(false), .required(false),
) )
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file of an app to build and run")
.required(true),
)
.arg(
Arg::with_name(ARGS_FOR_APP)
.help("Arguments to pass into the app being run")
.multiple(true),
)
) )
.subcommand(App::new("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("edit") .subcommand(App::new(CMD_EDIT)
.about("Launch the Roc editor") .about("Launch the Roc editor")
.arg(Arg::with_name(DIRECTORY_OR_FILES) .arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1) .index(1)
@ -77,7 +91,7 @@ pub fn build_app<'a>() -> App<'a> {
) )
) )
.subcommand( .subcommand(
App::new("docs") App::new(CMD_DOCS)
.about("Generate documentation for Roc modules") .about("Generate documentation for Roc modules")
.arg(Arg::with_name(DIRECTORY_OR_FILES) .arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1) .index(1)
@ -97,10 +111,18 @@ pub fn docs(files: Vec<PathBuf>) {
) )
} }
pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { pub enum BuildConfig {
let arena = Bump::new(); BuildOnly,
let filename = matches.value_of(FLAG_ROC_FILE).unwrap(); BuildAndRun { roc_file_arg_index: usize },
}
pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use BuildConfig::*;
let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap();
let original_cwd = std::env::current_dir()?;
let opt_level = if matches.is_present(FLAG_OPTIMIZE) { let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
OptLevel::Optimize OptLevel::Optimize
} else { } else {
@ -130,7 +152,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
} }
}); });
let res_binary_path = build::build_file( let res_binary_path = build_file(
&arena, &arena,
target, target,
src_dir, src_dir,
@ -141,23 +163,77 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
); );
match res_binary_path { match res_binary_path {
Ok(binary_path) => { Ok(BuiltFile {
if run_after_build { binary_path,
// Run the compiled app outcome,
Command::new(binary_path) total_time,
.spawn() }) => {
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err)) match config {
.wait() BuildOnly => {
.expect("TODO gracefully handle block_on failing"); // 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);
println!(
"🎉 Built {} in {} ms",
generated_filename.to_str().unwrap(),
total_time.as_millis()
);
// Return a nonzero exit code if there were problems
let status_code = match outcome {
BuildOutcome::NoProblems => 0,
BuildOutcome::OnlyWarnings => 1,
BuildOutcome::Errors => 2,
};
Ok(status_code)
}
BuildAndRun { roc_file_arg_index } => {
let mut cmd = Command::new(binary_path);
// Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like:
//
// roc run 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);
}
}
// Run the compiled app
let exit_status = cmd
.current_dir(original_cwd)
.spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing when roc run spawns a subprocess for the compiled app");
// `roc run` exits with the same status code as the app it ran.
//
// If you want to know whether there were compilation problems
// via status code, use either `roc build` or `roc check` instead!
match exit_status.code() {
Some(code) => Ok(code),
None => {
todo!("TODO gracefully handle the roc run subprocess terminating with a signal.");
}
}
}
} }
} }
Err(LoadingProblem::FormattedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {
print!("{}", report); print!("{}", report);
Ok(1)
} }
Err(other) => { Err(other) => {
panic!("build_file failed with error:\n{:?}", other); panic!("build_file failed with error:\n{:?}", other);
} }
} }
Ok(())
} }

View file

@ -1,4 +1,7 @@
use roc_cli::{build, build_app, docs, repl, DIRECTORY_OR_FILES}; use roc_cli::{
build, build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_DOCS, CMD_EDIT, CMD_REPL, CMD_RUN,
DIRECTORY_OR_FILES, ROC_FILE,
};
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use target_lexicon::Triple; use target_lexicon::Triple;
@ -6,38 +9,58 @@ use target_lexicon::Triple;
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
let matches = build_app().get_matches(); let matches = build_app().get_matches();
match matches.subcommand_name() { let exit_code = match matches.subcommand_name() {
None => roc_editor::launch(&[]), None => {
Some("build") => build( roc_editor::launch(&[])?;
// rustc couldn't infer the error type here
Result::<i32, io::Error>::Ok(0)
}
Some(CMD_BUILD) => Ok(build(
&Triple::host(), &Triple::host(),
matches.subcommand_matches("build").unwrap(), matches.subcommand_matches(CMD_BUILD).unwrap(),
false, BuildConfig::BuildOnly,
), )?),
Some("run") => build( Some(CMD_RUN) => {
&Triple::host(), let subcmd_matches = matches.subcommand_matches(CMD_RUN).unwrap();
matches.subcommand_matches("run").unwrap(), let roc_file_arg_index = subcmd_matches.index_of(ROC_FILE).unwrap() + 1; // Not sure why this +1 is necessary, but it is!
true,
), Ok(build(
Some("repl") => repl::main(), &Triple::host(),
Some("edit") => { subcmd_matches,
BuildConfig::BuildAndRun { roc_file_arg_index },
)?)
}
Some(CMD_REPL) => {
repl::main()?;
// Exit 0 if the repl exited normally
Ok(0)
}
Some(CMD_EDIT) => {
match matches match matches
.subcommand_matches("edit") .subcommand_matches(CMD_EDIT)
.unwrap() .unwrap()
.values_of_os(DIRECTORY_OR_FILES) .values_of_os(DIRECTORY_OR_FILES)
{ {
None => roc_editor::launch(&[]), None => {
roc_editor::launch(&[])?;
}
Some(values) => { Some(values) => {
let paths = values let paths = values
.map(|os_str| Path::new(os_str)) .map(|os_str| Path::new(os_str))
.collect::<Vec<&Path>>(); .collect::<Vec<&Path>>();
roc_editor::launch(&paths) roc_editor::launch(&paths)?;
} }
} }
// Exit 0 if the editor exited normally
Ok(0)
} }
Some("docs") => { Some(CMD_DOCS) => {
let values = matches let values = matches
.subcommand_matches("docs") .subcommand_matches(CMD_DOCS)
.unwrap() .unwrap()
.values_of_os(DIRECTORY_OR_FILES) .values_of_os(DIRECTORY_OR_FILES)
.unwrap(); .unwrap();
@ -48,8 +71,10 @@ fn main() -> io::Result<()> {
docs(paths); docs(paths);
Ok(()) Ok(0)
} }
_ => unreachable!(), _ => unreachable!(),
} }?;
std::process::exit(exit_code);
} }

View file

@ -2,7 +2,6 @@
use roc_std::{alloca, RocCallResult, RocResult, RocStr}; use roc_std::{alloca, RocCallResult, RocResult, RocStr};
use std::alloc::Layout; use std::alloc::Layout;
use std::time::SystemTime;
extern "C" { extern "C" {
#[link_name = "roc__mainForHost_1_exposed"] #[link_name = "roc__mainForHost_1_exposed"]