diff options
Diffstat (limited to 'cli/tools')
-rw-r--r-- | cli/tools/standalone.rs | 238 |
1 files changed, 43 insertions, 195 deletions
diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index fab3266ea..94b1c0170 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)?; + + 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); - // Select base binary based on target - let original_binary = - get_base_binary(&ps.http_client, deno_dir, compile_flags.target.clone()) - .await?; - - 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<String>, -) -> Result<Vec<u8>, AnyError> { - if target.is_none() { - let path = std::env::current_exe()?; - return Ok(tokio::fs::read(path).await?); - } - - 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?; + // 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 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<u8>, - eszip: eszip::EszipV2, - entrypoint: ModuleSpecifier, - compile_flags: &CompileFlags, - ps: ProcState, -) -> Result<Vec<u8>, 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<u8>, -) -> 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 <file-path>` 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 <file-path>` 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(()) |