summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/main.rs6
-rw-r--r--cli/standalone/binary.rs307
-rw-r--r--cli/standalone/mod.rs (renamed from cli/standalone.rs)94
-rw-r--r--cli/tests/integration/compile_tests.rs41
-rw-r--r--cli/tools/standalone.rs238
5 files changed, 382 insertions, 304 deletions
diff --git a/cli/main.rs b/cli/main.rs
index 5e088d891..02ac5891c 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<i32, AnyError> {
@@ -245,8 +246,11 @@ pub fn main() {
let args: Vec<String> = 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(&current_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 000000000..bca0aff2b
--- /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<String>,
+ pub unstable: bool,
+ pub seed: Option<u64>,
+ pub permissions: PermissionsOptions,
+ pub location: Option<Url>,
+ pub v8_flags: Vec<String>,
+ pub log_level: Option<Level>,
+ pub ca_stores: Option<Vec<String>>,
+ pub ca_data: Option<Vec<u8>>,
+ pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
+ pub maybe_import_map: Option<(Url, String)>,
+ pub entrypoint: ModuleSpecifier,
+}
+
+pub fn write_binary_bytes(
+ writer: &mut impl Write,
+ original_bin: Vec<u8>,
+ 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<String>,
+) -> Result<Option<(Metadata, eszip::EszipV2)>, 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<u64, AnyError> {
+ 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<FileFetcher>,
+ client: HttpClient,
+ deno_dir: DenoDir,
+}
+
+impl DenoCompileBinaryWriter {
+ pub fn new(
+ file_fetcher: Arc<FileFetcher>,
+ 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<String>,
+ ) -> Result<Vec<u8>, 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<u8>,
+ 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
index 48d71a045..a2872e9b9 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<String>,
- pub unstable: bool,
- pub seed: Option<u64>,
- pub permissions: PermissionsOptions,
- pub location: Option<Url>,
- pub v8_flags: Vec<String>,
- pub log_level: Option<Level>,
- pub ca_stores: Option<Vec<String>>,
- pub ca_data: Option<Vec<u8>>,
- pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
- pub maybe_import_map: Option<(Url, String)>,
- pub entrypoint: ModuleSpecifier,
-}
-
-pub 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 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<String>,
-) -> Result<Option<(Metadata, eszip::EszipV2)>, 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?;
+mod binary;
- let (eszip, loader) = eszip::EszipV2::parse(bufreader)
- .await
- .context("Failed to parse eszip header")?;
+pub use binary::extract_standalone;
+pub use binary::is_standalone_binary;
+pub use binary::DenoCompileBinaryWriter;
- 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<u64, AnyError> {
- 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 957beed30..7835d7f0d 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 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(())