From a68eb3fcc3997fce8680f87edce46f6450e79635 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Tue, 13 Feb 2024 21:52:30 +0530 Subject: [PATCH] feat: denort binary for `deno compile` (#22205) This introduces the `denort` binary - a slim version of deno without tooling. The binary is used as the default for `deno compile`. Improves `deno compile` final size by ~2.5x (141 MB -> 61 MB) on Linux x86_64. --- .github/workflows/ci.generate.ts | 12 ++- .github/workflows/ci.yml | 12 ++- cli/Cargo.toml | 5 + cli/factory.rs | 32 +++++- cli/mainrt.rs | 82 ++++++++++++++ cli/resolver.rs | 11 +- cli/standalone/binary.rs | 80 +++++++++++++- cli/standalone/mod.rs | 5 +- cli/tools/coverage/mod.rs | 97 +++++++++-------- cli/tools/run/hmr.rs | 179 ++++++++++++++++--------------- cli/tools/upgrade.rs | 80 ++------------ cli/worker.rs | 92 +++++++++------- test_util/src/builders.rs | 12 ++- test_util/src/lib.rs | 8 ++ 14 files changed, 453 insertions(+), 254 deletions(-) create mode 100644 cli/mainrt.rs diff --git a/.github/workflows/ci.generate.ts b/.github/workflows/ci.generate.ts index 26ca893162..29077ab546 100755 --- a/.github/workflows/ci.generate.ts +++ b/.github/workflows/ci.generate.ts @@ -704,6 +704,7 @@ const ci = { run: [ "cd target/release", "zip -r deno-${{ matrix.arch }}-unknown-linux-gnu.zip deno", + "zip -r denort-${{ matrix.arch }}-unknown-linux-gnu.zip denort", "./deno types > lib.deno.d.ts", ].join("\n"), }, @@ -728,6 +729,7 @@ const ci = { "--entitlements-xml-file=cli/entitlements.plist", "cd target/release", "zip -r deno-${{ matrix.arch }}-apple-darwin.zip deno", + "zip -r denort-${{ matrix.arch }}-apple-darwin.zip denort", ] .join("\n"), }, @@ -740,8 +742,10 @@ const ci = { "github.repository == 'denoland/deno'", ].join("\n"), shell: "pwsh", - run: + run: [ "Compress-Archive -CompressionLevel Optimal -Force -Path target/release/deno.exe -DestinationPath target/release/deno-${{ matrix.arch }}-pc-windows-msvc.zip", + "Compress-Archive -CompressionLevel Optimal -Force -Path target/release/denort.exe -DestinationPath target/release/denort-${{ matrix.arch }}-pc-windows-msvc.zip", + ].join("\n"), }, { name: "Upload canary to dl.deno.land", @@ -942,6 +946,7 @@ const ci = { run: [ 'du -hd1 "./target/${{ matrix.profile }}"', 'du -ha "./target/${{ matrix.profile }}/deno"', + 'du -ha "./target/${{ matrix.profile }}/denort"', ].join("\n"), }, { @@ -1007,10 +1012,15 @@ const ci = { with: { files: [ "target/release/deno-x86_64-pc-windows-msvc.zip", + "target/release/denort-x86_64-pc-windows-msvc.zip", "target/release/deno-x86_64-unknown-linux-gnu.zip", + "target/release/denort-x86_64-unknown-linux-gnu.zip", "target/release/deno-x86_64-apple-darwin.zip", + "target/release/denort-x86_64-apple-darwin.zip", "target/release/deno-aarch64-unknown-linux-gnu.zip", + "target/release/denort-aarch64-unknown-linux-gnu.zip", "target/release/deno-aarch64-apple-darwin.zip", + "target/release/denort-aarch64-apple-darwin.zip", "target/release/deno_src.tar.gz", "target/release/lib.deno.d.ts", ].join("\n"), diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79edd24c56..7342319c4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -424,6 +424,7 @@ jobs: run: |- cd target/release zip -r deno-${{ matrix.arch }}-unknown-linux-gnu.zip deno + zip -r denort-${{ matrix.arch }}-unknown-linux-gnu.zip denort ./deno types > lib.deno.d.ts - name: Pre-release (mac) if: |- @@ -439,6 +440,7 @@ jobs: rcodesign sign target/release/deno --code-signature-flags=runtime --p12-password="$APPLE_CODESIGN_PASSWORD" --p12-file=<(echo $APPLE_CODESIGN_KEY | base64 -d) --entitlements-xml-file=cli/entitlements.plist cd target/release zip -r deno-${{ matrix.arch }}-apple-darwin.zip deno + zip -r denort-${{ matrix.arch }}-apple-darwin.zip denort - name: Pre-release (windows) if: |- !(matrix.skip) && (matrix.os == 'windows' && @@ -446,7 +448,9 @@ jobs: matrix.profile == 'release' && github.repository == 'denoland/deno') shell: pwsh - run: 'Compress-Archive -CompressionLevel Optimal -Force -Path target/release/deno.exe -DestinationPath target/release/deno-${{ matrix.arch }}-pc-windows-msvc.zip' + run: |- + Compress-Archive -CompressionLevel Optimal -Force -Path target/release/deno.exe -DestinationPath target/release/deno-${{ matrix.arch }}-pc-windows-msvc.zip + Compress-Archive -CompressionLevel Optimal -Force -Path target/release/denort.exe -DestinationPath target/release/denort-${{ matrix.arch }}-pc-windows-msvc.zip - name: Upload canary to dl.deno.land if: |- !(matrix.skip) && (matrix.job == 'test' && @@ -588,6 +592,7 @@ jobs: run: |- du -hd1 "./target/${{ matrix.profile }}" du -ha "./target/${{ matrix.profile }}/deno" + du -ha "./target/${{ matrix.profile }}/denort" - name: Worker info if: '!(matrix.skip) && (matrix.job == ''bench'')' run: |- @@ -632,10 +637,15 @@ jobs: with: files: |- target/release/deno-x86_64-pc-windows-msvc.zip + target/release/denort-x86_64-pc-windows-msvc.zip target/release/deno-x86_64-unknown-linux-gnu.zip + target/release/denort-x86_64-unknown-linux-gnu.zip target/release/deno-x86_64-apple-darwin.zip + target/release/denort-x86_64-apple-darwin.zip target/release/deno-aarch64-unknown-linux-gnu.zip + target/release/denort-aarch64-unknown-linux-gnu.zip target/release/deno-aarch64-apple-darwin.zip + target/release/denort-aarch64-apple-darwin.zip target/release/deno_src.tar.gz target/release/lib.deno.d.ts body_path: target/release/release-notes.md diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 365f26f91e..000e1b17cf 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,6 +15,11 @@ name = "deno" path = "main.rs" doc = false +[[bin]] +name = "denort" +path = "mainrt.rs" +doc = false + [[test]] name = "integration" path = "integration_tests_runner.rs" diff --git a/cli/factory.rs b/cli/factory.rs index 32cb256a3f..0b24350ed0 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -42,6 +42,8 @@ use crate::resolver::NpmModuleLoader; use crate::resolver::SloppyImportsResolver; use crate::standalone::DenoCompileBinaryWriter; use crate::tools::check::TypeChecker; +use crate::tools::coverage::CoverageCollector; +use crate::tools::run::hmr::HmrRunner; use crate::util::file_watcher::WatcherCommunicator; use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::import_map::deno_json_deps; @@ -50,6 +52,7 @@ use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::worker::CliMainWorkerFactory; use crate::worker::CliMainWorkerOptions; +use std::path::PathBuf; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; @@ -766,7 +769,6 @@ impl CliFactory { )), self.root_cert_store_provider().clone(), self.fs().clone(), - Some(self.emitter()?.clone()), maybe_file_watcher_communicator, self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), @@ -783,6 +785,32 @@ impl CliFactory { fn create_cli_main_worker_options( &self, ) -> Result { + let create_hmr_runner = if self.options.has_hmr() { + let watcher_communicator = self.watcher_communicator.clone().unwrap(); + let emitter = self.emitter()?.clone(); + let fn_: crate::worker::CreateHmrRunnerCb = Box::new(move |session| { + Box::new(HmrRunner::new( + emitter.clone(), + session, + watcher_communicator.clone(), + )) + }); + Some(fn_) + } else { + None + }; + let create_coverage_collector = + if let Some(coverage_dir) = self.options.coverage_dir() { + let coverage_dir = PathBuf::from(coverage_dir); + let fn_: crate::worker::CreateCoverageCollectorCb = + Box::new(move |session| { + Box::new(CoverageCollector::new(coverage_dir.clone(), session)) + }); + Some(fn_) + } else { + None + }; + Ok(CliMainWorkerOptions { argv: self.options.argv().clone(), // This optimization is only available for "run" subcommand @@ -814,6 +842,8 @@ impl CliFactory { .clone(), unstable: self.options.legacy_unstable_flag(), maybe_root_package_json_deps: self.options.maybe_package_json_deps(), + create_hmr_runner, + create_coverage_collector, }) } } diff --git a/cli/mainrt.rs b/cli/mainrt.rs new file mode 100644 index 0000000000..9c7ee3c5c9 --- /dev/null +++ b/cli/mainrt.rs @@ -0,0 +1,82 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +// Allow unused code warnings because we share +// code between the two bin targets. +#![allow(dead_code)] +#![allow(unused_imports)] + +mod standalone; + +mod args; +mod auth_tokens; +mod cache; +mod emit; +mod errors; +mod file_fetcher; +mod http_util; +mod js; +mod node; +mod npm; +mod resolver; +mod util; +mod version; +mod worker; + +use deno_core::error::generic_error; +use deno_core::error::AnyError; +use deno_core::error::JsError; +use deno_runtime::fmt_errors::format_js_error; +use deno_runtime::tokio_util::create_and_run_current_thread_with_maybe_metrics; +pub use deno_runtime::UNSTABLE_GRANULAR_FLAGS; +use deno_terminal::colors; + +use std::env; +use std::env::current_exe; + +use crate::args::Flags; + +pub(crate) fn unstable_exit_cb(feature: &str, api_name: &str) { + eprintln!( + "Unstable API '{api_name}'. The `--unstable-{}` flag must be provided.", + feature + ); + std::process::exit(70); +} + +fn exit_with_message(message: &str, code: i32) -> ! { + eprintln!( + "{}: {}", + colors::red_bold("error"), + message.trim_start_matches("error: ") + ); + std::process::exit(code); +} + +fn unwrap_or_exit(result: Result) -> T { + match result { + Ok(value) => value, + Err(error) => { + let mut error_string = format!("{error:?}"); + + if let Some(e) = error.downcast_ref::() { + error_string = format_js_error(e); + } + + exit_with_message(&error_string, 1); + } + } +} + +fn main() { + let args: Vec = env::args().collect(); + let future = async move { + let current_exe_path = current_exe().unwrap(); + match standalone::extract_standalone(¤t_exe_path, args).await { + Ok(Some((metadata, eszip))) => standalone::run(eszip, metadata).await, + Ok(None) => Err(generic_error("No archive found.")), + Err(err) => Err(err), + } + }; + + unwrap_or_exit(create_and_run_current_thread_with_maybe_metrics(future)); +} diff --git a/cli/resolver.rs b/cli/resolver.rs index 2c04823a70..5bb9e66d00 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -40,7 +40,7 @@ use std::sync::Arc; use crate::args::package_json::PackageJsonDeps; use crate::args::JsxImportSourceConfig; use crate::args::PackageJsonDepsProvider; -use crate::graph_util::format_range_with_colors; +use crate::colors; use crate::node::CliNodeCodeTranslator; use crate::npm::ByonmCliNpmResolver; use crate::npm::CliNpmResolver; @@ -48,6 +48,15 @@ use crate::npm::InnerCliNpmResolverRef; use crate::util::path::specifier_to_file_path; use crate::util::sync::AtomicFlag; +pub fn format_range_with_colors(range: &deno_graph::Range) -> String { + format!( + "{}:{}:{}", + colors::cyan(range.specifier.as_str()), + colors::yellow(&(range.start.line + 1).to_string()), + colors::yellow(&(range.start.character + 1).to_string()) + ) +} + pub struct ModuleCodeStringSource { pub code: ModuleCodeString, pub found_url: ModuleSpecifier, diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index f8bb1e21c7..f9d65fdaa8 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -2,12 +2,14 @@ use std::collections::BTreeMap; use std::env::current_exe; +use std::fs; 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::process::Command; use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; @@ -337,6 +339,71 @@ fn u64_from_bytes(arr: &[u8]) -> Result { Ok(u64::from_be_bytes(*fixed_arr)) } +pub fn unpack_into_dir( + exe_name: &str, + archive_name: &str, + archive_data: Vec, + is_windows: bool, + temp_dir: &tempfile::TempDir, +) -> Result { + let temp_dir_path = temp_dir.path(); + let exe_ext = if is_windows { "exe" } else { "" }; + let archive_path = temp_dir_path.join(exe_name).with_extension("zip"); + let exe_path = temp_dir_path.join(exe_name).with_extension(exe_ext); + assert!(!exe_path.exists()); + + let archive_ext = Path::new(archive_name) + .extension() + .and_then(|ext| ext.to_str()) + .unwrap(); + let unpack_status = match archive_ext { + "zip" if cfg!(windows) => { + fs::write(&archive_path, &archive_data)?; + Command::new("tar.exe") + .arg("xf") + .arg(&archive_path) + .arg("-C") + .arg(temp_dir_path) + .spawn() + .map_err(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + std::io::Error::new( + std::io::ErrorKind::NotFound, + "`tar.exe` was not found in your PATH", + ) + } else { + err + } + })? + .wait()? + } + "zip" => { + fs::write(&archive_path, &archive_data)?; + Command::new("unzip") + .current_dir(temp_dir_path) + .arg(&archive_path) + .spawn() + .map_err(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + std::io::Error::new( + std::io::ErrorKind::NotFound, + "`unzip` was not found in your PATH, please install `unzip`", + ) + } else { + err + } + })? + .wait()? + } + ext => bail!("Unsupported archive type: '{ext}'"), + }; + if !unpack_status.success() { + bail!("Failed to unpack archive."); + } + assert!(exe_path.exists()); + fs::remove_file(&archive_path)?; + Ok(exe_path) +} pub struct DenoCompileBinaryWriter<'a> { file_fetcher: &'a FileFetcher, client: &'a HttpClient, @@ -404,13 +471,16 @@ impl<'a> DenoCompileBinaryWriter<'a> { &self, compile_flags: &CompileFlags, ) -> Result, AnyError> { - if compile_flags.target.is_none() { - let path = std::env::current_exe()?; + // Used for testing. + // + // Phase 2 of the 'min sized' deno compile RFC talks + // about adding this as a flag. + if let Some(path) = std::env::var_os("DENORT_BIN") { return Ok(std::fs::read(path)?); } let target = compile_flags.resolve_target(); - let binary_name = format!("deno-{target}.zip"); + let binary_name = format!("denort-{target}.zip"); let binary_path_suffix = if crate::version::is_canary() { format!("canary/{}/{}", crate::version::GIT_COMMIT_HASH, binary_name) @@ -429,7 +499,9 @@ impl<'a> DenoCompileBinaryWriter<'a> { let archive_data = std::fs::read(binary_path)?; let temp_dir = tempfile::TempDir::new()?; - let base_binary_path = crate::tools::upgrade::unpack_into_dir( + let base_binary_path = unpack_into_dir( + "denort", + &binary_name, archive_data, target.contains("windows"), &temp_dir, diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index e25b61c310..ecbb0be823 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -55,7 +55,7 @@ use import_map::parse_from_json; use std::rc::Rc; use std::sync::Arc; -mod binary; +pub mod binary; mod file_system; mod virtual_fs; @@ -520,7 +520,6 @@ pub async fn run( None, None, None, - None, feature_checker, CliMainWorkerOptions { argv: metadata.argv, @@ -548,6 +547,8 @@ pub async fn run( .unsafely_ignore_certificate_errors, unstable: metadata.unstable_config.legacy_flag_enabled, maybe_root_package_json_deps: package_json_deps_provider.deps().cloned(), + create_hmr_runner: None, + create_coverage_collector: None, }, None, // TODO(bartlomieju): temporarily disabled diff --git a/cli/tools/coverage/mod.rs b/cli/tools/coverage/mod.rs index 16c9555768..aafef292f7 100644 --- a/cli/tools/coverage/mod.rs +++ b/cli/tools/coverage/mod.rs @@ -46,6 +46,56 @@ pub struct CoverageCollector { session: LocalInspectorSession, } +#[async_trait::async_trait(?Send)] +impl crate::worker::CoverageCollector for CoverageCollector { + async fn start_collecting(&mut self) -> Result<(), AnyError> { + self.enable_debugger().await?; + self.enable_profiler().await?; + self + .start_precise_coverage(cdp::StartPreciseCoverageArgs { + call_count: true, + detailed: true, + allow_triggered_updates: false, + }) + .await?; + + Ok(()) + } + + async fn stop_collecting(&mut self) -> Result<(), AnyError> { + fs::create_dir_all(&self.dir)?; + + let script_coverages = self.take_precise_coverage().await?.result; + for script_coverage in script_coverages { + // Filter out internal JS files from being included in coverage reports + if script_coverage.url.starts_with("ext:") + || script_coverage.url.starts_with("[ext:") + { + continue; + } + + let filename = format!("{}.json", Uuid::new_v4()); + let filepath = self.dir.join(filename); + + let mut out = BufWriter::new(File::create(&filepath)?); + let coverage = serde_json::to_string(&script_coverage)?; + let formatted_coverage = + format_json(&filepath, &coverage, &Default::default()) + .ok() + .flatten() + .unwrap_or(coverage); + + out.write_all(formatted_coverage.as_bytes())?; + out.flush()?; + } + + self.disable_debugger().await?; + self.disable_profiler().await?; + + Ok(()) + } +} + impl CoverageCollector { pub fn new(dir: PathBuf, session: LocalInspectorSession) -> Self { Self { dir, session } @@ -109,53 +159,6 @@ impl CoverageCollector { Ok(return_object) } - - pub async fn start_collecting(&mut self) -> Result<(), AnyError> { - self.enable_debugger().await?; - self.enable_profiler().await?; - self - .start_precise_coverage(cdp::StartPreciseCoverageArgs { - call_count: true, - detailed: true, - allow_triggered_updates: false, - }) - .await?; - - Ok(()) - } - - pub async fn stop_collecting(&mut self) -> Result<(), AnyError> { - fs::create_dir_all(&self.dir)?; - - let script_coverages = self.take_precise_coverage().await?.result; - for script_coverage in script_coverages { - // Filter out internal JS files from being included in coverage reports - if script_coverage.url.starts_with("ext:") - || script_coverage.url.starts_with("[ext:") - { - continue; - } - - let filename = format!("{}.json", Uuid::new_v4()); - let filepath = self.dir.join(filename); - - let mut out = BufWriter::new(File::create(&filepath)?); - let coverage = serde_json::to_string(&script_coverage)?; - let formatted_coverage = - format_json(&filepath, &coverage, &Default::default()) - .ok() - .flatten() - .unwrap_or(coverage); - - out.write_all(formatted_coverage.as_bytes())?; - out.flush()?; - } - - self.disable_debugger().await?; - self.disable_profiler().await?; - - Ok(()) - } } #[derive(Debug, Clone)] diff --git a/cli/tools/run/hmr.rs b/cli/tools/run/hmr.rs index 88f90f680a..4ca9ee8b92 100644 --- a/cli/tools/run/hmr.rs +++ b/cli/tools/run/hmr.rs @@ -61,105 +61,22 @@ pub struct HmrRunner { emitter: Arc, } -impl HmrRunner { - pub fn new( - emitter: Arc, - session: LocalInspectorSession, - watcher_communicator: Arc, - ) -> Self { - Self { - session, - emitter, - watcher_communicator, - script_ids: HashMap::new(), - } - } - +#[async_trait::async_trait(?Send)] +impl crate::worker::HmrRunner for HmrRunner { // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` - pub async fn start(&mut self) -> Result<(), AnyError> { + async fn start(&mut self) -> Result<(), AnyError> { self.enable_debugger().await } // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` - pub async fn stop(&mut self) -> Result<(), AnyError> { + async fn stop(&mut self) -> Result<(), AnyError> { self .watcher_communicator .change_restart_mode(WatcherRestartMode::Automatic); self.disable_debugger().await } - // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` - async fn enable_debugger(&mut self) -> Result<(), AnyError> { - self - .session - .post_message::<()>("Debugger.enable", None) - .await?; - self - .session - .post_message::<()>("Runtime.enable", None) - .await?; - Ok(()) - } - - // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` - async fn disable_debugger(&mut self) -> Result<(), AnyError> { - self - .session - .post_message::<()>("Debugger.disable", None) - .await?; - self - .session - .post_message::<()>("Runtime.disable", None) - .await?; - Ok(()) - } - - async fn set_script_source( - &mut self, - script_id: &str, - source: &str, - ) -> Result { - let result = self - .session - .post_message( - "Debugger.setScriptSource", - Some(json!({ - "scriptId": script_id, - "scriptSource": source, - "allowTopFrameEditing": true, - })), - ) - .await?; - - Ok(serde_json::from_value::( - result, - )?) - } - - async fn dispatch_hmr_event( - &mut self, - script_id: &str, - ) -> Result<(), AnyError> { - let expr = format!( - "dispatchEvent(new CustomEvent(\"hmr\", {{ detail: {{ path: \"{}\" }} }}));", - script_id - ); - - let _result = self - .session - .post_message( - "Runtime.evaluate", - Some(json!({ - "expression": expr, - "contextId": Some(1), - })), - ) - .await?; - - Ok(()) - } - - pub async fn run(&mut self) -> Result<(), AnyError> { + async fn run(&mut self) -> Result<(), AnyError> { self .watcher_communicator .change_restart_mode(WatcherRestartMode::Manual); @@ -252,3 +169,89 @@ impl HmrRunner { } } } + +impl HmrRunner { + pub fn new( + emitter: Arc, + session: LocalInspectorSession, + watcher_communicator: Arc, + ) -> Self { + Self { + session, + emitter, + watcher_communicator, + script_ids: HashMap::new(), + } + } + + // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` + async fn enable_debugger(&mut self) -> Result<(), AnyError> { + self + .session + .post_message::<()>("Debugger.enable", None) + .await?; + self + .session + .post_message::<()>("Runtime.enable", None) + .await?; + Ok(()) + } + + // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` + async fn disable_debugger(&mut self) -> Result<(), AnyError> { + self + .session + .post_message::<()>("Debugger.disable", None) + .await?; + self + .session + .post_message::<()>("Runtime.disable", None) + .await?; + Ok(()) + } + + async fn set_script_source( + &mut self, + script_id: &str, + source: &str, + ) -> Result { + let result = self + .session + .post_message( + "Debugger.setScriptSource", + Some(json!({ + "scriptId": script_id, + "scriptSource": source, + "allowTopFrameEditing": true, + })), + ) + .await?; + + Ok(serde_json::from_value::( + result, + )?) + } + + async fn dispatch_hmr_event( + &mut self, + script_id: &str, + ) -> Result<(), AnyError> { + let expr = format!( + "dispatchEvent(new CustomEvent(\"hmr\", {{ detail: {{ path: \"{}\" }} }}));", + script_id + ); + + let _result = self + .session + .post_message( + "Runtime.evaluate", + Some(json!({ + "expression": expr, + "contextId": Some(1), + })), + ) + .await?; + + Ok(()) + } +} diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index 70141f5715..efe7e707eb 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -7,6 +7,7 @@ use crate::args::UpgradeFlags; use crate::colors; use crate::factory::CliFactory; use crate::http_util::HttpClient; +use crate::standalone::binary::unpack_into_dir; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::util::time; @@ -30,11 +31,11 @@ use std::process::Command; use std::sync::Arc; use std::time::Duration; -static ARCHIVE_NAME: Lazy = - Lazy::new(|| format!("deno-{}.zip", env!("TARGET"))); - const RELEASE_URL: &str = "https://github.com/denoland/deno/releases"; +pub static ARCHIVE_NAME: Lazy = + Lazy::new(|| format!("deno-{}.zip", env!("TARGET"))); + // How often query server for new version. In hours. const UPGRADE_CHECK_INTERVAL: i64 = 24; @@ -501,7 +502,13 @@ pub async fn upgrade( log::info!("Deno is upgrading to version {}", &install_version); let temp_dir = tempfile::TempDir::new()?; - let new_exe_path = unpack_into_dir(archive_data, cfg!(windows), &temp_dir)?; + let new_exe_path = unpack_into_dir( + "deno", + &ARCHIVE_NAME, + archive_data, + cfg!(windows), + &temp_dir, + )?; fs::set_permissions(&new_exe_path, permissions)?; check_exe(&new_exe_path)?; @@ -628,71 +635,6 @@ async fn download_package( } } -pub fn unpack_into_dir( - archive_data: Vec, - is_windows: bool, - temp_dir: &tempfile::TempDir, -) -> Result { - const EXE_NAME: &str = "deno"; - let temp_dir_path = temp_dir.path(); - let exe_ext = if is_windows { "exe" } else { "" }; - let archive_path = temp_dir_path.join(EXE_NAME).with_extension("zip"); - let exe_path = temp_dir_path.join(EXE_NAME).with_extension(exe_ext); - assert!(!exe_path.exists()); - - let archive_ext = Path::new(&*ARCHIVE_NAME) - .extension() - .and_then(|ext| ext.to_str()) - .unwrap(); - let unpack_status = match archive_ext { - "zip" if cfg!(windows) => { - fs::write(&archive_path, &archive_data)?; - Command::new("tar.exe") - .arg("xf") - .arg(&archive_path) - .arg("-C") - .arg(temp_dir_path) - .spawn() - .map_err(|err| { - if err.kind() == std::io::ErrorKind::NotFound { - std::io::Error::new( - std::io::ErrorKind::NotFound, - "`tar.exe` was not found in your PATH", - ) - } else { - err - } - })? - .wait()? - } - "zip" => { - fs::write(&archive_path, &archive_data)?; - Command::new("unzip") - .current_dir(temp_dir_path) - .arg(&archive_path) - .spawn() - .map_err(|err| { - if err.kind() == std::io::ErrorKind::NotFound { - std::io::Error::new( - std::io::ErrorKind::NotFound, - "`unzip` was not found in your PATH, please install `unzip`", - ) - } else { - err - } - })? - .wait()? - } - ext => bail!("Unsupported archive type: '{ext}'"), - }; - if !unpack_status.success() { - bail!("Failed to unpack archive."); - } - assert!(exe_path.exists()); - fs::remove_file(&archive_path)?; - Ok(exe_path) -} - fn replace_exe(from: &Path, to: &Path) -> Result<(), std::io::Error> { if cfg!(windows) { // On windows you cannot replace the currently running executable. diff --git a/cli/worker.rs b/cli/worker.rs index e897c1a86e..3f75ebc5c8 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -49,12 +49,8 @@ use tokio::select; use crate::args::package_json::PackageJsonDeps; use crate::args::DenoSubcommand; use crate::args::StorageKeyResolver; -use crate::emit::Emitter; use crate::errors; use crate::npm::CliNpmResolver; -use crate::tools; -use crate::tools::coverage::CoverageCollector; -use crate::tools::run::hmr::HmrRunner; use crate::util::checksum; use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherRestartMode; @@ -82,7 +78,29 @@ pub trait HasNodeSpecifierChecker: Send + Sync { fn has_node_specifier(&self) -> bool; } -#[derive(Clone)] +#[async_trait::async_trait(?Send)] +pub trait HmrRunner: Send + Sync { + async fn start(&mut self) -> Result<(), AnyError>; + async fn stop(&mut self) -> Result<(), AnyError>; + async fn run(&mut self) -> Result<(), AnyError>; +} + +#[async_trait::async_trait(?Send)] +pub trait CoverageCollector: Send + Sync { + async fn start_collecting(&mut self) -> Result<(), AnyError>; + async fn stop_collecting(&mut self) -> Result<(), AnyError>; +} + +pub type CreateHmrRunnerCb = Box< + dyn Fn(deno_core::LocalInspectorSession) -> Box + Send + Sync, +>; + +pub type CreateCoverageCollectorCb = Box< + dyn Fn(deno_core::LocalInspectorSession) -> Box + + Send + + Sync, +>; + pub struct CliMainWorkerOptions { pub argv: Vec, pub log_level: WorkerLogLevel, @@ -104,6 +122,8 @@ pub struct CliMainWorkerOptions { pub unstable: bool, pub skip_op_registration: bool, pub maybe_root_package_json_deps: Option, + pub create_hmr_runner: Option, + pub create_coverage_collector: Option, } struct SharedWorkerState { @@ -119,7 +139,6 @@ struct SharedWorkerState { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, - emitter: Option>, maybe_file_watcher_communicator: Option>, maybe_inspector_server: Option>, maybe_lockfile: Option>>, @@ -324,42 +343,20 @@ impl CliMainWorker { self.worker.evaluate_module(id).await } - pub async fn maybe_setup_coverage_collector( - &mut self, - ) -> Result, AnyError> { - if let Some(coverage_dir) = &self.shared.options.coverage_dir { - let session = self.worker.create_inspector_session().await; - - let coverage_dir = PathBuf::from(coverage_dir); - let mut coverage_collector = - tools::coverage::CoverageCollector::new(coverage_dir, session); - self - .worker - .js_runtime - .with_event_loop_future( - coverage_collector.start_collecting().boxed_local(), - PollEventLoopOptions::default(), - ) - .await?; - Ok(Some(coverage_collector)) - } else { - Ok(None) - } - } - pub async fn maybe_setup_hmr_runner( &mut self, - ) -> Result, AnyError> { + ) -> Result>, AnyError> { if !self.shared.options.hmr { return Ok(None); } - - let watcher_communicator = - self.shared.maybe_file_watcher_communicator.clone().unwrap(); - let emitter = self.shared.emitter.clone().unwrap(); + let Some(setup_hmr_runner) = self.shared.options.create_hmr_runner.as_ref() + else { + return Ok(None); + }; let session = self.worker.create_inspector_session().await; - let mut hmr_runner = HmrRunner::new(emitter, session, watcher_communicator); + + let mut hmr_runner = setup_hmr_runner(session); self .worker @@ -369,10 +366,31 @@ impl CliMainWorker { PollEventLoopOptions::default(), ) .await?; - Ok(Some(hmr_runner)) } + pub async fn maybe_setup_coverage_collector( + &mut self, + ) -> Result>, AnyError> { + let Some(create_coverage_collector) = + self.shared.options.create_coverage_collector.as_ref() + else { + return Ok(None); + }; + + let session = self.worker.create_inspector_session().await; + let mut coverage_collector = create_coverage_collector(session); + self + .worker + .js_runtime + .with_event_loop_future( + coverage_collector.start_collecting().boxed_local(), + PollEventLoopOptions::default(), + ) + .await?; + Ok(Some(coverage_collector)) + } + pub fn execute_script_static( &mut self, name: &'static str, @@ -400,7 +418,6 @@ impl CliMainWorkerFactory { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, - emitter: Option>, maybe_file_watcher_communicator: Option>, maybe_inspector_server: Option>, maybe_lockfile: Option>>, @@ -423,7 +440,6 @@ impl CliMainWorkerFactory { compiled_wasm_module_store: Default::default(), module_loader_factory, root_cert_store_provider, - emitter, fs, maybe_file_watcher_communicator, maybe_inspector_server, diff --git a/test_util/src/builders.rs b/test_util/src/builders.rs index 862838dcb3..9e9c64cf58 100644 --- a/test_util/src/builders.rs +++ b/test_util/src/builders.rs @@ -20,6 +20,7 @@ use os_pipe::pipe; use crate::assertions::assert_wildcard_match; use crate::deno_exe_path; +use crate::denort_exe_path; use crate::env_vars_for_jsr_tests; use crate::env_vars_for_npm_tests; use crate::fs::PathRef; @@ -80,7 +81,7 @@ pub struct TestContextBuilder { impl TestContextBuilder { pub fn new() -> Self { - Self::default() + Self::default().add_compile_env_vars() } pub fn for_npm() -> Self { @@ -158,6 +159,13 @@ impl TestContextBuilder { self } + pub fn add_compile_env_vars(mut self) -> Self { + // The `denort` binary is in the same artifact directory as the `deno` binary. + let denort_bin = denort_exe_path(); + self = self.env("DENORT_BIN", denort_bin.to_string()); + self + } + pub fn add_jsr_env_vars(mut self) -> Self { for (key, value) in env_vars_for_jsr_tests() { self = self.env(key, value); @@ -236,7 +244,7 @@ impl Default for TestContext { impl TestContext { pub fn with_http_server() -> Self { - TestContextBuilder::default().use_http_server().build() + TestContextBuilder::new().use_http_server().build() } pub fn deno_dir(&self) -> &TempDir { diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index b750cb99a6..f8fbb8b65e 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -140,6 +140,14 @@ pub fn deno_exe_path() -> PathRef { PathRef::new(p) } +pub fn denort_exe_path() -> PathRef { + let mut p = target_dir().join("denort").to_path_buf(); + if cfg!(windows) { + p.set_extension("exe"); + } + PathRef::new(p) +} + pub fn prebuilt_tool_path(tool: &str) -> PathRef { let mut exe = tool.to_string(); exe.push_str(if cfg!(windows) { ".exe" } else { "" });