From e334dcac243d5ef3f22a69b5853d948f2161fd6d Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 14:49:49 +0100 Subject: [PATCH 01/16] feat: deno compile --- cli/flags.rs | 35 ++++++++++++++ cli/main.rs | 101 +++++++++++++++++++++++++++++++++++++++- cli/standalone.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++ cli/worker.rs | 12 +++++ 4 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 cli/standalone.rs diff --git a/cli/flags.rs b/cli/flags.rs index 5219471f21968f..d6c5eae9c8c0ca 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -22,6 +22,10 @@ pub enum DenoSubcommand { source_file: String, out_file: Option, }, + Compile { + source_file: String, + out_file: String, + }, Completions { buf: Box<[u8]>, }, @@ -291,6 +295,8 @@ pub fn flags_from_vec_safe(args: Vec) -> clap::Result { doc_parse(&mut flags, m); } else if let Some(m) = matches.subcommand_matches("lint") { lint_parse(&mut flags, m); + } else if let Some(m) = matches.subcommand_matches("compile") { + compile_parse(&mut flags, m); } else { repl_parse(&mut flags, &matches); } @@ -340,6 +346,7 @@ If the flag is set, restrict these messages to errors.", ) .subcommand(bundle_subcommand()) .subcommand(cache_subcommand()) + .subcommand(compile_subcommand()) .subcommand(completions_subcommand()) .subcommand(doc_subcommand()) .subcommand(eval_subcommand()) @@ -407,6 +414,18 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) { }; } +fn compile_parse(flags: &mut Flags, matches: &clap::ArgMatches) { + compile_args_parse(flags, matches); + + let source_file = matches.value_of("source_file").unwrap().to_string(); + let out_file = matches.value_of("out_file").unwrap().to_string(); + + flags.subcommand = DenoSubcommand::Compile { + source_file, + out_file, + }; +} + fn bundle_parse(flags: &mut Flags, matches: &clap::ArgMatches) { compile_args_parse(flags, matches); @@ -789,6 +808,22 @@ The installation root is determined, in order of precedence: These must be added to the path manually if required.") } +fn compile_subcommand<'a, 'b>() -> App<'a, 'b> { + compile_args(SubCommand::with_name("compile")) + .arg( + Arg::with_name("source_file") + .takes_value(true) + .required(true), + ) + .arg(Arg::with_name("out_file").takes_value(true).required(true)) + .about("Compile the script into a self contained executable") + .long_about( + "Compiles the given script into a self contained executable. + deno compile https://deno.land/std/http/file_server.ts file_server + deno compile https://deno.land/std/examples/colors.ts colors", + ) +} + fn bundle_subcommand<'a, 'b>() -> App<'a, 'b> { compile_args(SubCommand::with_name("bundle")) .arg( diff --git a/cli/main.rs b/cli/main.rs index de2e1b40251ee8..2ef5fc51f3f793 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -39,6 +39,7 @@ mod resolve_addr; mod signal; mod source_maps; mod specifier_handler; +mod standalone; mod text_encoding; mod tokio_util; mod tools; @@ -149,6 +150,99 @@ fn get_types(unstable: bool) -> String { types } +async fn compile_command( + flags: Flags, + source_file: String, + out_file: String, +) -> Result<(), AnyError> { + let debug = flags.log_level == Some(log::Level::Debug); + + let module_specifier = ModuleSpecifier::resolve_url_or_path(&source_file)?; + let program_state = ProgramState::new(flags.clone())?; + + info!( + "{} {}", + colors::green("Bundle"), + module_specifier.to_string() + ); + + let handler = Rc::new(RefCell::new(FetchHandler::new( + &program_state, + // when bundling, dynamic imports are only access for their type safety, + // therefore we will allow the graph to access any module. + Permissions::allow_all(), + )?)); + let mut builder = module_graph::GraphBuilder::new( + handler, + program_state.maybe_import_map.clone(), + program_state.lockfile.clone(), + ); + builder.add(&module_specifier, false).await?; + let module_graph = builder.get_graph(); + + if !flags.no_check { + // TODO(@kitsonk) support bundling for workers + let lib = if flags.unstable { + module_graph::TypeLib::UnstableDenoWindow + } else { + module_graph::TypeLib::DenoWindow + }; + let result_info = + module_graph.clone().check(module_graph::CheckOptions { + debug, + emit: false, + lib, + maybe_config_path: flags.config_path.clone(), + reload: flags.reload, + })?; + + debug!("{}", result_info.stats); + if let Some(ignored_options) = result_info.maybe_ignored_options { + eprintln!("{}", ignored_options); + } + if !result_info.diagnostics.is_empty() { + return Err(generic_error(result_info.diagnostics.to_string())); + } + } + + let (bundle_str, stats, maybe_ignored_options) = + module_graph.bundle(module_graph::BundleOptions { + debug, + maybe_config_path: flags.config_path, + })?; + + match maybe_ignored_options { + Some(ignored_options) if flags.no_check => { + eprintln!("{}", ignored_options); + } + _ => {} + } + debug!("{}", stats); + + info!( + "{} {}", + colors::green("Compile"), + module_specifier.to_string() + ); + let original_binary_path = std::env::current_exe()?; + let mut original_bin = tokio::fs::read(original_binary_path).await?; + + let mut bundle = bundle_str.as_bytes().to_vec(); + + let mut magic_trailer = b"DENO".to_vec(); + magic_trailer.write_all(&original_bin.len().to_be_bytes())?; + + let mut final_bin = + Vec::with_capacity(original_bin.len() + bundle.len() + 12); + final_bin.append(&mut original_bin); + final_bin.append(&mut bundle); + final_bin.append(&mut magic_trailer); + + tokio::fs::write(out_file, final_bin).await?; + + Ok(()) +} + async fn info_command( flags: Flags, maybe_specifier: Option, @@ -898,6 +992,10 @@ fn get_subcommand( DenoSubcommand::Cache { files } => { cache_command(flags, files).boxed_local() } + DenoSubcommand::Compile { + source_file, + out_file, + } => compile_command(flags, source_file, out_file).boxed_local(), DenoSubcommand::Fmt { check, files, @@ -967,8 +1065,9 @@ pub fn main() { colors::enable_ansi(); // For Windows 10 let args: Vec = env::args().collect(); - let flags = flags::flags_from_vec(args); + standalone::standalone(); + let flags = flags::flags_from_vec(args); if let Some(ref v8_flags) = flags.v8_flags { init_v8_flags(v8_flags); } diff --git a/cli/standalone.rs b/cli/standalone.rs new file mode 100644 index 00000000000000..0e1d4d19b00f2d --- /dev/null +++ b/cli/standalone.rs @@ -0,0 +1,114 @@ +use crate::colors; +use crate::flags::Flags; +use crate::permissions::Permissions; +use crate::program_state::ProgramState; +use crate::tokio_util; +use crate::worker::MainWorker; +use deno_core::error::AnyError; +use deno_core::futures::FutureExt; +use deno_core::ModuleLoader; +use deno_core::ModuleSpecifier; +use deno_core::OpState; +use std::cell::RefCell; +use std::convert::TryInto; +use std::env::current_exe; +use std::fs::File; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; +use std::pin::Pin; +use std::rc::Rc; + +pub fn standalone() { + let current_exe_path = + current_exe().expect("expect current exe path to be known"); + + let mut current_exe = File::open(current_exe_path) + .expect("expected to be able to open current exe"); + let magic_trailer_pos = current_exe + .seek(SeekFrom::End(-12)) + .expect("expected to be able to seek to magic trailer in current exe"); + let mut magic_trailer = [0; 12]; + current_exe + .read_exact(&mut magic_trailer) + .expect("expected to be able to read magic trailer from current exe"); + let (magic_trailer, bundle_pos) = magic_trailer.split_at(4); + if magic_trailer == b"DENO" { + let bundle_pos_arr: &[u8; 8] = + bundle_pos.try_into().expect("slice with incorrect length"); + let bundle_pos = u64::from_be_bytes(*bundle_pos_arr); + current_exe + .seek(SeekFrom::Start(bundle_pos)) + .expect("expected to be able to seek to bundle pos in current exe"); + + let bundle_len = magic_trailer_pos - bundle_pos; + let mut bundle = String::new(); + current_exe + .take(bundle_len) + .read_to_string(&mut bundle) + .expect("expected to be able to read bundle from current exe"); + // TODO: check amount of bytes read + + let result = tokio_util::run_basic(run(bundle)); + if let Err(err) = result { + eprintln!("{}: {}", colors::red_bold("error"), err.to_string()); + std::process::exit(1); + } + std::process::exit(0); + } +} + +const SPECIFIER: &str = "file://$deno$/bundle.js"; + +struct EmbeddedModuleLoader(String); + +impl ModuleLoader for EmbeddedModuleLoader { + fn resolve( + &self, + _op_state: Rc>, + specifier: &str, + _referrer: &str, + _is_main: bool, + ) -> Result { + assert_eq!(specifier, SPECIFIER); + Ok(ModuleSpecifier::resolve_url(specifier)?) + } + + fn load( + &self, + _op_state: Rc>, + module_specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dynamic: bool, + ) -> Pin> { + let module_specifier = module_specifier.clone(); + let code = self.0.to_string(); + async move { + Ok(deno_core::ModuleSource { + code, + module_url_specified: module_specifier.to_string(), + module_url_found: module_specifier.to_string(), + }) + } + .boxed_local() + } +} + +async fn run(source_code: String) -> Result<(), AnyError> { + let flags = Flags::default(); + let main_module = ModuleSpecifier::resolve_url(SPECIFIER)?; + let program_state = ProgramState::new(flags.clone())?; + let permissions = Permissions::allow_all(); + let module_loader = Rc::new(EmbeddedModuleLoader(source_code)); + let mut worker = MainWorker::from_options( + &program_state, + main_module.clone(), + permissions, + module_loader, + ); + worker.execute_module(&main_module).await?; + worker.execute("window.dispatchEvent(new Event('load'))")?; + worker.run_event_loop().await?; + worker.execute("window.dispatchEvent(new Event('unload'))")?; + Ok(()) +} diff --git a/cli/worker.rs b/cli/worker.rs index f4a919df6aadc6..68f0a2210e570e 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -17,9 +17,11 @@ use deno_core::futures::future::FutureExt; use deno_core::url::Url; use deno_core::JsRuntime; use deno_core::ModuleId; +use deno_core::ModuleLoader; use deno_core::ModuleSpecifier; use deno_core::RuntimeOptions; use std::env; +use std::rc::Rc; use std::sync::Arc; use std::task::Context; use std::task::Poll; @@ -45,6 +47,16 @@ impl MainWorker { ) -> Self { let module_loader = CliModuleLoader::new(program_state.maybe_import_map.clone()); + + Self::from_options(program_state, main_module, permissions, module_loader) + } + + pub fn from_options( + program_state: &Arc, + main_module: ModuleSpecifier, + permissions: Permissions, + module_loader: Rc, + ) -> Self { let global_state_ = program_state.clone(); let js_error_create_fn = Box::new(move |core_js_error| { From 405ab3496e50eb4401563c4b27700960fc525c89 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 14:59:20 +0100 Subject: [PATCH 02/16] upload in ci --- .github/workflows/ci.yml | 12 +++++++++++- cli/main.rs | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77b027ae5c4c11..13277234f9dbb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -164,7 +164,17 @@ jobs: - name: Test release if: matrix.kind == 'test_release' - run: cargo test --release --locked --all-targets + run: | + cargo test --release --locked --all-targets + # TODO(lucacasonato): remove before landing + ./target/release/deno compile https://deno.land/std/http/file_server.ts file_server + + - uses: actions/upload-artifact@v2 + with: + name: 'file_server_${{ runner.os }}' + path: | + file_server + file_server.exe - name: Test debug if: matrix.kind == 'test_debug' diff --git a/cli/main.rs b/cli/main.rs index 2ef5fc51f3f793..e7b64642ee2c03 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -238,6 +238,11 @@ async fn compile_command( final_bin.append(&mut bundle); final_bin.append(&mut magic_trailer); + let out_file = if cfg!(windows) && !out_file.ends_with(".exe") { + format!("{}.exe", out_file) + } else { + out_file + }; tokio::fs::write(out_file, final_bin).await?; Ok(()) From 469e0badb0247d62f63f3972bda331f51814ac62 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 15:02:07 +0100 Subject: [PATCH 03/16] x From 5f187b1b6179399987962010c8bf627b56fa2022 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 15:03:33 +0100 Subject: [PATCH 04/16] fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13277234f9dbb6..0db852cdc3633d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,11 +162,11 @@ jobs: if: matrix.kind == 'test_debug' run: cargo build --locked --all-targets + # TODO(lucacasonato): remove deno compile before landing - name: Test release if: matrix.kind == 'test_release' run: | cargo test --release --locked --all-targets - # TODO(lucacasonato): remove before landing ./target/release/deno compile https://deno.land/std/http/file_server.ts file_server - uses: actions/upload-artifact@v2 From 9b1df8488257ca1a934d58cd3971c047f8dcc330 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 15:45:11 +0100 Subject: [PATCH 05/16] add simple test --- .github/workflows/ci.yml | 12 +----------- cli/main.rs | 9 +++++++-- cli/tests/integration_tests.rs | 30 +++++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0db852cdc3633d..77b027ae5c4c11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,19 +162,9 @@ jobs: if: matrix.kind == 'test_debug' run: cargo build --locked --all-targets - # TODO(lucacasonato): remove deno compile before landing - name: Test release if: matrix.kind == 'test_release' - run: | - cargo test --release --locked --all-targets - ./target/release/deno compile https://deno.land/std/http/file_server.ts file_server - - - uses: actions/upload-artifact@v2 - with: - name: 'file_server_${{ runner.os }}' - path: | - file_server - file_server.exe + run: cargo test --release --locked --all-targets - name: Test debug if: matrix.kind == 'test_debug' diff --git a/cli/main.rs b/cli/main.rs index e7b64642ee2c03..a4672411221b48 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -243,8 +243,13 @@ async fn compile_command( } else { out_file }; - tokio::fs::write(out_file, final_bin).await?; - + tokio::fs::write(&out_file, final_bin).await?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let perms = std::fs::Permissions::from_mode(0o777); + tokio::fs::set_permissions(out_file, perms).await?; + } Ok(()) } diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 430bd53cd7b534..b221939e7d8c39 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -98,7 +98,6 @@ fn eval_p() { } #[test] - fn run_from_stdin() { let mut deno = util::deno_cmd() .current_dir(util::root_path()) @@ -4467,3 +4466,32 @@ fn fmt_ignore_unexplicit_files() { assert!(output.status.success()); assert_eq!(output.stderr, b"Checked 0 file\n"); } + +#[test] +fn deno_compile_test() { + let dir = TempDir::new().expect("tempdir fail"); + let exe = if cfg!(windows) { + dir.path().join("./welcome.exe") + } else { + dir.path().join("./welcome") + }; + let output = util::deno_cmd() + .current_dir(util::root_path()) + .arg("compile") + .arg("./std/examples/welcome.ts") + .arg(&exe) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = Command::new(exe) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, "Welcome to Deno 🦕\n".as_bytes()); +} From b88346fc357e38f43bad826bc376c0242f0c252d Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 17:08:11 +0100 Subject: [PATCH 06/16] add flag tests --- cli/flags.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/cli/flags.rs b/cli/flags.rs index d6c5eae9c8c0ca..14dee4321edb35 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -3179,4 +3179,49 @@ mod tests { } ); } + + #[test] + fn compile() { + let r = flags_from_vec_safe(svec![ + "deno", + "compile", + "https://deno.land/std/examples/colors.ts", + "colors" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Compile { + source_file: "https://deno.land/std/examples/colors.ts".to_string(), + out_file: "colors".to_string() + }, + ..Flags::default() + } + ); + } + + #[test] + fn compile_with_flags() { + #[rustfmt::skip] + let r = flags_from_vec_safe(svec!["deno", "compile", "--unstable", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "https://deno.land/std/examples/colors.ts", "colors"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Compile { + source_file: "https://deno.land/std/examples/colors.ts".to_string(), + out_file: "colors".to_string() + }, + unstable: true, + import_map_path: Some("import_map.json".to_string()), + no_remote: true, + config_path: Some("tsconfig.json".to_string()), + no_check: true, + reload: true, + lock: Some(PathBuf::from("lock.json")), + lock_write: true, + ca_file: Some("example.crt".to_string()), + ..Flags::default() + } + ); + } } From c01e714e060c2e58bd47269dede99eb9c5cc0e62 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 17:41:29 +0100 Subject: [PATCH 07/16] require --unstable --- cli/flags.rs | 4 ++-- cli/main.rs | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cli/flags.rs b/cli/flags.rs index 14dee4321edb35..e0d6c85343e4d3 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -819,8 +819,8 @@ fn compile_subcommand<'a, 'b>() -> App<'a, 'b> { .about("Compile the script into a self contained executable") .long_about( "Compiles the given script into a self contained executable. - deno compile https://deno.land/std/http/file_server.ts file_server - deno compile https://deno.land/std/examples/colors.ts colors", + deno compile --unstable https://deno.land/std/http/file_server.ts file_server + deno compile --unstable https://deno.land/std/examples/colors.ts colors", ) } diff --git a/cli/main.rs b/cli/main.rs index a4672411221b48..86093752826bb2 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -155,6 +155,10 @@ async fn compile_command( source_file: String, out_file: String, ) -> Result<(), AnyError> { + if !flags.unstable { + exit_unstable("compile"); + } + let debug = flags.log_level == Some(log::Level::Debug); let module_specifier = ModuleSpecifier::resolve_url_or_path(&source_file)?; From 545fb7725083238c695f87d90b20fe03c6957dd7 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 17:51:05 +0100 Subject: [PATCH 08/16] address some review comments --- cli/main.rs | 7 +++++-- cli/standalone.rs | 43 ++++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cli/main.rs b/cli/main.rs index 86093752826bb2..4aaa5bfec2155b 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -233,7 +233,7 @@ async fn compile_command( let mut bundle = bundle_str.as_bytes().to_vec(); - let mut magic_trailer = b"DENO".to_vec(); + let mut magic_trailer = b"D3N0".to_vec(); magic_trailer.write_all(&original_bin.len().to_be_bytes())?; let mut final_bin = @@ -1079,7 +1079,10 @@ pub fn main() { colors::enable_ansi(); // For Windows 10 let args: Vec = env::args().collect(); - standalone::standalone(); + if let Err(err) = standalone::try_run_standalone_binary() { + eprintln!("{}: {}", colors::red_bold("error"), err.to_string()); + std::process::exit(1); + } let flags = flags::flags_from_vec(args); if let Some(ref v8_flags) = flags.v8_flags { diff --git a/cli/standalone.rs b/cli/standalone.rs index 0e1d4d19b00f2d..f5dffc6a7a109e 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -19,42 +19,39 @@ use std::io::SeekFrom; use std::pin::Pin; use std::rc::Rc; -pub fn standalone() { - let current_exe_path = - current_exe().expect("expect current exe path to be known"); +const MAGIC_TRAILER: &'static [u8; 4] = b"D3N0"; - let mut current_exe = File::open(current_exe_path) - .expect("expected to be able to open current exe"); - let magic_trailer_pos = current_exe - .seek(SeekFrom::End(-12)) - .expect("expected to be able to seek to magic trailer in current exe"); +/// This function will try to run this binary as a standalone binary +/// produced by `deno compile`. It determines if this is a stanalone +/// binary by checking for the magic trailer string `D3N0` at EOF-12. +/// After the magic trailer is a u64 pointer to the start of the JS +/// file embedded in the binary. This file is read, and run. If no +/// magic trailer is present, this function exits with Ok(()). +pub fn try_run_standalone_binary() -> Result<(), AnyError> { + let current_exe_path = current_exe()?; + + let mut current_exe = File::open(current_exe_path)?; + let magic_trailer_pos = current_exe.seek(SeekFrom::End(-12))?; let mut magic_trailer = [0; 12]; - current_exe - .read_exact(&mut magic_trailer) - .expect("expected to be able to read magic trailer from current exe"); + current_exe.read_exact(&mut magic_trailer)?; let (magic_trailer, bundle_pos) = magic_trailer.split_at(4); - if magic_trailer == b"DENO" { - let bundle_pos_arr: &[u8; 8] = - bundle_pos.try_into().expect("slice with incorrect length"); + if magic_trailer == MAGIC_TRAILER { + let bundle_pos_arr: &[u8; 8] = bundle_pos.try_into()?; let bundle_pos = u64::from_be_bytes(*bundle_pos_arr); - current_exe - .seek(SeekFrom::Start(bundle_pos)) - .expect("expected to be able to seek to bundle pos in current exe"); + current_exe.seek(SeekFrom::Start(bundle_pos))?; let bundle_len = magic_trailer_pos - bundle_pos; let mut bundle = String::new(); - current_exe - .take(bundle_len) - .read_to_string(&mut bundle) - .expect("expected to be able to read bundle from current exe"); + current_exe.take(bundle_len).read_to_string(&mut bundle)?; // TODO: check amount of bytes read - let result = tokio_util::run_basic(run(bundle)); - if let Err(err) = result { + if let Err(err) = tokio_util::run_basic(run(bundle)) { eprintln!("{}: {}", colors::red_bold("error"), err.to_string()); std::process::exit(1); } std::process::exit(0); + } else { + Ok(()) } } From 8c8743cb9fd57d256a96e0d11a49404bb835d832 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 17:58:16 +0100 Subject: [PATCH 09/16] more review comments --- cli/flags.rs | 4 +++- cli/main.rs | 2 +- cli/standalone.rs | 11 +++++++---- cli/tests/integration_tests.rs | 34 +++++++++++++++++++++++++++++++++- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/cli/flags.rs b/cli/flags.rs index e0d6c85343e4d3..c0d79a3399ae3f 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -820,7 +820,9 @@ fn compile_subcommand<'a, 'b>() -> App<'a, 'b> { .long_about( "Compiles the given script into a self contained executable. deno compile --unstable https://deno.land/std/http/file_server.ts file_server - deno compile --unstable https://deno.land/std/examples/colors.ts colors", + deno compile --unstable https://deno.land/std/examples/colors.ts colors + +Cross compiling binaries for different platforms is not currently possible.", ) } diff --git a/cli/main.rs b/cli/main.rs index 4aaa5bfec2155b..0611b8d329c112 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -1079,7 +1079,7 @@ pub fn main() { colors::enable_ansi(); // For Windows 10 let args: Vec = env::args().collect(); - if let Err(err) = standalone::try_run_standalone_binary() { + if let Err(err) = standalone::try_run_standalone_binary(args.clone()) { eprintln!("{}: {}", colors::red_bold("error"), err.to_string()); std::process::exit(1); } diff --git a/cli/standalone.rs b/cli/standalone.rs index f5dffc6a7a109e..d40bb1ff1b40b1 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -27,7 +27,7 @@ const MAGIC_TRAILER: &'static [u8; 4] = b"D3N0"; /// After the magic trailer is a u64 pointer to the start of the JS /// file embedded in the binary. This file is read, and run. If no /// magic trailer is present, this function exits with Ok(()). -pub fn try_run_standalone_binary() -> Result<(), AnyError> { +pub fn try_run_standalone_binary(args: Vec) -> Result<(), AnyError> { let current_exe_path = current_exe()?; let mut current_exe = File::open(current_exe_path)?; @@ -45,7 +45,7 @@ pub fn try_run_standalone_binary() -> Result<(), AnyError> { current_exe.take(bundle_len).read_to_string(&mut bundle)?; // TODO: check amount of bytes read - if let Err(err) = tokio_util::run_basic(run(bundle)) { + if let Err(err) = tokio_util::run_basic(run(bundle, args)) { eprintln!("{}: {}", colors::red_bold("error"), err.to_string()); std::process::exit(1); } @@ -91,8 +91,11 @@ impl ModuleLoader for EmbeddedModuleLoader { } } -async fn run(source_code: String) -> Result<(), AnyError> { - let flags = Flags::default(); +async fn run(source_code: String, args: Vec) -> Result<(), AnyError> { + let mut flags = Flags::default(); + flags.argv = args; + // TODO(lucacasonato): remove once you can specify this correctly through embedded metadata + flags.unstable = true; let main_module = ModuleSpecifier::resolve_url(SPECIFIER)?; let program_state = ProgramState::new(flags.clone())?; let permissions = Permissions::allow_all(); diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index b221939e7d8c39..196f2a6ac325b7 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -4468,7 +4468,7 @@ fn fmt_ignore_unexplicit_files() { } #[test] -fn deno_compile_test() { +fn compile() { let dir = TempDir::new().expect("tempdir fail"); let exe = if cfg!(windows) { dir.path().join("./welcome.exe") @@ -4495,3 +4495,35 @@ fn deno_compile_test() { assert!(output.status.success()); assert_eq!(output.stdout, "Welcome to Deno 🦕\n".as_bytes()); } + +#[test] +fn compile_args() { + let dir = TempDir::new().expect("tempdir fail"); + let exe = if cfg!(windows) { + dir.path().join("./args.exe") + } else { + dir.path().join("./args") + }; + let output = util::deno_cmd() + .current_dir(util::root_path()) + .arg("compile") + .arg("./cli/tests/028_args.ts") + .arg(&exe) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = Command::new(exe) + .arg("foo") + .arg("--bar") + .arg("--unstable") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, "foo\n--bar\n--unstable\n".as_bytes()); +} From 3d498b8337f264f217fc0e49b2ca1352d4121536 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 18:10:39 +0100 Subject: [PATCH 10/16] review comments and fix tests --- cli/standalone.rs | 14 ++++++++++++-- cli/tests/integration_tests.rs | 10 ++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/cli/standalone.rs b/cli/standalone.rs index d40bb1ff1b40b1..8072e87c5af080 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -4,6 +4,7 @@ use crate::permissions::Permissions; use crate::program_state::ProgramState; use crate::tokio_util; use crate::worker::MainWorker; +use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::futures::FutureExt; use deno_core::ModuleLoader; @@ -67,7 +68,11 @@ impl ModuleLoader for EmbeddedModuleLoader { _referrer: &str, _is_main: bool, ) -> Result { - assert_eq!(specifier, SPECIFIER); + if specifier != SPECIFIER { + return Err(type_error( + "Self-contained binaries don't support module loading", + )); + } Ok(ModuleSpecifier::resolve_url(specifier)?) } @@ -81,6 +86,11 @@ impl ModuleLoader for EmbeddedModuleLoader { let module_specifier = module_specifier.clone(); let code = self.0.to_string(); async move { + if module_specifier.to_string() != SPECIFIER { + return Err(type_error( + "Self-contained binaries don't support module loading", + )); + } Ok(deno_core::ModuleSource { code, module_url_specified: module_specifier.to_string(), @@ -93,7 +103,7 @@ impl ModuleLoader for EmbeddedModuleLoader { async fn run(source_code: String, args: Vec) -> Result<(), AnyError> { let mut flags = Flags::default(); - flags.argv = args; + flags.argv = args[1..].to_vec(); // TODO(lucacasonato): remove once you can specify this correctly through embedded metadata flags.unstable = true; let main_module = ModuleSpecifier::resolve_url(SPECIFIER)?; diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 196f2a6ac325b7..82e2993a3656bc 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -4471,13 +4471,14 @@ fn fmt_ignore_unexplicit_files() { fn compile() { let dir = TempDir::new().expect("tempdir fail"); let exe = if cfg!(windows) { - dir.path().join("./welcome.exe") + dir.path().join("welcome.exe") } else { - dir.path().join("./welcome") + dir.path().join("welcome") }; let output = util::deno_cmd() .current_dir(util::root_path()) .arg("compile") + .arg("--unstable") .arg("./std/examples/welcome.ts") .arg(&exe) .stdout(std::process::Stdio::piped()) @@ -4500,13 +4501,14 @@ fn compile() { fn compile_args() { let dir = TempDir::new().expect("tempdir fail"); let exe = if cfg!(windows) { - dir.path().join("./args.exe") + dir.path().join("args.exe") } else { - dir.path().join("./args") + dir.path().join("args") }; let output = util::deno_cmd() .current_dir(util::root_path()) .arg("compile") + .arg("--unstable") .arg("./cli/tests/028_args.ts") .arg(&exe) .stdout(std::process::Stdio::piped()) From 11167019f6c39d7fb334cd4158784e9ea63090df Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 18:31:25 +0100 Subject: [PATCH 11/16] review comments --- cli/main.rs | 213 ++++++++++++++++++---------------------------- cli/standalone.rs | 35 ++++++++ 2 files changed, 119 insertions(+), 129 deletions(-) diff --git a/cli/main.rs b/cli/main.rs index 0611b8d329c112..36c672f8515be1 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -56,6 +56,7 @@ use crate::media_type::MediaType; use crate::permissions::Permissions; use crate::program_state::ProgramState; use crate::specifier_handler::FetchHandler; +use crate::standalone::create_standalone_binary; use crate::worker::MainWorker; use deno_core::error::generic_error; use deno_core::error::AnyError; @@ -164,96 +165,27 @@ async fn compile_command( let module_specifier = ModuleSpecifier::resolve_url_or_path(&source_file)?; let program_state = ProgramState::new(flags.clone())?; + let module_graph = create_module_graph_and_maybe_check( + module_specifier.clone(), + program_state.clone(), + debug, + ) + .await?; + info!( "{} {}", colors::green("Bundle"), module_specifier.to_string() ); - - let handler = Rc::new(RefCell::new(FetchHandler::new( - &program_state, - // when bundling, dynamic imports are only access for their type safety, - // therefore we will allow the graph to access any module. - Permissions::allow_all(), - )?)); - let mut builder = module_graph::GraphBuilder::new( - handler, - program_state.maybe_import_map.clone(), - program_state.lockfile.clone(), - ); - builder.add(&module_specifier, false).await?; - let module_graph = builder.get_graph(); - - if !flags.no_check { - // TODO(@kitsonk) support bundling for workers - let lib = if flags.unstable { - module_graph::TypeLib::UnstableDenoWindow - } else { - module_graph::TypeLib::DenoWindow - }; - let result_info = - module_graph.clone().check(module_graph::CheckOptions { - debug, - emit: false, - lib, - maybe_config_path: flags.config_path.clone(), - reload: flags.reload, - })?; - - debug!("{}", result_info.stats); - if let Some(ignored_options) = result_info.maybe_ignored_options { - eprintln!("{}", ignored_options); - } - if !result_info.diagnostics.is_empty() { - return Err(generic_error(result_info.diagnostics.to_string())); - } - } - - let (bundle_str, stats, maybe_ignored_options) = - module_graph.bundle(module_graph::BundleOptions { - debug, - maybe_config_path: flags.config_path, - })?; - - match maybe_ignored_options { - Some(ignored_options) if flags.no_check => { - eprintln!("{}", ignored_options); - } - _ => {} - } - debug!("{}", stats); + let bundle_str = bundle_module_graph(module_graph, flags, debug)?; info!( "{} {}", colors::green("Compile"), module_specifier.to_string() ); - let original_binary_path = std::env::current_exe()?; - let mut original_bin = tokio::fs::read(original_binary_path).await?; - - let mut bundle = bundle_str.as_bytes().to_vec(); + create_standalone_binary(bundle_str.as_bytes().to_vec(), out_file).await?; - let mut magic_trailer = b"D3N0".to_vec(); - magic_trailer.write_all(&original_bin.len().to_be_bytes())?; - - let mut final_bin = - Vec::with_capacity(original_bin.len() + bundle.len() + 12); - final_bin.append(&mut original_bin); - final_bin.append(&mut bundle); - final_bin.append(&mut magic_trailer); - - let out_file = if cfg!(windows) && !out_file.ends_with(".exe") { - format!("{}.exe", out_file) - } else { - out_file - }; - tokio::fs::write(&out_file, final_bin).await?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let perms = std::fs::Permissions::from_mode(0o777); - tokio::fs::set_permissions(out_file, perms).await?; - } Ok(()) } @@ -407,6 +339,73 @@ async fn eval_command( Ok(()) } +async fn create_module_graph_and_maybe_check( + module_specifier: ModuleSpecifier, + program_state: Arc, + debug: bool, +) -> Result { + let handler = Rc::new(RefCell::new(FetchHandler::new( + &program_state, + // when bundling, dynamic imports are only access for their type safety, + // therefore we will allow the graph to access any module. + Permissions::allow_all(), + )?)); + let mut builder = module_graph::GraphBuilder::new( + handler, + program_state.maybe_import_map.clone(), + program_state.lockfile.clone(), + ); + builder.add(&module_specifier, false).await?; + let module_graph = builder.get_graph(); + + if !program_state.flags.no_check { + // TODO(@kitsonk) support bundling for workers + let lib = if program_state.flags.unstable { + module_graph::TypeLib::UnstableDenoWindow + } else { + module_graph::TypeLib::DenoWindow + }; + let result_info = + module_graph.clone().check(module_graph::CheckOptions { + debug, + emit: false, + lib, + maybe_config_path: program_state.flags.config_path.clone(), + reload: program_state.flags.reload, + })?; + + debug!("{}", result_info.stats); + if let Some(ignored_options) = result_info.maybe_ignored_options { + eprintln!("{}", ignored_options); + } + if !result_info.diagnostics.is_empty() { + return Err(generic_error(result_info.diagnostics.to_string())); + } + } + + Ok(module_graph) +} + +fn bundle_module_graph( + module_graph: module_graph::Graph, + flags: Flags, + debug: bool, +) -> Result { + let (bundle, stats, maybe_ignored_options) = + module_graph.bundle(module_graph::BundleOptions { + debug, + maybe_config_path: flags.config_path, + })?; + match maybe_ignored_options { + Some(ignored_options) if flags.no_check => { + eprintln!("{}", ignored_options); + } + _ => {} + } + debug!("{}", stats); + Ok(bundle) +} + async fn bundle_command( flags: Flags, source_file: String, @@ -431,44 +430,12 @@ async fn bundle_command( module_specifier.to_string() ); - let handler = Rc::new(RefCell::new(FetchHandler::new( - &program_state, - // when bundling, dynamic imports are only access for their type safety, - // therefore we will allow the graph to access any module. - Permissions::allow_all(), - )?)); - let mut builder = module_graph::GraphBuilder::new( - handler, - program_state.maybe_import_map.clone(), - program_state.lockfile.clone(), - ); - builder.add(&module_specifier, false).await?; - let module_graph = builder.get_graph(); - - if !flags.no_check { - // TODO(@kitsonk) support bundling for workers - let lib = if flags.unstable { - module_graph::TypeLib::UnstableDenoWindow - } else { - module_graph::TypeLib::DenoWindow - }; - let result_info = - module_graph.clone().check(module_graph::CheckOptions { - debug, - emit: false, - lib, - maybe_config_path: flags.config_path.clone(), - reload: flags.reload, - })?; - - debug!("{}", result_info.stats); - if let Some(ignored_options) = result_info.maybe_ignored_options { - eprintln!("{}", ignored_options); - } - if !result_info.diagnostics.is_empty() { - return Err(generic_error(result_info.diagnostics.to_string())); - } - } + let module_graph = create_module_graph_and_maybe_check( + module_specifier, + program_state.clone(), + debug, + ) + .await?; let mut paths_to_watch: Vec = module_graph .get_modules() @@ -500,19 +467,7 @@ async fn bundle_command( let flags = flags.clone(); let out_file = out_file.clone(); async move { - let (output, stats, maybe_ignored_options) = - module_graph.bundle(module_graph::BundleOptions { - debug, - maybe_config_path: flags.config_path, - })?; - - match maybe_ignored_options { - Some(ignored_options) if flags.no_check => { - eprintln!("{}", ignored_options); - } - _ => {} - } - debug!("{}", stats); + let output = bundle_module_graph(module_graph, flags, debug)?; debug!(">>>>> bundle END"); diff --git a/cli/standalone.rs b/cli/standalone.rs index 8072e87c5af080..66f38f3f3f926b 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -17,6 +17,7 @@ use std::fs::File; use std::io::Read; use std::io::Seek; use std::io::SeekFrom; +use std::io::Write; use std::pin::Pin; use std::rc::Rc; @@ -122,3 +123,37 @@ async fn run(source_code: String, args: Vec) -> Result<(), AnyError> { worker.execute("window.dispatchEvent(new Event('unload'))")?; Ok(()) } + +/// This functions creates a standalone deno binary by appending a bundle +/// and magic trailer to the currently executing binary. +pub async fn create_standalone_binary( + mut source_code: Vec, + out_file: String, +) -> Result<(), AnyError> { + let original_binary_path = std::env::current_exe()?; + let mut original_bin = tokio::fs::read(original_binary_path).await?; + + let mut magic_trailer = b"D3N0".to_vec(); + magic_trailer.write_all(&original_bin.len().to_be_bytes())?; + + let mut final_bin = + Vec::with_capacity(original_bin.len() + source_code.len() + 12); + final_bin.append(&mut original_bin); + final_bin.append(&mut source_code); + final_bin.append(&mut magic_trailer); + + let out_file = if cfg!(windows) && !out_file.ends_with(".exe") { + format!("{}.exe", out_file) + } else { + out_file + }; + tokio::fs::write(&out_file, final_bin).await?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let perms = std::fs::Permissions::from_mode(0o777); + tokio::fs::set_permissions(out_file, perms).await?; + } + + Ok(()) +} From 9720a354631b5062f00ad58c2502739a37d4913b Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 18:33:45 +0100 Subject: [PATCH 12/16] lint --- cli/standalone.rs | 4 ++-- cli/tests/integration_tests.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/standalone.rs b/cli/standalone.rs index 66f38f3f3f926b..38d4addd460710 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -21,7 +21,7 @@ use std::io::Write; use std::pin::Pin; use std::rc::Rc; -const MAGIC_TRAILER: &'static [u8; 4] = b"D3N0"; +const MAGIC_TRAILER: &[u8; 4] = b"D3N0"; /// This function will try to run this binary as a standalone binary /// produced by `deno compile`. It determines if this is a stanalone @@ -133,7 +133,7 @@ pub async fn create_standalone_binary( let original_binary_path = std::env::current_exe()?; let mut original_bin = tokio::fs::read(original_binary_path).await?; - let mut magic_trailer = b"D3N0".to_vec(); + let mut magic_trailer = MAGIC_TRAILER.to_vec(); magic_trailer.write_all(&original_bin.len().to_be_bytes())?; let mut final_bin = diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 82e2993a3656bc..d4f8e7f6926294 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -4527,5 +4527,5 @@ fn compile_args() { .wait_with_output() .unwrap(); assert!(output.status.success()); - assert_eq!(output.stdout, "foo\n--bar\n--unstable\n".as_bytes()); + assert_eq!(output.stdout, b"foo\n--bar\n--unstable\n"); } From 155e4e04fbaf1bca6bca0593b67bb4455be3ab25 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 18:42:14 +0100 Subject: [PATCH 13/16] add test for standalone import --- cli/tests/integration_tests.rs | 36 +++++++++++++++++++++++++++++++++- cli/tests/standalone_import.ts | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 cli/tests/standalone_import.ts diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index d4f8e7f6926294..f33a610bff8ecb 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -4498,7 +4498,7 @@ fn compile() { } #[test] -fn compile_args() { +fn standalone_args() { let dir = TempDir::new().expect("tempdir fail"); let exe = if cfg!(windows) { dir.path().join("args.exe") @@ -4529,3 +4529,37 @@ fn compile_args() { assert!(output.status.success()); assert_eq!(output.stdout, b"foo\n--bar\n--unstable\n"); } + +#[test] +fn standalone_no_module_load() { + let dir = TempDir::new().expect("tempdir fail"); + let exe = if cfg!(windows) { + dir.path().join("hello.exe") + } else { + dir.path().join("hello") + }; + let output = util::deno_cmd() + .current_dir(util::root_path()) + .arg("compile") + .arg("--unstable") + .arg("./cli/tests/standalone_import.ts") + .arg(&exe) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = Command::new(exe) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert_eq!(output.stdout, b"start\n"); + let stderr_str = String::from_utf8(output.stderr).unwrap(); + assert!(util::strip_ansi_codes(&stderr_str) + .contains("Self-contained binaries don't support module loading")); +} diff --git a/cli/tests/standalone_import.ts b/cli/tests/standalone_import.ts new file mode 100644 index 00000000000000..804102a535a7f3 --- /dev/null +++ b/cli/tests/standalone_import.ts @@ -0,0 +1,2 @@ +console.log("start"); +await import("./001_hello.js"); From f6fa74d5a1161e99dda01ad11b6bec030e8c77f5 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sun, 29 Nov 2020 21:53:26 +0100 Subject: [PATCH 14/16] use 8 byte magic trailer --- cli/standalone.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cli/standalone.rs b/cli/standalone.rs index 38d4addd460710..805849c81f989d 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -21,7 +21,7 @@ use std::io::Write; use std::pin::Pin; use std::rc::Rc; -const MAGIC_TRAILER: &[u8; 4] = b"D3N0"; +const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd"; /// This function will try to run this binary as a standalone binary /// produced by `deno compile`. It determines if this is a stanalone @@ -33,16 +33,16 @@ pub fn try_run_standalone_binary(args: Vec) -> Result<(), AnyError> { let current_exe_path = current_exe()?; let mut current_exe = File::open(current_exe_path)?; - let magic_trailer_pos = current_exe.seek(SeekFrom::End(-12))?; - let mut magic_trailer = [0; 12]; - current_exe.read_exact(&mut magic_trailer)?; - let (magic_trailer, bundle_pos) = magic_trailer.split_at(4); + let trailer_pos = current_exe.seek(SeekFrom::End(-16))?; + let mut trailer = [0; 16]; + current_exe.read_exact(&mut trailer)?; + let (magic_trailer, bundle_pos_arr) = trailer.split_at(8); if magic_trailer == MAGIC_TRAILER { - let bundle_pos_arr: &[u8; 8] = bundle_pos.try_into()?; + let bundle_pos_arr: &[u8; 8] = bundle_pos_arr.try_into()?; let bundle_pos = u64::from_be_bytes(*bundle_pos_arr); current_exe.seek(SeekFrom::Start(bundle_pos))?; - let bundle_len = magic_trailer_pos - bundle_pos; + let bundle_len = trailer_pos - bundle_pos; let mut bundle = String::new(); current_exe.take(bundle_len).read_to_string(&mut bundle)?; // TODO: check amount of bytes read @@ -133,14 +133,14 @@ pub async fn create_standalone_binary( let original_binary_path = std::env::current_exe()?; let mut original_bin = tokio::fs::read(original_binary_path).await?; - let mut magic_trailer = MAGIC_TRAILER.to_vec(); - magic_trailer.write_all(&original_bin.len().to_be_bytes())?; + let mut trailer = MAGIC_TRAILER.to_vec(); + trailer.write_all(&original_bin.len().to_be_bytes())?; let mut final_bin = - Vec::with_capacity(original_bin.len() + source_code.len() + 12); + Vec::with_capacity(original_bin.len() + source_code.len() + trailer.len()); final_bin.append(&mut original_bin); final_bin.append(&mut source_code); - final_bin.append(&mut magic_trailer); + final_bin.append(&mut trailer); let out_file = if cfg!(windows) && !out_file.ends_with(".exe") { format!("{}.exe", out_file) From 770c632861f7a030def32771dc04e4c455a8180a Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 30 Nov 2020 17:52:20 +0100 Subject: [PATCH 15/16] make out_file optional, default to infer from url --- cli/flags.rs | 25 ++++++++++++++++--------- cli/main.rs | 12 +++++++++++- cli/tests/integration_tests.rs | 1 - cli/tools/installer.rs | 2 +- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/cli/flags.rs b/cli/flags.rs index cf7e6a82fc0c34..9282decce5a00e 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -24,7 +24,7 @@ pub enum DenoSubcommand { }, Compile { source_file: String, - out_file: String, + out_file: Option, }, Completions { buf: Box<[u8]>, @@ -419,7 +419,7 @@ fn compile_parse(flags: &mut Flags, matches: &clap::ArgMatches) { compile_args_parse(flags, matches); let source_file = matches.value_of("source_file").unwrap().to_string(); - let out_file = matches.value_of("out_file").unwrap().to_string(); + let out_file = matches.value_of("out_file").map(|s| s.to_string()); flags.subcommand = DenoSubcommand::Compile { source_file, @@ -828,13 +828,21 @@ fn compile_subcommand<'a, 'b>() -> App<'a, 'b> { .takes_value(true) .required(true), ) - .arg(Arg::with_name("out_file").takes_value(true).required(true)) + .arg(Arg::with_name("out_file").takes_value(true)) .about("Compile the script into a self contained executable") .long_about( "Compiles the given script into a self contained executable. - deno compile --unstable https://deno.land/std/http/file_server.ts file_server - deno compile --unstable https://deno.land/std/examples/colors.ts colors + deno compile --unstable https://deno.land/std/http/file_server.ts + deno compile --unstable https://deno.land/std/examples/colors.ts color_util +The executable name is inferred by default: + - Attempt to take the file stem of the URL path. The above example would + become 'file_server'. + - If the file stem is something generic like 'main', 'mod', 'index' or 'cli', + and the path has no parent, take the file name of the parent path. Otherwise + settle with the generic name. + - If the resulting name has an '@...' suffix, strip it. + Cross compiling binaries for different platforms is not currently possible.", ) } @@ -3243,15 +3251,14 @@ mod tests { let r = flags_from_vec_safe(svec![ "deno", "compile", - "https://deno.land/std/examples/colors.ts", - "colors" + "https://deno.land/std/examples/colors.ts" ]); assert_eq!( r.unwrap(), Flags { subcommand: DenoSubcommand::Compile { source_file: "https://deno.land/std/examples/colors.ts".to_string(), - out_file: "colors".to_string() + out_file: None }, ..Flags::default() } @@ -3267,7 +3274,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Compile { source_file: "https://deno.land/std/examples/colors.ts".to_string(), - out_file: "colors".to_string() + out_file: Some("colors".to_string()) }, unstable: true, import_map_path: Some("import_map.json".to_string()), diff --git a/cli/main.rs b/cli/main.rs index 088eab2316fcd2..b340516f207044 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -83,6 +83,7 @@ use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; +use tools::installer::infer_name_from_url; fn write_to_stdout_ignore_sigpipe(bytes: &[u8]) -> Result<(), std::io::Error> { use std::io::ErrorKind; @@ -154,7 +155,7 @@ fn get_types(unstable: bool) -> String { async fn compile_command( flags: Flags, source_file: String, - out_file: String, + out_file: Option, ) -> Result<(), AnyError> { if !flags.unstable { exit_unstable("compile"); @@ -165,6 +166,15 @@ async fn compile_command( let module_specifier = ModuleSpecifier::resolve_url_or_path(&source_file)?; let program_state = ProgramState::new(flags.clone())?; + let out_file = + out_file.or_else(|| infer_name_from_url(module_specifier.as_url())); + let out_file = match out_file { + Some(out_file) => out_file, + None => return Err(generic_error( + "An executable name was not provided. One could not be inferred from the URL. Aborting.", + )), + }; + let module_graph = create_module_graph_and_maybe_check( module_specifier.clone(), program_state.clone(), diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 380fa0fda7ab1e..69102d52a591df 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -4509,7 +4509,6 @@ fn compile() { .arg("compile") .arg("--unstable") .arg("./std/examples/welcome.ts") - .arg(&exe) .stdout(std::process::Stdio::piped()) .spawn() .unwrap() diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index e0a99873a12d08..f2f5562c2ceb48 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -108,7 +108,7 @@ fn get_installer_root() -> Result { Ok(home_path) } -fn infer_name_from_url(url: &Url) -> Option { +pub fn infer_name_from_url(url: &Url) -> Option { let path = PathBuf::from(url.path()); let mut stem = match path.file_stem() { Some(stem) => stem.to_string_lossy().to_string(), From ecc9d428fde8789070a0c10dcd8cf374ef6359ce Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 30 Nov 2020 18:11:36 +0100 Subject: [PATCH 16/16] fix test --- cli/main.rs | 15 +++++++++------ cli/tests/integration_tests.rs | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cli/main.rs b/cli/main.rs index b340516f207044..b4e1f1d2c16e9b 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -52,11 +52,16 @@ mod worker; use crate::file_fetcher::File; use crate::file_fetcher::FileFetcher; use crate::file_watcher::ModuleResolutionResult; +use crate::flags::DenoSubcommand; +use crate::flags::Flags; +use crate::import_map::ImportMap; use crate::media_type::MediaType; use crate::permissions::Permissions; +use crate::program_state::exit_unstable; use crate::program_state::ProgramState; use crate::specifier_handler::FetchHandler; use crate::standalone::create_standalone_binary; +use crate::tools::installer::infer_name_from_url; use crate::worker::MainWorker; use deno_core::error::generic_error; use deno_core::error::AnyError; @@ -68,12 +73,8 @@ use deno_core::v8_set_flags; use deno_core::ModuleSpecifier; use deno_doc as doc; use deno_doc::parser::DocFileLoader; -use flags::DenoSubcommand; -use flags::Flags; -use import_map::ImportMap; use log::Level; use log::LevelFilter; -use program_state::exit_unstable; use std::cell::RefCell; use std::env; use std::io::Read; @@ -83,7 +84,6 @@ use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; -use tools::installer::infer_name_from_url; fn write_to_stdout_ignore_sigpipe(bytes: &[u8]) -> Result<(), std::io::Error> { use std::io::ErrorKind; @@ -194,7 +194,10 @@ async fn compile_command( colors::green("Compile"), module_specifier.to_string() ); - create_standalone_binary(bundle_str.as_bytes().to_vec(), out_file).await?; + create_standalone_binary(bundle_str.as_bytes().to_vec(), out_file.clone()) + .await?; + + info!("{} {}", colors::green("Emit"), out_file); Ok(()) } diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 69102d52a591df..380fa0fda7ab1e 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -4509,6 +4509,7 @@ fn compile() { .arg("compile") .arg("--unstable") .arg("./std/examples/welcome.ts") + .arg(&exe) .stdout(std::process::Stdio::piped()) .spawn() .unwrap()