Add --output to build, default to app filename

This commit is contained in:
Richard Feldman 2023-10-19 18:12:15 -04:00
parent a96752a65a
commit eebec8a378
No known key found for this signature in database
GPG Key ID: F1F21AA5B1D9E43B
7 changed files with 91 additions and 57 deletions

View File

@ -150,6 +150,12 @@ pub fn build_app() -> Command {
.args_conflicts_with_subcommands(true)
.subcommand(Command::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it")
.arg(Arg::new(FLAG_OUTPUT)
.long(FLAG_OUTPUT)
.help("The full path to the output binary, including filename. To specify directory only, specify a path that ends in a directory separator (e.g. a slash).")
.value_parser(value_parser!(OsString))
.required(false)
)
.arg(flag_optimize.clone())
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone())
@ -537,6 +543,7 @@ pub fn build(
subcommands: &[String],
config: BuildConfig,
triple: Triple,
out_path: Option<&Path>,
roc_cache_dir: RocCacheDir<'_>,
link_type: LinkType,
) -> io::Result<i32> {
@ -725,6 +732,7 @@ pub fn build(
wasm_dev_stack_bytes,
roc_cache_dir,
load_config,
out_path,
);
match res_binary_path {

View File

@ -48,6 +48,7 @@ fn main() -> io::Result<()> {
&subcommands,
BuildConfig::BuildAndRunIfNoErrors,
Triple::host(),
None,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
LinkType::Executable,
)
@ -62,6 +63,7 @@ fn main() -> io::Result<()> {
&subcommands,
BuildConfig::BuildAndRun,
Triple::host(),
None,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
LinkType::Executable,
)
@ -87,6 +89,7 @@ fn main() -> io::Result<()> {
&subcommands,
BuildConfig::BuildAndRunIfNoErrors,
Triple::host(),
None,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
LinkType::Executable,
)
@ -140,12 +143,16 @@ fn main() -> io::Result<()> {
(false, true) => LinkType::None,
(false, false) => LinkType::Executable,
};
let out_path = matches
.get_one::<OsString>(FLAG_OUTPUT)
.map(OsString::as_ref);
Ok(build(
matches,
&subcommands,
BuildConfig::BuildOnly,
target.to_triple(),
out_path,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
link_type,
)?)

View File

@ -17,7 +17,7 @@ use roc_reporting::{
cli::{report_problems, Problems},
report::{RenderTarget, DEFAULT_PALETTE},
};
use roc_target::TargetInfo;
use roc_target::{OperatingSystem, TargetInfo};
use std::ffi::OsStr;
use std::ops::Deref;
use std::{
@ -773,6 +773,7 @@ pub fn build_file<'a>(
wasm_dev_stack_bytes: Option<u32>,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
out_path: Option<&Path>,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let compilation_start = Instant::now();
@ -793,6 +794,7 @@ pub fn build_file<'a>(
wasm_dev_stack_bytes,
loaded,
compilation_start,
out_path,
)
}
@ -809,6 +811,7 @@ fn build_loaded_file<'a>(
wasm_dev_stack_bytes: Option<u32>,
loaded: roc_load::MonomorphizedModule<'a>,
compilation_start: Instant,
out_path: Option<&Path>,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let operating_system = roc_target::OperatingSystem::from(target.operating_system);
@ -834,12 +837,40 @@ fn build_loaded_file<'a>(
// even if the --prebuilt-platform CLI flag wasn't set.
let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
let cwd = app_module_path.parent().unwrap();
let mut output_exe_path = cwd.join(&*loaded.output_path);
let mut output_exe_path = match out_path {
Some(path) => {
// true iff the path ends with a directory separator,
// e.g. '/' on UNIX, '/' or '\\' on Windows
let ends_with_sep = {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
if let Some(extension) = operating_system.executable_file_ext() {
output_exe_path.set_extension(extension);
}
path.as_os_str().as_bytes().ends_with(&[b'/'])
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
let last = path.as_os_str().encode_wide().last();
last == Some('/') || last == Some('\\');
}
};
// If you specified a path that ends in in a directory separator, then
// use that directory, but use the app module's filename for the filename.
if ends_with_sep {
let filename = app_module_path.file_name().unwrap_or_default();
with_executable_extension(&path.join(filename), operating_system)
} else {
path.to_path_buf()
}
}
None => with_executable_extension(&app_module_path, operating_system),
};
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
@ -1322,5 +1353,10 @@ pub fn build_str_test<'a>(
wasm_dev_stack_bytes,
loaded,
compilation_start,
None,
)
}
fn with_executable_extension(path: &Path, os: OperatingSystem) -> PathBuf {
path.with_extension(os.executable_file_ext().unwrap_or_default())
}

View File

@ -69,7 +69,6 @@ use roc_types::subs::{CopiedImport, ExposedTypesStorageSubs, Subs, VarStore, Var
use roc_types::types::{Alias, Types};
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::HashMap;
use std::env::current_dir;
use std::io;
use std::iter;
use std::ops::ControlFlow;
@ -91,9 +90,6 @@ use crate::wasm_instant::{Duration, Instant};
#[cfg(not(target_family = "wasm"))]
use std::time::{Duration, Instant};
/// Default name for the binary generated for an app, if an invalid one was specified.
const DEFAULT_APP_OUTPUT_PATH: &str = "app";
/// Filename extension for normal Roc modules
const ROC_FILE_EXTENSION: &str = "roc";
@ -697,7 +693,6 @@ struct State<'a> {
pub opt_platform_shorthand: Option<&'a str>,
pub platform_data: Option<PlatformData<'a>>,
pub exposed_types: ExposedByModule,
pub output_path: Option<&'a str>,
pub platform_path: PlatformPath<'a>,
pub target_info: TargetInfo,
pub(self) function_kind: FunctionKind,
@ -785,7 +780,6 @@ impl<'a> State<'a> {
target_info,
function_kind,
platform_data: None,
output_path: None,
platform_path: PlatformPath::NotSpecified,
module_cache: ModuleCache::default(),
dependencies,
@ -2286,7 +2280,6 @@ fn update<'a>(
match header.header_type {
App { to_platform, .. } => {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::Valid(to_platform);
}
Package {
@ -2469,24 +2462,9 @@ fn update<'a>(
.sources
.insert(parsed.module_id, (parsed.module_path.clone(), parsed.src));
// If this was an app module, set the output path to be
// the module's declared "name".
//
// e.g. for `app "blah"` we should generate an output file named "blah"
if let HeaderType::App { output_name, .. } = &parsed.header_type {
match output_name {
StrLiteral::PlainLine(path) => {
state.output_path = Some(path);
}
_ => {
todo!("TODO gracefully handle a malformed string literal after `app` keyword.");
}
}
}
let module_id = parsed.module_id;
state.module_cache.parsed.insert(parsed.module_id, parsed);
state.module_cache.parsed.insert(module_id, parsed);
let work = state.dependencies.notify(module_id, Phase::Parse);
@ -3229,7 +3207,6 @@ fn finish_specialization<'a>(
procedures,
host_exposed_lambda_sets,
module_cache,
output_path,
platform_data,
..
} = state;
@ -3247,12 +3224,6 @@ fn finish_specialization<'a>(
.collect();
let module_id = state.root_id;
let output_path = match output_path {
Some(path_str) => Path::new(path_str).into(),
None => current_dir().unwrap().join(DEFAULT_APP_OUTPUT_PATH).into(),
};
let uses_prebuilt_platform = match platform_data {
Some(data) => data.is_prebuilt,
// If there's no platform data (e.g. because we're building an interface module)
@ -3263,7 +3234,6 @@ fn finish_specialization<'a>(
Ok(MonomorphizedModule {
can_problems,
type_problems,
output_path,
expectations: module_expectations,
exposed_to_host,
module_id,

View File

@ -20,7 +20,6 @@ use roc_solve::module::Solved;
use roc_solve_problem::TypeError;
use roc_types::subs::{ExposedTypesStorageSubs, Subs, VarStore, Variable};
use roc_types::types::{Alias, Types};
use std::path::Path;
use std::path::PathBuf;
#[cfg(target_family = "wasm")]
@ -163,7 +162,6 @@ pub struct MonomorphizedModule<'a> {
pub interns: Interns,
pub subs: Subs,
pub layout_interner: STLayoutInterner<'a>,
pub output_path: Box<Path>,
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,

View File

@ -13,8 +13,8 @@ roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" }
roc_collections = { path = "../compiler/collections" }
roc_error_macros = { path = "../error_macros" }
roc_gen_llvm= { path = "../compiler/gen_llvm" }
roc_linker = { path = "../linker"}
roc_gen_llvm = { path = "../compiler/gen_llvm" }
roc_linker = { path = "../linker" }
roc_load = { path = "../compiler/load" }
roc_module = { path = "../compiler/module" }
roc_mono = { path = "../compiler/mono" }
@ -34,6 +34,7 @@ libloading.workspace = true
strum.workspace = true
strum_macros.workspace = true
target-lexicon.workspace = true
tempfile.workspace = true
[dev-dependencies]
cli_utils = { path = "../cli_utils" }
@ -41,4 +42,3 @@ dircpy.workspace = true
indoc.workspace = true
pretty_assertions.workspace = true
tempfile.workspace = true

View File

@ -71,21 +71,30 @@ pub fn generate(
LinkingStrategy::Legacy
};
let res_binary_path = build_file(
&arena,
&triple,
spec_path.to_path_buf(),
code_gen_options,
false,
link_type,
linking_strategy,
true,
None,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
load_config,
);
let tempdir_res = tempfile::tempdir();
match res_binary_path {
let res_binary_path = match tempdir_res {
Ok(dylib_dir) => build_file(
&arena,
&triple,
spec_path.to_path_buf(),
code_gen_options,
false,
link_type,
linking_strategy,
true,
None,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
load_config,
Some(dylib_dir.path()),
),
Err(_) => {
eprintln!("`roc glue` was unable to create a tempdir.");
std::process::exit(1);
}
};
let answer = match res_binary_path {
Ok(BuiltFile {
binary_path,
problems,
@ -196,7 +205,13 @@ pub fn generate(
handle_error_module(module, total_time, spec_path.as_os_str(), true)
}
Err(BuildFileError::LoadingProblem(problem)) => handle_loading_problem(problem),
}
};
// Extend the lifetime of the tempdir to after we're done with everything,
// so it doesn't get dropped before we're done reading from it!
let _ = tempdir_res;
answer
}
Err(err) => match err.kind() {
ErrorKind::NotFound => {