mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
roc run
passes through arguments and exit code
This commit is contained in:
parent
962de95492
commit
da7dffe0e9
4 changed files with 178 additions and 70 deletions
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
138
cli/src/lib.rs
138
cli/src/lib.rs
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue