diff --git a/cli/main.rs b/cli/main.rs index 5e088d8911..02ac5891cd 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -42,6 +42,7 @@ use deno_runtime::colors; use deno_runtime::fmt_errors::format_js_error; use deno_runtime::tokio_util::run_local; use std::env; +use std::env::current_exe; use std::path::PathBuf; async fn run_subcommand(flags: Flags) -> Result { @@ -245,8 +246,11 @@ pub fn main() { let args: Vec = env::args().collect(); let future = async move { + let current_exe_path = current_exe()?; let standalone_res = - match standalone::extract_standalone(args.clone()).await { + match standalone::extract_standalone(¤t_exe_path, args.clone()) + .await + { Ok(Some((metadata, eszip))) => standalone::run(eszip, metadata).await, Ok(None) => Ok(()), Err(err) => Err(err), diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs new file mode 100644 index 0000000000..bca0aff2b4 --- /dev/null +++ b/cli/standalone/binary.rs @@ -0,0 +1,307 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; +use std::io::Write; +use std::path::Path; +use std::sync::Arc; + +use deno_ast::ModuleSpecifier; +use deno_core::anyhow::Context; +use deno_core::error::AnyError; +use deno_core::futures::io::AllowStdIo; +use deno_core::futures::AsyncReadExt; +use deno_core::futures::AsyncSeekExt; +use deno_core::serde_json; +use deno_core::url::Url; +use deno_runtime::permissions::PermissionsOptions; +use log::Level; +use serde::Deserialize; +use serde::Serialize; + +use crate::args::CaData; +use crate::args::CliOptions; +use crate::args::CompileFlags; +use crate::cache::DenoDir; +use crate::file_fetcher::FileFetcher; +use crate::http_util::HttpClient; +use crate::util::progress_bar::ProgressBar; +use crate::util::progress_bar::ProgressBarStyle; + +const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd"; + +#[derive(Deserialize, Serialize)] +pub struct Metadata { + pub argv: Vec, + pub unstable: bool, + pub seed: Option, + pub permissions: PermissionsOptions, + pub location: Option, + pub v8_flags: Vec, + pub log_level: Option, + pub ca_stores: Option>, + pub ca_data: Option>, + pub unsafely_ignore_certificate_errors: Option>, + pub maybe_import_map: Option<(Url, String)>, + pub entrypoint: ModuleSpecifier, +} + +pub fn write_binary_bytes( + writer: &mut impl Write, + original_bin: Vec, + metadata: &Metadata, + eszip: eszip::EszipV2, +) -> Result<(), AnyError> { + let metadata = serde_json::to_string(metadata)?.as_bytes().to_vec(); + let eszip_archive = eszip.into_bytes(); + + let eszip_pos = original_bin.len(); + let metadata_pos = eszip_pos + eszip_archive.len(); + let mut trailer = MAGIC_TRAILER.to_vec(); + trailer.write_all(&eszip_pos.to_be_bytes())?; + trailer.write_all(&metadata_pos.to_be_bytes())?; + + writer.write_all(&original_bin)?; + writer.write_all(&eszip_archive)?; + writer.write_all(&metadata)?; + writer.write_all(&trailer)?; + + Ok(()) +} + +pub fn is_standalone_binary(exe_path: &Path) -> bool { + let Ok(mut output_file) = std::fs::File::open(exe_path) else { + return false; + }; + if output_file.seek(SeekFrom::End(-24)).is_err() { + // This seek may fail because the file is too small to possibly be + // `deno compile` output. + return false; + } + let mut trailer = [0; 24]; + if output_file.read_exact(&mut trailer).is_err() { + return false; + }; + let (magic_trailer, _) = trailer.split_at(8); + magic_trailer == MAGIC_TRAILER +} + +/// This function will try to run this binary as a standalone binary +/// produced by `deno compile`. It determines if this is a standalone +/// binary by checking for the magic trailer string `d3n0l4nd` at EOF-24 (8 bytes * 3). +/// The magic trailer is followed by: +/// - a u64 pointer to the JS bundle embedded in the binary +/// - a u64 pointer to JSON metadata (serialized flags) embedded in the binary +/// These are dereferenced, and the bundle is executed under the configuration +/// specified by the metadata. If no magic trailer is present, this function +/// exits with `Ok(None)`. +pub async fn extract_standalone( + exe_path: &Path, + cli_args: Vec, +) -> Result, AnyError> { + let file = std::fs::File::open(exe_path)?; + + let mut bufreader = + deno_core::futures::io::BufReader::new(AllowStdIo::new(file)); + + let trailer_pos = bufreader.seek(SeekFrom::End(-24)).await?; + let mut trailer = [0; 24]; + bufreader.read_exact(&mut trailer).await?; + let (magic_trailer, rest) = trailer.split_at(8); + if magic_trailer != MAGIC_TRAILER { + return Ok(None); + } + + let (eszip_archive_pos, rest) = rest.split_at(8); + let metadata_pos = rest; + let eszip_archive_pos = u64_from_bytes(eszip_archive_pos)?; + let metadata_pos = u64_from_bytes(metadata_pos)?; + let metadata_len = trailer_pos - metadata_pos; + + bufreader.seek(SeekFrom::Start(eszip_archive_pos)).await?; + + let (eszip, loader) = eszip::EszipV2::parse(bufreader) + .await + .context("Failed to parse eszip header")?; + + let mut bufreader = loader.await.context("Failed to parse eszip archive")?; + + bufreader.seek(SeekFrom::Start(metadata_pos)).await?; + + let mut metadata = String::new(); + + bufreader + .take(metadata_len) + .read_to_string(&mut metadata) + .await + .context("Failed to read metadata from the current executable")?; + + let mut metadata: Metadata = serde_json::from_str(&metadata).unwrap(); + metadata.argv.append(&mut cli_args[1..].to_vec()); + + Ok(Some((metadata, eszip))) +} + +fn u64_from_bytes(arr: &[u8]) -> Result { + let fixed_arr: &[u8; 8] = arr + .try_into() + .context("Failed to convert the buffer into a fixed-size array")?; + Ok(u64::from_be_bytes(*fixed_arr)) +} + +pub struct DenoCompileBinaryWriter { + file_fetcher: Arc, + client: HttpClient, + deno_dir: DenoDir, +} + +impl DenoCompileBinaryWriter { + pub fn new( + file_fetcher: Arc, + client: HttpClient, + deno_dir: DenoDir, + ) -> Self { + Self { + file_fetcher, + client, + deno_dir, + } + } + + pub async fn write_bin( + &self, + writer: &mut impl Write, + eszip: eszip::EszipV2, + module_specifier: &ModuleSpecifier, + compile_flags: &CompileFlags, + cli_options: &CliOptions, + ) -> Result<(), AnyError> { + // Select base binary based on target + let original_binary = + self.get_base_binary(compile_flags.target.clone()).await?; + + self + .write_standalone_binary( + writer, + original_binary, + eszip, + module_specifier, + cli_options, + compile_flags, + ) + .await + } + + async fn get_base_binary( + &self, + target: Option, + ) -> Result, AnyError> { + if target.is_none() { + let path = std::env::current_exe()?; + return Ok(std::fs::read(path)?); + } + + let target = target.unwrap_or_else(|| env!("TARGET").to_string()); + let binary_name = format!("deno-{target}.zip"); + + let binary_path_suffix = if crate::version::is_canary() { + format!("canary/{}/{}", crate::version::GIT_COMMIT_HASH, binary_name) + } else { + format!("release/v{}/{}", env!("CARGO_PKG_VERSION"), binary_name) + }; + + let download_directory = self.deno_dir.dl_folder_path(); + let binary_path = download_directory.join(&binary_path_suffix); + + if !binary_path.exists() { + self + .download_base_binary(&download_directory, &binary_path_suffix) + .await?; + } + + let archive_data = std::fs::read(binary_path)?; + let temp_dir = tempfile::TempDir::new()?; + let base_binary_path = crate::tools::upgrade::unpack_into_dir( + archive_data, + target.contains("windows"), + &temp_dir, + )?; + let base_binary = std::fs::read(base_binary_path)?; + drop(temp_dir); // delete the temp dir + Ok(base_binary) + } + + async fn download_base_binary( + &self, + output_directory: &Path, + binary_path_suffix: &str, + ) -> Result<(), AnyError> { + let download_url = format!("https://dl.deno.land/{binary_path_suffix}"); + let maybe_bytes = { + let progress_bars = ProgressBar::new(ProgressBarStyle::DownloadBars); + let progress = progress_bars.update(&download_url); + + self + .client + .download_with_progress(download_url, &progress) + .await? + }; + let bytes = match maybe_bytes { + Some(bytes) => bytes, + None => { + log::info!("Download could not be found, aborting"); + std::process::exit(1) + } + }; + + std::fs::create_dir_all(output_directory)?; + let output_path = output_directory.join(binary_path_suffix); + std::fs::create_dir_all(output_path.parent().unwrap())?; + tokio::fs::write(output_path, bytes).await?; + Ok(()) + } + + /// This functions creates a standalone deno binary by appending a bundle + /// and magic trailer to the currently executing binary. + async fn write_standalone_binary( + &self, + writer: &mut impl Write, + original_bin: Vec, + eszip: eszip::EszipV2, + entrypoint: &ModuleSpecifier, + cli_options: &CliOptions, + compile_flags: &CompileFlags, + ) -> Result<(), AnyError> { + let ca_data = match cli_options.ca_data() { + Some(CaData::File(ca_file)) => Some( + std::fs::read(ca_file) + .with_context(|| format!("Reading: {ca_file}"))?, + ), + Some(CaData::Bytes(bytes)) => Some(bytes.clone()), + None => None, + }; + let maybe_import_map = cli_options + .resolve_import_map(&self.file_fetcher) + .await? + .map(|import_map| (import_map.base_url().clone(), import_map.to_json())); + let metadata = Metadata { + argv: compile_flags.args.clone(), + unstable: cli_options.unstable(), + seed: cli_options.seed(), + location: cli_options.location_flag().clone(), + permissions: cli_options.permissions_options(), + v8_flags: cli_options.v8_flags().clone(), + unsafely_ignore_certificate_errors: cli_options + .unsafely_ignore_certificate_errors() + .clone(), + log_level: cli_options.log_level(), + ca_stores: cli_options.ca_stores().clone(), + ca_data, + entrypoint: entrypoint.clone(), + maybe_import_map, + }; + + write_binary_bytes(writer, original_bin, &metadata, eszip) + } +} diff --git a/cli/standalone.rs b/cli/standalone/mod.rs similarity index 77% rename from cli/standalone.rs rename to cli/standalone/mod.rs index 48d71a045c..a2872e9b92 100644 --- a/cli/standalone.rs +++ b/cli/standalone/mod.rs @@ -12,16 +12,9 @@ use crate::CliGraphResolver; use deno_core::anyhow::Context; use deno_core::error::type_error; use deno_core::error::AnyError; -use deno_core::futures::io::AllowStdIo; use deno_core::futures::task::LocalFutureObj; -use deno_core::futures::AsyncReadExt; -use deno_core::futures::AsyncSeekExt; use deno_core::futures::FutureExt; use deno_core::located_script_name; -use deno_core::serde::Deserialize; -use deno_core::serde::Serialize; -use deno_core::serde_json; -use deno_core::url::Url; use deno_core::v8_set_flags; use deno_core::ModuleLoader; use deno_core::ModuleSpecifier; @@ -33,7 +26,6 @@ use deno_runtime::ops::worker_host::CreateWebWorkerCb; use deno_runtime::ops::worker_host::WorkerEventCb; use deno_runtime::permissions::Permissions; use deno_runtime::permissions::PermissionsContainer; -use deno_runtime::permissions::PermissionsOptions; use deno_runtime::web_worker::WebWorker; use deno_runtime::web_worker::WebWorkerOptions; use deno_runtime::worker::MainWorker; @@ -41,93 +33,17 @@ use deno_runtime::worker::WorkerOptions; use deno_runtime::BootstrapOptions; use import_map::parse_from_json; use log::Level; -use std::env::current_exe; -use std::io::SeekFrom; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; -#[derive(Deserialize, Serialize)] -pub struct Metadata { - pub argv: Vec, - pub unstable: bool, - pub seed: Option, - pub permissions: PermissionsOptions, - pub location: Option, - pub v8_flags: Vec, - pub log_level: Option, - pub ca_stores: Option>, - pub ca_data: Option>, - pub unsafely_ignore_certificate_errors: Option>, - pub maybe_import_map: Option<(Url, String)>, - pub entrypoint: ModuleSpecifier, -} +mod binary; -pub const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd"; +pub use binary::extract_standalone; +pub use binary::is_standalone_binary; +pub use binary::DenoCompileBinaryWriter; -/// This function will try to run this binary as a standalone binary -/// produced by `deno compile`. It determines if this is a standalone -/// binary by checking for the magic trailer string `d3n0l4nd` at EOF-24. -/// The magic trailer is followed by: -/// - a u64 pointer to the JS bundle embedded in the binary -/// - a u64 pointer to JSON metadata (serialized flags) embedded in the binary -/// These are dereferenced, and the bundle is executed under the configuration -/// specified by the metadata. If no magic trailer is present, this function -/// exits with `Ok(None)`. -pub async fn extract_standalone( - args: Vec, -) -> Result, AnyError> { - let current_exe_path = current_exe()?; - - let file = std::fs::File::open(current_exe_path)?; - - let mut bufreader = - deno_core::futures::io::BufReader::new(AllowStdIo::new(file)); - - let trailer_pos = bufreader.seek(SeekFrom::End(-24)).await?; - let mut trailer = [0; 24]; - bufreader.read_exact(&mut trailer).await?; - let (magic_trailer, rest) = trailer.split_at(8); - if magic_trailer != MAGIC_TRAILER { - return Ok(None); - } - - let (eszip_archive_pos, rest) = rest.split_at(8); - let metadata_pos = rest; - let eszip_archive_pos = u64_from_bytes(eszip_archive_pos)?; - let metadata_pos = u64_from_bytes(metadata_pos)?; - let metadata_len = trailer_pos - metadata_pos; - - bufreader.seek(SeekFrom::Start(eszip_archive_pos)).await?; - - let (eszip, loader) = eszip::EszipV2::parse(bufreader) - .await - .context("Failed to parse eszip header")?; - - let mut bufreader = loader.await.context("Failed to parse eszip archive")?; - - bufreader.seek(SeekFrom::Start(metadata_pos)).await?; - - let mut metadata = String::new(); - - bufreader - .take(metadata_len) - .read_to_string(&mut metadata) - .await - .context("Failed to read metadata from the current executable")?; - - let mut metadata: Metadata = serde_json::from_str(&metadata).unwrap(); - metadata.argv.append(&mut args[1..].to_vec()); - - Ok(Some((metadata, eszip))) -} - -fn u64_from_bytes(arr: &[u8]) -> Result { - let fixed_arr: &[u8; 8] = arr - .try_into() - .context("Failed to convert the buffer into a fixed-size array")?; - Ok(u64::from_be_bytes(*fixed_arr)) -} +use self::binary::Metadata; #[derive(Clone)] struct EmbeddedModuleLoader { diff --git a/cli/tests/integration/compile_tests.rs b/cli/tests/integration/compile_tests.rs index 957beed30a..7835d7f0d8 100644 --- a/cli/tests/integration/compile_tests.rs +++ b/cli/tests/integration/compile_tests.rs @@ -4,6 +4,7 @@ use std::fs::File; use std::process::Command; use test_util as util; use test_util::TempDir; +use util::assert_contains; #[test] fn compile() { @@ -111,13 +112,13 @@ fn standalone_error() { let stderr = util::strip_ansi_codes(&stderr).to_string(); // On Windows, we cannot assert the file path (because '\'). // Instead we just check for relevant output. - assert!(stderr.contains("error: Uncaught Error: boom!")); - assert!(stderr.contains("throw new Error(\"boom!\");")); - assert!(stderr.contains("\n at boom (file://")); - assert!(stderr.contains("standalone_error.ts:2:11")); - assert!(stderr.contains("at foo (file://")); - assert!(stderr.contains("standalone_error.ts:5:5")); - assert!(stderr.contains("standalone_error.ts:7:1")); + assert_contains!(stderr, "error: Uncaught Error: boom!"); + assert_contains!(stderr, "throw new Error(\"boom!\");"); + assert_contains!(stderr, "\n at boom (file://"); + assert_contains!(stderr, "standalone_error.ts:2:11"); + assert_contains!(stderr, "at foo (file://"); + assert_contains!(stderr, "standalone_error.ts:5:5"); + assert_contains!(stderr, "standalone_error.ts:7:1"); } #[test] @@ -156,10 +157,10 @@ fn standalone_error_module_with_imports() { let stderr = util::strip_ansi_codes(&stderr).to_string(); // On Windows, we cannot assert the file path (because '\'). // Instead we just check for relevant output. - assert!(stderr.contains("error: Uncaught Error: boom!")); - assert!(stderr.contains("throw new Error(\"boom!\");")); - assert!(stderr.contains("\n at file://")); - assert!(stderr.contains("standalone_error_module_with_imports_2.ts:2:7")); + assert_contains!(stderr, "error: Uncaught Error: boom!"); + assert_contains!(stderr, "throw new Error(\"boom!\");"); + assert_contains!(stderr, "\n at file://"); + assert_contains!(stderr, "standalone_error_module_with_imports_2.ts:2:7"); } #[test] @@ -259,7 +260,7 @@ fn compile_with_file_exists_error() { file_path.display(), ); let stderr = String::from_utf8(output.stderr).unwrap(); - assert!(stderr.contains(&expected_stderr)); + assert_contains!(stderr, &expected_stderr); } #[test] @@ -293,7 +294,7 @@ fn compile_with_directory_exists_error() { exe.display() ); let stderr = String::from_utf8(output.stderr).unwrap(); - assert!(stderr.contains(&expected_stderr)); + assert_contains!(stderr, &expected_stderr); } #[test] @@ -327,8 +328,7 @@ fn compile_with_conflict_file_exists_error() { exe.display() ); let stderr = String::from_utf8(output.stderr).unwrap(); - dbg!(&stderr); - assert!(stderr.contains(&expected_stderr)); + assert_contains!(stderr, &expected_stderr); assert!(std::fs::read(&exe) .unwrap() .eq(b"SHOULD NOT BE OVERWRITTEN")); @@ -407,8 +407,10 @@ fn standalone_runtime_flags() { let stdout_str = String::from_utf8(output.stdout).unwrap(); assert_eq!(util::strip_ansi_codes(&stdout_str), "0.147205063401058\n"); let stderr_str = String::from_utf8(output.stderr).unwrap(); - assert!(util::strip_ansi_codes(&stderr_str) - .contains("PermissionDenied: Requires write access")); + assert_contains!( + util::strip_ansi_codes(&stderr_str), + "PermissionDenied: Requires write access" + ); } #[test] @@ -636,9 +638,10 @@ fn check_local_by_default2() { let stdout = String::from_utf8(output.stdout).unwrap(); let stderr = String::from_utf8(output.stderr).unwrap(); assert!(stdout.is_empty()); - assert!(stderr.contains( + assert_contains!( + stderr, r#"error: TS2322 [ERROR]: Type '12' is not assignable to type '"b"'."# - )); + ); } #[test] diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index fab3266ea4..94b1c01703 100644 --- a/cli/tools/standalone.rs +++ b/cli/tools/standalone.rs @@ -1,32 +1,18 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use crate::args::CaData; use crate::args::CompileFlags; use crate::args::Flags; -use crate::cache::DenoDir; use crate::graph_util::error_for_any_npm_specifier; -use crate::http_util::HttpClient; -use crate::standalone::Metadata; -use crate::standalone::MAGIC_TRAILER; +use crate::standalone::is_standalone_binary; +use crate::standalone::DenoCompileBinaryWriter; use crate::util::path::path_has_trailing_slash; -use crate::util::progress_bar::ProgressBar; -use crate::util::progress_bar::ProgressBarStyle; use crate::ProcState; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::resolve_url_or_path; -use deno_core::serde_json; -use deno_graph::ModuleSpecifier; use deno_runtime::colors; -use std::env; -use std::fs; -use std::fs::File; -use std::io::Read; -use std::io::Seek; -use std::io::SeekFrom; -use std::io::Write; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -38,6 +24,11 @@ pub async fn compile( compile_flags: CompileFlags, ) -> Result<(), AnyError> { let ps = ProcState::from_flags(flags).await?; + let binary_writer = DenoCompileBinaryWriter::new( + ps.file_fetcher.clone(), + ps.http_client.clone(), + ps.dir.clone(), + ); let module_specifier = ps.options.resolve_main_module()?; let module_roots = { let mut vec = Vec::with_capacity(compile_flags.include.len() + 1); @@ -47,7 +38,6 @@ pub async fn compile( } vec }; - let deno_dir = &ps.dir; let output_path = resolve_compile_executable_output_path( &compile_flags, @@ -69,164 +59,40 @@ pub async fn compile( let eszip = eszip::EszipV2::from_graph(graph, &parser, Default::default())?; log::info!( - "{} {}", + "{} {} to {}", colors::green("Compile"), - module_specifier.to_string() + module_specifier.to_string(), + output_path.display(), ); + validate_output_path(&output_path)?; - // Select base binary based on target - let original_binary = - get_base_binary(&ps.http_client, deno_dir, compile_flags.target.clone()) - .await?; + let mut file = std::fs::File::create(&output_path)?; + binary_writer + .write_bin( + &mut file, + eszip, + &module_specifier, + &compile_flags, + &ps.options, + ) + .await + .with_context(|| format!("Writing {}", output_path.display()))?; + drop(file); - let final_bin = create_standalone_binary( - original_binary, - eszip, - module_specifier, - &compile_flags, - ps, - ) - .await?; - - log::info!("{} {}", colors::green("Emit"), output_path.display()); - - write_standalone_binary(output_path, final_bin).await?; - Ok(()) -} - -async fn get_base_binary( - client: &HttpClient, - deno_dir: &DenoDir, - target: Option, -) -> Result, AnyError> { - if target.is_none() { - let path = std::env::current_exe()?; - return Ok(tokio::fs::read(path).await?); + // set it as executable + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let perms = std::fs::Permissions::from_mode(0o777); + std::fs::set_permissions(output_path, perms)?; } - let target = target.unwrap_or_else(|| env!("TARGET").to_string()); - let binary_name = format!("deno-{target}.zip"); - - let binary_path_suffix = if crate::version::is_canary() { - format!("canary/{}/{}", crate::version::GIT_COMMIT_HASH, binary_name) - } else { - format!("release/v{}/{}", env!("CARGO_PKG_VERSION"), binary_name) - }; - - let download_directory = deno_dir.dl_folder_path(); - let binary_path = download_directory.join(&binary_path_suffix); - - if !binary_path.exists() { - download_base_binary(client, &download_directory, &binary_path_suffix) - .await?; - } - - let archive_data = tokio::fs::read(binary_path).await?; - let temp_dir = tempfile::TempDir::new()?; - let base_binary_path = crate::tools::upgrade::unpack_into_dir( - archive_data, - target.contains("windows"), - &temp_dir, - )?; - let base_binary = tokio::fs::read(base_binary_path).await?; - drop(temp_dir); // delete the temp dir - Ok(base_binary) -} - -async fn download_base_binary( - client: &HttpClient, - output_directory: &Path, - binary_path_suffix: &str, -) -> Result<(), AnyError> { - let download_url = format!("https://dl.deno.land/{binary_path_suffix}"); - let maybe_bytes = { - let progress_bars = ProgressBar::new(ProgressBarStyle::DownloadBars); - let progress = progress_bars.update(&download_url); - - client - .download_with_progress(download_url, &progress) - .await? - }; - let bytes = match maybe_bytes { - Some(bytes) => bytes, - None => { - log::info!("Download could not be found, aborting"); - std::process::exit(1) - } - }; - - std::fs::create_dir_all(output_directory)?; - let output_path = output_directory.join(binary_path_suffix); - std::fs::create_dir_all(output_path.parent().unwrap())?; - tokio::fs::write(output_path, bytes).await?; Ok(()) } -/// This functions creates a standalone deno binary by appending a bundle -/// and magic trailer to the currently executing binary. -async fn create_standalone_binary( - mut original_bin: Vec, - eszip: eszip::EszipV2, - entrypoint: ModuleSpecifier, - compile_flags: &CompileFlags, - ps: ProcState, -) -> Result, AnyError> { - let mut eszip_archive = eszip.into_bytes(); - - let ca_data = match ps.options.ca_data() { - Some(CaData::File(ca_file)) => { - Some(fs::read(ca_file).with_context(|| format!("Reading: {ca_file}"))?) - } - Some(CaData::Bytes(bytes)) => Some(bytes.clone()), - None => None, - }; - let maybe_import_map = ps - .options - .resolve_import_map(&ps.file_fetcher) - .await? - .map(|import_map| (import_map.base_url().clone(), import_map.to_json())); - let metadata = Metadata { - argv: compile_flags.args.clone(), - unstable: ps.options.unstable(), - seed: ps.options.seed(), - location: ps.options.location_flag().clone(), - permissions: ps.options.permissions_options(), - v8_flags: ps.options.v8_flags().clone(), - unsafely_ignore_certificate_errors: ps - .options - .unsafely_ignore_certificate_errors() - .clone(), - log_level: ps.options.log_level(), - ca_stores: ps.options.ca_stores().clone(), - ca_data, - entrypoint, - maybe_import_map, - }; - let mut metadata = serde_json::to_string(&metadata)?.as_bytes().to_vec(); - - let eszip_pos = original_bin.len(); - let metadata_pos = eszip_pos + eszip_archive.len(); - let mut trailer = MAGIC_TRAILER.to_vec(); - trailer.write_all(&eszip_pos.to_be_bytes())?; - trailer.write_all(&metadata_pos.to_be_bytes())?; - - let mut final_bin = Vec::with_capacity( - original_bin.len() + eszip_archive.len() + trailer.len(), - ); - final_bin.append(&mut original_bin); - final_bin.append(&mut eszip_archive); - final_bin.append(&mut metadata); - final_bin.append(&mut trailer); - - Ok(final_bin) -} - /// This function writes out a final binary to specified path. If output path /// is not already standalone binary it will return error instead. -async fn write_standalone_binary( - output_path: PathBuf, - final_bin: Vec, -) -> Result<(), AnyError> { +fn validate_output_path(output_path: &Path) -> Result<(), AnyError> { if output_path.exists() { // If the output is a directory, throw error if output_path.is_dir() { @@ -240,19 +106,9 @@ async fn write_standalone_binary( ); } - // Make sure we don't overwrite any file not created by Deno compiler. - // Check for magic trailer in last 24 bytes. - let mut has_trailer = false; - let mut output_file = File::open(&output_path)?; - // This seek may fail because the file is too small to possibly be - // `deno compile` output. - if output_file.seek(SeekFrom::End(-24)).is_ok() { - let mut trailer = [0; 24]; - output_file.read_exact(&mut trailer)?; - let (magic_trailer, _) = trailer.split_at(8); - has_trailer = magic_trailer == MAGIC_TRAILER; - } - if !has_trailer { + // Make sure we don't overwrite any file not created by Deno compiler because + // this filename is chosen automatically in some cases. + if !is_standalone_binary(output_path) { bail!( concat!( "Could not compile to file '{}' because the file already exists ", @@ -265,28 +121,20 @@ async fn write_standalone_binary( // Remove file if it was indeed a deno compiled binary, to avoid corruption // (see https://github.com/denoland/deno/issues/10310) - std::fs::remove_file(&output_path)?; + std::fs::remove_file(output_path)?; } else { let output_base = &output_path.parent().unwrap(); if output_base.exists() && output_base.is_file() { bail!( - concat!( - "Could not compile to file '{}' because its parent directory ", - "is an existing file. You can use the `--output ` flag to ", - "provide an alternative name.", - ), - output_base.display(), - ); + concat!( + "Could not compile to file '{}' because its parent directory ", + "is an existing file. You can use the `--output ` flag to ", + "provide an alternative name.", + ), + output_base.display(), + ); } - tokio::fs::create_dir_all(output_base).await?; - } - - tokio::fs::write(&output_path, final_bin).await?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let perms = std::fs::Permissions::from_mode(0o777); - tokio::fs::set_permissions(output_path, perms).await?; + std::fs::create_dir_all(output_base)?; } Ok(())