mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
342 lines
12 KiB
Rust
342 lines
12 KiB
Rust
//! The `roc` binary that brings together all functionality in the Roc toolset.
|
|
use roc_build::link::LinkType;
|
|
use roc_build::program::{check_file, CodeGenBackend};
|
|
use roc_cli::{
|
|
build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DEV,
|
|
CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GEN_STUB_LIB, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST,
|
|
CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_DEV, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET,
|
|
FLAG_TIME, GLUE_DIR, GLUE_SPEC, ROC_FILE,
|
|
};
|
|
use roc_docs::generate_docs_html;
|
|
use roc_error_macros::user_error;
|
|
use roc_gen_dev::AssemblyBackendMode;
|
|
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
|
use roc_load::{LoadingProblem, Threading};
|
|
use roc_packaging::cache::{self, RocCacheDir};
|
|
use std::fs::{self, FileType};
|
|
use std::io;
|
|
use std::path::{Path, PathBuf};
|
|
use std::str::FromStr;
|
|
use target_lexicon::Triple;
|
|
|
|
#[macro_use]
|
|
extern crate const_format;
|
|
|
|
#[global_allocator]
|
|
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
|
|
|
use std::ffi::{OsStr, OsString};
|
|
|
|
use roc_cli::build;
|
|
|
|
fn main() -> io::Result<()> {
|
|
let _tracing_guards = roc_tracing::setup_tracing!();
|
|
|
|
let app = build_app();
|
|
let subcommands: Vec<String> = app
|
|
.get_subcommands()
|
|
.map(|c| c.get_name().to_owned())
|
|
.collect();
|
|
let matches = app.get_matches();
|
|
|
|
let exit_code = match matches.subcommand() {
|
|
None => {
|
|
if matches.contains_id(ROC_FILE) {
|
|
build(
|
|
&matches,
|
|
&subcommands,
|
|
BuildConfig::BuildAndRunIfNoErrors,
|
|
Triple::host(),
|
|
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
|
LinkType::Executable,
|
|
)
|
|
} else {
|
|
launch_editor(None)?;
|
|
|
|
Ok(0)
|
|
}
|
|
}
|
|
Some((CMD_RUN, matches)) => {
|
|
if matches.contains_id(ROC_FILE) {
|
|
build(
|
|
matches,
|
|
&subcommands,
|
|
BuildConfig::BuildAndRun,
|
|
Triple::host(),
|
|
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
|
LinkType::Executable,
|
|
)
|
|
} else {
|
|
eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command.");
|
|
|
|
Ok(1)
|
|
}
|
|
}
|
|
Some((CMD_TEST, matches)) => {
|
|
if matches.contains_id(ROC_FILE) {
|
|
test(matches, Triple::host())
|
|
} else {
|
|
eprintln!("What .roc file do you want to test? Specify it at the end of the `roc test` command.");
|
|
|
|
Ok(1)
|
|
}
|
|
}
|
|
Some((CMD_DEV, matches)) => {
|
|
if matches.contains_id(ROC_FILE) {
|
|
build(
|
|
matches,
|
|
&subcommands,
|
|
BuildConfig::BuildAndRunIfNoErrors,
|
|
Triple::host(),
|
|
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
|
LinkType::Executable,
|
|
)
|
|
} else {
|
|
eprintln!("What .roc file do you want to build? Specify it at the end of the `roc run` command.");
|
|
|
|
Ok(1)
|
|
}
|
|
}
|
|
Some((CMD_GLUE, matches)) => {
|
|
let input_path = matches.get_one::<PathBuf>(ROC_FILE).unwrap();
|
|
let output_path = matches.get_one::<PathBuf>(GLUE_DIR).unwrap();
|
|
let spec_path = matches.get_one::<PathBuf>(GLUE_SPEC).unwrap();
|
|
|
|
// have the backend supply `roc_alloc` and friends
|
|
let backend = match matches.get_flag(FLAG_DEV) {
|
|
true => CodeGenBackend::Assembly(AssemblyBackendMode::Test),
|
|
false => CodeGenBackend::Llvm(LlvmBackendMode::BinaryGlue),
|
|
};
|
|
|
|
if !output_path.exists() || output_path.is_dir() {
|
|
roc_glue::generate(input_path, output_path, spec_path, backend)
|
|
} else {
|
|
eprintln!("`roc glue` must be given a directory to output into, because the glue might generate multiple files.");
|
|
|
|
Ok(1)
|
|
}
|
|
}
|
|
Some((CMD_GEN_STUB_LIB, matches)) => {
|
|
let input_path = matches.get_one::<PathBuf>(ROC_FILE).unwrap();
|
|
let target = matches
|
|
.get_one::<String>(FLAG_TARGET)
|
|
.and_then(|s| Target::from_str(s).ok())
|
|
.unwrap_or_default();
|
|
roc_linker::generate_stub_lib(
|
|
input_path,
|
|
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
|
&target.to_triple(),
|
|
)
|
|
}
|
|
Some((CMD_BUILD, matches)) => {
|
|
let target = matches
|
|
.get_one::<String>(FLAG_TARGET)
|
|
.and_then(|s| Target::from_str(s).ok())
|
|
.unwrap_or_default();
|
|
let link_type = match (matches.get_flag(FLAG_LIB), matches.get_flag(FLAG_NO_LINK)) {
|
|
(true, false) => LinkType::Dylib,
|
|
(true, true) => user_error!("build can only be one of `--lib` or `--no-link`"),
|
|
(false, true) => LinkType::None,
|
|
(false, false) => LinkType::Executable,
|
|
};
|
|
|
|
Ok(build(
|
|
matches,
|
|
&subcommands,
|
|
BuildConfig::BuildOnly,
|
|
target.to_triple(),
|
|
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
|
link_type,
|
|
)?)
|
|
}
|
|
Some((CMD_CHECK, matches)) => {
|
|
let arena = bumpalo::Bump::new();
|
|
|
|
let emit_timings = matches.get_flag(FLAG_TIME);
|
|
let roc_file_path = matches.get_one::<PathBuf>(ROC_FILE).unwrap();
|
|
let threading = match matches.get_one::<usize>(roc_cli::FLAG_MAX_THREADS) {
|
|
None => Threading::AllAvailable,
|
|
Some(0) => user_error!("cannot build with at most 0 threads"),
|
|
Some(1) => Threading::Single,
|
|
Some(n) => Threading::AtMost(*n),
|
|
};
|
|
|
|
match check_file(
|
|
&arena,
|
|
roc_file_path.to_owned(),
|
|
emit_timings,
|
|
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
|
|
threading,
|
|
) {
|
|
Ok((problems, total_time)) => {
|
|
println!(
|
|
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.",
|
|
if problems.errors == 0 {
|
|
32 // green
|
|
} else {
|
|
33 // yellow
|
|
},
|
|
problems.errors,
|
|
if problems.errors == 1 {
|
|
"error"
|
|
} else {
|
|
"errors"
|
|
},
|
|
if problems.warnings == 0 {
|
|
32 // green
|
|
} else {
|
|
33 // yellow
|
|
},
|
|
problems.warnings,
|
|
if problems.warnings == 1 {
|
|
"warning"
|
|
} else {
|
|
"warnings"
|
|
},
|
|
total_time.as_millis(),
|
|
);
|
|
|
|
Ok(problems.exit_code())
|
|
}
|
|
|
|
Err(LoadingProblem::FormattedReport(report)) => {
|
|
print!("{}", report);
|
|
|
|
Ok(1)
|
|
}
|
|
Err(other) => {
|
|
panic!("build_file failed with error:\n{:?}", other);
|
|
}
|
|
}
|
|
}
|
|
Some((CMD_REPL, _)) => Ok(roc_repl_cli::main()),
|
|
Some((CMD_EDIT, matches)) => {
|
|
match matches
|
|
.get_many::<OsString>(DIRECTORY_OR_FILES)
|
|
.map(|mut values| values.next())
|
|
{
|
|
Some(Some(os_string)) => {
|
|
launch_editor(Some(Path::new(os_string)))?;
|
|
}
|
|
_ => {
|
|
launch_editor(None)?;
|
|
}
|
|
}
|
|
|
|
// Exit 0 if the editor exited normally
|
|
Ok(0)
|
|
}
|
|
Some((CMD_DOCS, matches)) => {
|
|
let root_path = matches.get_one::<PathBuf>(ROC_FILE).unwrap();
|
|
|
|
generate_docs_html(root_path.to_owned());
|
|
|
|
Ok(0)
|
|
}
|
|
Some((CMD_FORMAT, matches)) => {
|
|
let maybe_values = matches.get_many::<OsString>(DIRECTORY_OR_FILES);
|
|
|
|
let mut values: Vec<OsString> = Vec::new();
|
|
|
|
match maybe_values {
|
|
None => {
|
|
let mut os_string_values: Vec<OsString> = Vec::new();
|
|
read_all_roc_files(
|
|
&std::env::current_dir()?.as_os_str().to_os_string(),
|
|
&mut os_string_values,
|
|
)?;
|
|
for os_string in os_string_values {
|
|
values.push(os_string);
|
|
}
|
|
}
|
|
Some(os_values) => {
|
|
for os_string in os_values {
|
|
values.push(os_string.to_owned());
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut roc_files = Vec::new();
|
|
|
|
// Populate roc_files
|
|
for os_str in values {
|
|
let metadata = fs::metadata(os_str.clone())?;
|
|
roc_files_recursive(os_str.as_os_str(), metadata.file_type(), &mut roc_files)?;
|
|
}
|
|
|
|
let format_mode = match matches.get_flag(FLAG_CHECK) {
|
|
true => FormatMode::CheckOnly,
|
|
false => FormatMode::Format,
|
|
};
|
|
|
|
let format_exit_code = match format(roc_files, format_mode) {
|
|
Ok(_) => 0,
|
|
Err(message) => {
|
|
eprintln!("{}", message);
|
|
1
|
|
}
|
|
};
|
|
|
|
Ok(format_exit_code)
|
|
}
|
|
Some((CMD_VERSION, _)) => {
|
|
print!(
|
|
"{}",
|
|
concatcp!("roc ", include_str!("../../../version.txt"))
|
|
);
|
|
|
|
Ok(0)
|
|
}
|
|
_ => unreachable!(),
|
|
}?;
|
|
|
|
std::process::exit(exit_code);
|
|
}
|
|
|
|
fn read_all_roc_files(
|
|
dir: &OsString,
|
|
roc_file_paths: &mut Vec<OsString>,
|
|
) -> Result<(), std::io::Error> {
|
|
let entries = fs::read_dir(dir)?;
|
|
|
|
for entry in entries {
|
|
let path = entry?.path();
|
|
|
|
if path.is_dir() {
|
|
read_all_roc_files(&path.into_os_string(), roc_file_paths)?;
|
|
} else if path.extension().and_then(OsStr::to_str) == Some("roc") {
|
|
let file_path = path.into_os_string();
|
|
roc_file_paths.push(file_path);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn roc_files_recursive<P: AsRef<Path>>(
|
|
path: P,
|
|
file_type: FileType,
|
|
roc_files: &mut Vec<PathBuf>,
|
|
) -> io::Result<()> {
|
|
if file_type.is_dir() {
|
|
for entry_res in fs::read_dir(path)? {
|
|
let entry = entry_res?;
|
|
|
|
roc_files_recursive(entry.path(), entry.file_type()?, roc_files)?;
|
|
}
|
|
} else {
|
|
roc_files.push(path.as_ref().to_path_buf());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "editor")]
|
|
fn launch_editor(project_dir_path: Option<&Path>) -> io::Result<()> {
|
|
roc_editor::launch(project_dir_path)
|
|
}
|
|
|
|
#[cfg(not(feature = "editor"))]
|
|
fn launch_editor(_project_dir_path: Option<&Path>) -> io::Result<()> {
|
|
panic!("Cannot launch the editor because this build of roc did not include `feature = \"editor\"`!");
|
|
}
|