2024-01-01 14:58:21 -05:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2023-04-19 17:50:56 -04:00
2024-03-31 10:58:19 -04:00
use std ::borrow ::Cow ;
2023-05-10 20:06:59 -04:00
use std ::collections ::BTreeMap ;
2024-07-09 17:33:41 -04:00
use std ::collections ::HashMap ;
2024-07-03 20:54:33 -04:00
use std ::collections ::VecDeque ;
2023-05-10 20:06:59 -04:00
use std ::env ::current_exe ;
2024-03-22 17:03:56 -04:00
use std ::ffi ::OsString ;
2024-02-13 11:22:30 -05:00
use std ::fs ;
2024-08-01 00:15:13 -04:00
use std ::fs ::File ;
2024-03-05 17:14:49 -05:00
use std ::future ::Future ;
2024-10-24 15:48:48 -04:00
use std ::io ::ErrorKind ;
2023-04-19 17:50:56 -04:00
use std ::io ::Read ;
use std ::io ::Seek ;
use std ::io ::SeekFrom ;
use std ::io ::Write ;
2024-10-24 15:48:48 -04:00
use std ::ops ::Range ;
2023-04-19 17:50:56 -04:00
use std ::path ::Path ;
2023-05-10 20:06:59 -04:00
use std ::path ::PathBuf ;
2024-02-13 11:22:30 -05:00
use std ::process ::Command ;
2024-10-24 15:48:48 -04:00
use std ::sync ::Arc ;
2023-04-19 17:50:56 -04:00
2024-10-24 15:48:48 -04:00
use deno_ast ::MediaType ;
2023-04-19 17:50:56 -04:00
use deno_ast ::ModuleSpecifier ;
2024-07-03 20:54:33 -04:00
use deno_config ::workspace ::PackageJsonDepResolution ;
2024-08-07 03:43:05 -04:00
use deno_config ::workspace ::ResolverWorkspaceJsrPackage ;
2024-07-03 20:54:33 -04:00
use deno_config ::workspace ::Workspace ;
2024-07-04 20:41:01 -04:00
use deno_config ::workspace ::WorkspaceResolver ;
2023-07-28 11:46:26 -04:00
use deno_core ::anyhow ::bail ;
2023-04-19 17:50:56 -04:00
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 ;
2024-10-24 15:48:48 -04:00
use deno_graph ::source ::RealFileSystem ;
use deno_graph ::ModuleGraph ;
use deno_npm ::resolution ::SerializedNpmResolutionSnapshot ;
use deno_npm ::resolution ::SerializedNpmResolutionSnapshotPackage ;
use deno_npm ::resolution ::ValidSerializedNpmResolutionSnapshot ;
use deno_npm ::NpmPackageId ;
2023-05-17 17:38:50 -04:00
use deno_npm ::NpmSystemInfo ;
2024-10-24 15:48:48 -04:00
use deno_runtime ::deno_fs ;
use deno_runtime ::deno_fs ::FileSystem ;
use deno_runtime ::deno_fs ::RealFs ;
use deno_runtime ::deno_io ::fs ::FsError ;
2024-07-03 20:54:33 -04:00
use deno_runtime ::deno_node ::PackageJson ;
2024-06-13 18:29:27 -04:00
use deno_semver ::npm ::NpmVersionReqParseError ;
2023-08-21 05:53:52 -04:00
use deno_semver ::package ::PackageReq ;
2024-08-07 03:43:05 -04:00
use deno_semver ::Version ;
2023-08-21 05:53:52 -04:00
use deno_semver ::VersionReqSpecifierParseError ;
2024-07-03 20:54:33 -04:00
use indexmap ::IndexMap ;
2023-04-19 17:50:56 -04:00
use log ::Level ;
use serde ::Deserialize ;
use serde ::Serialize ;
use crate ::args ::CaData ;
use crate ::args ::CliOptions ;
use crate ::args ::CompileFlags ;
2024-09-04 10:00:44 -04:00
use crate ::args ::NpmInstallDepsProvider ;
2024-05-06 19:21:58 -04:00
use crate ::args ::PermissionFlags ;
2024-01-22 12:37:28 -05:00
use crate ::args ::UnstableConfig ;
2023-04-19 17:50:56 -04:00
use crate ::cache ::DenoDir ;
2024-10-24 15:48:48 -04:00
use crate ::emit ::Emitter ;
2023-04-19 17:50:56 -04:00
use crate ::file_fetcher ::FileFetcher ;
2024-06-03 17:17:08 -04:00
use crate ::http_util ::HttpClientProvider ;
2023-05-10 20:06:59 -04:00
use crate ::npm ::CliNpmResolver ;
2023-09-29 09:26:25 -04:00
use crate ::npm ::InnerCliNpmResolverRef ;
2024-08-15 15:59:16 -04:00
use crate ::shared ::ReleaseChannel ;
2024-07-03 20:54:33 -04:00
use crate ::standalone ::virtual_fs ::VfsEntry ;
2024-08-08 03:19:05 -04:00
use crate ::util ::archive ;
2024-07-03 20:54:33 -04:00
use crate ::util ::fs ::canonicalize_path_maybe_not_exists ;
2023-04-19 17:50:56 -04:00
use crate ::util ::progress_bar ::ProgressBar ;
use crate ::util ::progress_bar ::ProgressBarStyle ;
2024-10-24 15:48:48 -04:00
use super ::file_system ::DenoCompileFileSystem ;
use super ::serialization ::deserialize_binary_data_section ;
use super ::serialization ::serialize_binary_data_section ;
use super ::serialization ::DenoCompileModuleData ;
use super ::serialization ::DeserializedDataSection ;
use super ::serialization ::RemoteModulesStore ;
use super ::serialization ::RemoteModulesStoreBuilder ;
2023-05-10 20:06:59 -04:00
use super ::virtual_fs ::FileBackedVfs ;
use super ::virtual_fs ::VfsBuilder ;
use super ::virtual_fs ::VfsRoot ;
use super ::virtual_fs ::VirtualDirectory ;
2024-10-24 15:48:48 -04:00
/// A URL that can be designated as the base for relative URLs.
///
/// After creation, this URL may be used to get the key for a
/// module in the binary.
#[ derive(Debug, Clone, Copy, PartialEq, Eq, Hash) ]
pub struct StandaloneRelativeFileBaseUrl < ' a > ( & ' a Url ) ;
impl < ' a > From < & ' a Url > for StandaloneRelativeFileBaseUrl < ' a > {
fn from ( url : & ' a Url ) -> Self {
Self ( url )
}
}
impl < ' a > StandaloneRelativeFileBaseUrl < ' a > {
pub fn new ( url : & ' a Url ) -> Self {
debug_assert_eq! ( url . scheme ( ) , " file " ) ;
Self ( url )
}
/// Gets the module map key of the provided specifier.
///
/// * Descendant file specifiers will be made relative to the base.
/// * Non-descendant file specifiers will stay as-is (absolute).
/// * Non-file specifiers will stay as-is.
pub fn specifier_key < ' b > ( & self , target : & ' b Url ) -> Cow < ' b , str > {
if target . scheme ( ) ! = " file " {
return Cow ::Borrowed ( target . as_str ( ) ) ;
}
match self . 0. make_relative ( target ) {
Some ( relative ) = > {
if relative . starts_with ( " ../ " ) {
Cow ::Borrowed ( target . as_str ( ) )
} else {
Cow ::Owned ( relative )
}
}
None = > Cow ::Borrowed ( target . as_str ( ) ) ,
}
}
pub fn inner ( & self ) -> & Url {
self . 0
}
}
2023-04-19 17:50:56 -04:00
2023-11-29 09:32:23 -05:00
#[ derive(Deserialize, Serialize) ]
pub enum NodeModules {
Managed {
2024-07-03 20:54:33 -04:00
/// Relative path for the node_modules directory in the vfs.
node_modules_dir : Option < String > ,
2023-11-29 09:32:23 -05:00
} ,
Byonm {
2024-07-10 14:46:25 -04:00
root_node_modules_dir : Option < String > ,
2023-11-29 09:32:23 -05:00
} ,
}
2024-07-03 20:54:33 -04:00
#[ derive(Deserialize, Serialize) ]
pub struct SerializedWorkspaceResolverImportMap {
pub specifier : String ,
pub json : String ,
}
2024-08-07 03:43:05 -04:00
#[ derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize) ]
pub struct SerializedResolverWorkspaceJsrPackage {
pub relative_base : String ,
pub name : String ,
pub version : Option < Version > ,
pub exports : IndexMap < String , String > ,
}
2024-07-03 20:54:33 -04:00
#[ derive(Deserialize, Serialize) ]
pub struct SerializedWorkspaceResolver {
pub import_map : Option < SerializedWorkspaceResolverImportMap > ,
2024-08-07 03:43:05 -04:00
pub jsr_pkgs : Vec < SerializedResolverWorkspaceJsrPackage > ,
2024-07-03 20:54:33 -04:00
pub package_jsons : BTreeMap < String , serde_json ::Value > ,
pub pkg_json_resolution : PackageJsonDepResolution ,
}
2024-08-19 12:41:11 -04:00
// Note: Don't use hashmaps/hashsets. Ensure the serialization
// is deterministic.
2023-04-19 17:50:56 -04:00
#[ derive(Deserialize, Serialize) ]
pub struct Metadata {
pub argv : Vec < String > ,
pub seed : Option < u64 > ,
2024-05-06 19:21:58 -04:00
pub permissions : PermissionFlags ,
2023-04-19 17:50:56 -04:00
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 > > ,
2024-08-19 12:41:11 -04:00
pub env_vars_from_env_file : IndexMap < String , String > ,
2024-07-03 20:54:33 -04:00
pub workspace_resolver : SerializedWorkspaceResolver ,
pub entrypoint_key : String ,
2023-11-29 09:32:23 -05:00
pub node_modules : Option < NodeModules > ,
2024-01-22 12:37:28 -05:00
pub unstable_config : UnstableConfig ,
2023-05-10 20:06:59 -04:00
}
fn write_binary_bytes (
2024-08-01 00:15:13 -04:00
mut file_writer : File ,
2023-04-19 17:50:56 -04:00
original_bin : Vec < u8 > ,
metadata : & Metadata ,
2024-10-24 15:48:48 -04:00
npm_snapshot : Option < SerializedNpmResolutionSnapshot > ,
remote_modules : & RemoteModulesStoreBuilder ,
vfs : VfsBuilder ,
2024-08-01 00:15:13 -04:00
compile_flags : & CompileFlags ,
2023-04-19 17:50:56 -04:00
) -> Result < ( ) , AnyError > {
2024-10-24 15:48:48 -04:00
let data_section_bytes =
serialize_binary_data_section ( metadata , npm_snapshot , remote_modules , vfs ) ? ;
2024-08-01 00:15:13 -04:00
let target = compile_flags . resolve_target ( ) ;
if target . contains ( " linux " ) {
2024-08-15 00:42:23 -04:00
libsui ::Elf ::new ( & original_bin ) . append (
" d3n0l4nd " ,
2024-10-24 15:48:48 -04:00
& data_section_bytes ,
2024-08-15 00:42:23 -04:00
& mut file_writer ,
) ? ;
2024-08-01 00:15:13 -04:00
} else if target . contains ( " windows " ) {
2024-08-15 00:42:23 -04:00
let mut pe = libsui ::PortableExecutable ::from ( & original_bin ) ? ;
if let Some ( icon ) = compile_flags . icon . as_ref ( ) {
let icon = std ::fs ::read ( icon ) ? ;
pe = pe . set_icon ( & icon ) ? ;
}
2024-10-24 15:48:48 -04:00
pe . write_resource ( " d3n0l4nd " , data_section_bytes ) ?
2024-08-01 00:15:13 -04:00
. build ( & mut file_writer ) ? ;
} else if target . contains ( " darwin " ) {
libsui ::Macho ::from ( original_bin ) ?
2024-10-24 15:48:48 -04:00
. write_section ( " d3n0l4nd " , data_section_bytes ) ?
2024-08-01 05:11:24 -04:00
. build_and_sign ( & mut file_writer ) ? ;
2024-08-01 00:15:13 -04:00
}
2023-04-19 17:50:56 -04:00
Ok ( ( ) )
}
pub fn is_standalone_binary ( exe_path : & Path ) -> bool {
2024-08-01 00:15:13 -04:00
let Ok ( data ) = std ::fs ::read ( exe_path ) else {
2023-04-19 17:50:56 -04:00
return false ;
} ;
2024-08-01 00:15:13 -04:00
libsui ::utils ::is_elf ( & data )
2024-08-01 05:11:24 -04:00
| | libsui ::utils ::is_pe ( & data )
| | libsui ::utils ::is_macho ( & data )
2023-04-19 17:50:56 -04:00
}
2024-10-24 15:48:48 -04:00
pub struct StandaloneData {
pub fs : Arc < dyn deno_fs ::FileSystem > ,
pub metadata : Metadata ,
pub modules : StandaloneModules ,
pub npm_snapshot : Option < ValidSerializedNpmResolutionSnapshot > ,
pub root_path : PathBuf ,
pub vfs : Arc < FileBackedVfs > ,
}
pub struct StandaloneModules {
remote_modules : RemoteModulesStore ,
vfs : Arc < FileBackedVfs > ,
}
impl StandaloneModules {
pub fn resolve_specifier < ' a > (
& ' a self ,
specifier : & ' a ModuleSpecifier ,
) -> Result < Option < & ' a ModuleSpecifier > , AnyError > {
if specifier . scheme ( ) = = " file " {
Ok ( Some ( specifier ) )
} else {
self . remote_modules . resolve_specifier ( specifier )
}
}
pub fn read < ' a > (
& ' a self ,
specifier : & ' a ModuleSpecifier ,
) -> Result < Option < DenoCompileModuleData < ' a > > , AnyError > {
if specifier . scheme ( ) = = " file " {
let path = deno_path_util ::url_to_file_path ( specifier ) ? ;
let bytes = match self . vfs . file_entry ( & path ) {
Ok ( entry ) = > self . vfs . read_file_all ( entry ) ? ,
Err ( err ) if err . kind ( ) = = ErrorKind ::NotFound = > {
let bytes = match RealFs . read_file_sync ( & path , None ) {
Ok ( bytes ) = > bytes ,
Err ( FsError ::Io ( err ) ) if err . kind ( ) = = ErrorKind ::NotFound = > {
return Ok ( None )
}
Err ( err ) = > return Err ( err . into ( ) ) ,
} ;
Cow ::Owned ( bytes )
}
Err ( err ) = > return Err ( err . into ( ) ) ,
} ;
Ok ( Some ( DenoCompileModuleData {
media_type : MediaType ::from_specifier ( specifier ) ,
specifier ,
data : bytes ,
} ) )
} else {
self . remote_modules . read ( specifier )
}
}
}
2023-04-19 17:50:56 -04:00
/// This function will try to run this binary as a standalone binary
/// produced by `deno compile`. It determines if this is a standalone
2023-05-10 20:06:59 -04:00
/// binary by skipping over the trailer width at the end of the file,
/// then checking for the magic trailer string `d3n0l4nd`. If found,
/// the bundle is executed. If not, this function exits with `Ok(None)`.
2024-03-05 17:14:49 -05:00
pub fn extract_standalone (
2024-03-31 10:58:19 -04:00
cli_args : Cow < Vec < OsString > > ,
2024-10-24 15:48:48 -04:00
) -> Result < Option < StandaloneData > , AnyError > {
2024-08-01 00:15:13 -04:00
let Some ( data ) = libsui ::find_section ( " d3n0l4nd " ) else {
return Ok ( None ) ;
} ;
2024-10-24 15:48:48 -04:00
let DeserializedDataSection {
mut metadata ,
npm_snapshot ,
remote_modules ,
mut vfs_dir ,
vfs_files_data ,
} = match deserialize_binary_data_section ( data ) ? {
Some ( data_section ) = > data_section ,
2023-05-10 20:06:59 -04:00
None = > return Ok ( None ) ,
} ;
2023-04-19 17:50:56 -04:00
2024-10-24 15:48:48 -04:00
let root_path = {
let maybe_current_exe = std ::env ::current_exe ( ) . ok ( ) ;
let current_exe_name = maybe_current_exe
. as_ref ( )
. and_then ( | p | p . file_name ( ) )
. map ( | p | p . to_string_lossy ( ) )
// should never happen
. unwrap_or_else ( | | Cow ::Borrowed ( " binary " ) ) ;
std ::env ::temp_dir ( ) . join ( format! ( " deno-compile- {} " , current_exe_name ) )
} ;
2024-03-31 10:58:19 -04:00
let cli_args = cli_args . into_owned ( ) ;
2024-10-24 15:48:48 -04:00
metadata . argv . reserve ( cli_args . len ( ) - 1 ) ;
for arg in cli_args . into_iter ( ) . skip ( 1 ) {
metadata . argv . push ( arg . into_string ( ) . unwrap ( ) ) ;
2023-05-10 20:06:59 -04:00
}
2024-10-24 15:48:48 -04:00
let vfs = {
// align the name of the directory with the root dir
vfs_dir . name = root_path . file_name ( ) . unwrap ( ) . to_string_lossy ( ) . to_string ( ) ;
let fs_root = VfsRoot {
dir : vfs_dir ,
root_path : root_path . clone ( ) ,
start_file_offset : 0 ,
} ;
Arc ::new ( FileBackedVfs ::new ( Cow ::Borrowed ( vfs_files_data ) , fs_root ) )
} ;
let fs : Arc < dyn deno_fs ::FileSystem > =
Arc ::new ( DenoCompileFileSystem ::new ( vfs . clone ( ) ) ) ;
Ok ( Some ( StandaloneData {
fs ,
metadata ,
modules : StandaloneModules {
remote_modules ,
vfs : vfs . clone ( ) ,
} ,
npm_snapshot ,
root_path ,
vfs ,
} ) )
2023-04-19 17:50:56 -04:00
}
2023-05-01 14:35:23 -04:00
pub struct DenoCompileBinaryWriter < ' a > {
deno_dir : & ' a DenoDir ,
2024-10-24 15:48:48 -04:00
emitter : & ' a Emitter ,
2024-06-03 17:17:08 -04:00
file_fetcher : & ' a FileFetcher ,
http_client_provider : & ' a HttpClientProvider ,
2023-09-29 09:26:25 -04:00
npm_resolver : & ' a dyn CliNpmResolver ,
2024-07-04 20:41:01 -04:00
workspace_resolver : & ' a WorkspaceResolver ,
2023-05-17 17:38:50 -04:00
npm_system_info : NpmSystemInfo ,
2023-04-19 17:50:56 -04:00
}
2023-05-01 14:35:23 -04:00
impl < ' a > DenoCompileBinaryWriter < ' a > {
2023-05-10 20:06:59 -04:00
#[ allow(clippy::too_many_arguments) ]
2023-04-19 17:50:56 -04:00
pub fn new (
2023-05-01 14:35:23 -04:00
deno_dir : & ' a DenoDir ,
2024-10-24 15:48:48 -04:00
emitter : & ' a Emitter ,
2024-06-03 17:17:08 -04:00
file_fetcher : & ' a FileFetcher ,
http_client_provider : & ' a HttpClientProvider ,
2023-09-29 09:26:25 -04:00
npm_resolver : & ' a dyn CliNpmResolver ,
2024-07-04 20:41:01 -04:00
workspace_resolver : & ' a WorkspaceResolver ,
2023-05-17 17:38:50 -04:00
npm_system_info : NpmSystemInfo ,
2023-04-19 17:50:56 -04:00
) -> Self {
Self {
deno_dir ,
2024-10-24 15:48:48 -04:00
emitter ,
2024-06-03 17:17:08 -04:00
file_fetcher ,
http_client_provider ,
2023-05-10 20:06:59 -04:00
npm_resolver ,
2024-07-04 20:41:01 -04:00
workspace_resolver ,
2023-05-17 17:38:50 -04:00
npm_system_info ,
2023-04-19 17:50:56 -04:00
}
}
pub async fn write_bin (
& self ,
2024-08-01 00:15:13 -04:00
writer : File ,
2024-10-24 15:48:48 -04:00
graph : & ModuleGraph ,
root_dir_url : StandaloneRelativeFileBaseUrl < '_ > ,
2024-07-03 20:54:33 -04:00
entrypoint : & ModuleSpecifier ,
2023-04-19 17:50:56 -04:00
compile_flags : & CompileFlags ,
cli_options : & CliOptions ,
) -> Result < ( ) , AnyError > {
// Select base binary based on target
2023-07-29 08:06:47 -04:00
let mut original_binary = self . get_base_binary ( compile_flags ) . await ? ;
2023-07-28 11:46:26 -04:00
if compile_flags . no_terminal {
2023-07-29 08:06:47 -04:00
let target = compile_flags . resolve_target ( ) ;
if ! target . contains ( " windows " ) {
2023-07-28 11:46:26 -04:00
bail! (
2023-07-29 08:06:47 -04:00
" The `--no-terminal` flag is only available when targeting Windows (current: {}) " ,
target ,
2023-07-28 11:46:26 -04:00
)
}
set_windows_binary_to_gui ( & mut original_binary ) ? ;
}
2024-08-15 00:42:23 -04:00
if compile_flags . icon . is_some ( ) {
let target = compile_flags . resolve_target ( ) ;
if ! target . contains ( " windows " ) {
bail! (
" The `--icon` flag is only available when targeting Windows (current: {}) " ,
target ,
)
}
}
2024-10-24 15:48:48 -04:00
self
. write_standalone_binary (
writer ,
original_binary ,
graph ,
root_dir_url ,
entrypoint ,
cli_options ,
compile_flags ,
)
. await
2023-04-19 17:50:56 -04:00
}
async fn get_base_binary (
& self ,
2023-07-29 08:06:47 -04:00
compile_flags : & CompileFlags ,
2023-04-19 17:50:56 -04:00
) -> Result < Vec < u8 > , AnyError > {
2024-02-13 11:22:30 -05:00
// 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 " ) {
2024-04-27 17:11:57 -04:00
return std ::fs ::read ( & path ) . with_context ( | | {
format! ( " Could not find denort at ' {} ' " , path . to_string_lossy ( ) )
} ) ;
2023-04-19 17:50:56 -04:00
}
2023-07-29 08:06:47 -04:00
let target = compile_flags . resolve_target ( ) ;
2024-02-13 11:22:30 -05:00
let binary_name = format! ( " denort- {target} .zip " ) ;
2023-04-19 17:50:56 -04:00
2024-08-15 17:47:16 -04:00
let binary_path_suffix =
match crate ::version ::DENO_VERSION_INFO . release_channel {
ReleaseChannel ::Canary = > {
format! (
" canary/{}/{} " ,
crate ::version ::DENO_VERSION_INFO . git_hash ,
binary_name
)
}
2024-09-25 20:40:35 -04:00
_ = > {
2024-08-15 17:47:16 -04:00
format! ( " release/v {} / {} " , env! ( " CARGO_PKG_VERSION " ) , binary_name )
}
} ;
2023-04-19 17:50:56 -04:00
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 ( ) ? ;
2024-08-08 03:19:05 -04:00
let base_binary_path = archive ::unpack_into_dir ( archive ::UnpackArgs {
exe_name : " denort " ,
archive_name : & binary_name ,
archive_data : & archive_data ,
is_windows : target . contains ( " windows " ) ,
dest_path : temp_dir . path ( ) ,
} ) ? ;
2023-04-19 17:50:56 -04:00
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
2024-06-03 17:17:08 -04:00
. http_client_provider
. get_or_create ( ) ?
2024-10-15 19:46:42 -04:00
. download_with_progress_and_retries (
download_url . parse ( ) ? ,
None ,
& progress ,
)
2023-04-19 17:50:56 -04:00
. 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.
2024-07-03 20:54:33 -04:00
#[ allow(clippy::too_many_arguments) ]
2024-10-24 15:48:48 -04:00
async fn write_standalone_binary (
2023-04-19 17:50:56 -04:00
& self ,
2024-08-01 00:15:13 -04:00
writer : File ,
2023-04-19 17:50:56 -04:00
original_bin : Vec < u8 > ,
2024-10-24 15:48:48 -04:00
graph : & ModuleGraph ,
root_dir_url : StandaloneRelativeFileBaseUrl < '_ > ,
2023-04-19 17:50:56 -04:00
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 ,
} ;
2024-07-03 20:54:33 -04:00
let root_path = root_dir_url . inner ( ) . to_file_path ( ) . unwrap ( ) ;
2024-10-24 15:48:48 -04:00
let ( maybe_npm_vfs , node_modules , npm_snapshot ) = match self
. npm_resolver
. as_inner ( )
2024-07-03 20:54:33 -04:00
{
InnerCliNpmResolverRef ::Managed ( managed ) = > {
let snapshot =
managed . serialized_valid_snapshot_for_system ( & self . npm_system_info ) ;
if ! snapshot . as_serialized ( ) . packages . is_empty ( ) {
2024-10-24 15:48:48 -04:00
let npm_vfs_builder = self . build_npm_vfs ( & root_path , cli_options ) ? ;
2023-11-29 09:32:23 -05:00
(
2024-10-24 15:48:48 -04:00
Some ( npm_vfs_builder ) ,
2024-07-03 20:54:33 -04:00
Some ( NodeModules ::Managed {
node_modules_dir : self . npm_resolver . root_node_modules_path ( ) . map (
| path | {
root_dir_url
. specifier_key (
& ModuleSpecifier ::from_directory_path ( path ) . unwrap ( ) ,
)
. into_owned ( )
} ,
2023-11-29 09:32:23 -05:00
) ,
} ) ,
2024-10-24 15:48:48 -04:00
Some ( snapshot ) ,
2023-11-29 09:32:23 -05:00
)
2024-07-03 20:54:33 -04:00
} else {
2024-10-24 15:48:48 -04:00
( None , None , None )
2023-10-02 17:53:55 -04:00
}
2024-07-03 20:54:33 -04:00
}
InnerCliNpmResolverRef ::Byonm ( resolver ) = > {
2024-10-24 15:48:48 -04:00
let npm_vfs_builder = self . build_npm_vfs ( & root_path , cli_options ) ? ;
2024-07-03 20:54:33 -04:00
(
2024-10-24 15:48:48 -04:00
Some ( npm_vfs_builder ) ,
2024-07-03 20:54:33 -04:00
Some ( NodeModules ::Byonm {
2024-07-10 14:46:25 -04:00
root_node_modules_dir : resolver . root_node_modules_path ( ) . map (
| node_modules_dir | {
root_dir_url
. specifier_key (
& ModuleSpecifier ::from_directory_path ( node_modules_dir )
. unwrap ( ) ,
)
. into_owned ( )
} ,
) ,
2024-07-03 20:54:33 -04:00
} ) ,
2024-10-24 15:48:48 -04:00
None ,
2024-07-03 20:54:33 -04:00
)
}
} ;
2024-10-24 15:48:48 -04:00
let mut vfs = if let Some ( npm_vfs ) = maybe_npm_vfs {
npm_vfs
} else {
VfsBuilder ::new ( root_path . clone ( ) ) ?
} ;
let mut remote_modules_store = RemoteModulesStoreBuilder ::default ( ) ;
for module in graph . modules ( ) {
if module . specifier ( ) . scheme ( ) = = " data " {
continue ; // don't store data urls as an entry as they're in the code
}
let ( maybe_source , media_type ) = match module {
deno_graph ::Module ::Js ( m ) = > {
// todo(https://github.com/denoland/deno_media_type/pull/12): use is_emittable()
let is_emittable = matches! (
m . media_type ,
MediaType ::TypeScript
| MediaType ::Mts
| MediaType ::Cts
| MediaType ::Jsx
| MediaType ::Tsx
) ;
let source = if is_emittable {
let source = self
. emitter
. emit_parsed_source ( & m . specifier , m . media_type , & m . source )
. await ? ;
source . to_vec ( )
} else {
m . source . as_bytes ( ) . to_vec ( )
} ;
( Some ( source ) , m . media_type )
}
deno_graph ::Module ::Json ( m ) = > {
( Some ( m . source . as_bytes ( ) . to_vec ( ) ) , m . media_type )
}
deno_graph ::Module ::Npm ( _ )
| deno_graph ::Module ::Node ( _ )
| deno_graph ::Module ::External ( _ ) = > ( None , MediaType ::Unknown ) ,
} ;
if module . specifier ( ) . scheme ( ) = = " file " {
let file_path = deno_path_util ::url_to_file_path ( module . specifier ( ) ) ? ;
vfs
. add_file_with_data (
& file_path ,
match maybe_source {
Some ( source ) = > source ,
None = > RealFs . read_file_sync ( & file_path , None ) ? ,
} ,
)
. with_context ( | | {
format! ( " Failed adding ' {} ' " , file_path . display ( ) )
} ) ? ;
} else if let Some ( source ) = maybe_source {
remote_modules_store . add ( module . specifier ( ) , media_type , source ) ;
}
}
remote_modules_store . add_redirects ( & graph . redirects ) ;
2023-05-10 20:06:59 -04:00
2024-07-09 17:33:41 -04:00
let env_vars_from_env_file = match cli_options . env_file_name ( ) {
Some ( env_filename ) = > {
log ::info! ( " {} Environment variables from the file \" {} \" were embedded in the generated executable file " , crate ::colors ::yellow ( " Warning " ) , env_filename ) ;
get_file_env_vars ( env_filename . to_string ( ) ) ?
}
None = > Default ::default ( ) ,
} ;
2023-04-19 17:50:56 -04:00
let metadata = Metadata {
argv : compile_flags . args . clone ( ) ,
seed : cli_options . seed ( ) ,
location : cli_options . location_flag ( ) . clone ( ) ,
2024-05-06 19:21:58 -04:00
permissions : cli_options . permission_flags ( ) . clone ( ) ,
2023-04-19 17:50:56 -04:00
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 ,
2024-07-09 17:33:41 -04:00
env_vars_from_env_file ,
2024-07-03 20:54:33 -04:00
entrypoint_key : root_dir_url . specifier_key ( entrypoint ) . into_owned ( ) ,
workspace_resolver : SerializedWorkspaceResolver {
2024-07-04 20:41:01 -04:00
import_map : self . workspace_resolver . maybe_import_map ( ) . map ( | i | {
2024-07-03 20:54:33 -04:00
SerializedWorkspaceResolverImportMap {
specifier : if i . base_url ( ) . scheme ( ) = = " file " {
root_dir_url . specifier_key ( i . base_url ( ) ) . into_owned ( )
} else {
// just make a remote url local
" deno.json " . to_string ( )
} ,
json : i . to_json ( ) ,
}
} ) ,
2024-08-07 03:43:05 -04:00
jsr_pkgs : self
. workspace_resolver
. jsr_packages ( )
. map ( | pkg | SerializedResolverWorkspaceJsrPackage {
relative_base : root_dir_url . specifier_key ( & pkg . base ) . into_owned ( ) ,
name : pkg . name . clone ( ) ,
version : pkg . version . clone ( ) ,
exports : pkg . exports . clone ( ) ,
} )
. collect ( ) ,
2024-07-04 20:41:01 -04:00
package_jsons : self
. workspace_resolver
2024-07-03 20:54:33 -04:00
. package_jsons ( )
. map ( | pkg_json | {
(
root_dir_url
. specifier_key ( & pkg_json . specifier ( ) )
. into_owned ( ) ,
serde_json ::to_value ( pkg_json ) . unwrap ( ) ,
)
} )
. collect ( ) ,
2024-07-04 20:41:01 -04:00
pkg_json_resolution : self . workspace_resolver . pkg_json_dep_resolution ( ) ,
2024-07-03 20:54:33 -04:00
} ,
2023-11-29 09:32:23 -05:00
node_modules ,
2024-01-22 12:37:28 -05:00
unstable_config : UnstableConfig {
2024-09-09 17:44:29 -04:00
legacy_flag_enabled : false ,
2024-01-22 12:37:28 -05:00
bare_node_builtins : cli_options . unstable_bare_node_builtins ( ) ,
2024-10-14 20:48:39 -04:00
detect_cjs : cli_options . unstable_detect_cjs ( ) ,
2024-01-22 12:37:28 -05:00
sloppy_imports : cli_options . unstable_sloppy_imports ( ) ,
features : cli_options . unstable_features ( ) ,
} ,
2023-04-19 17:50:56 -04:00
} ;
2023-05-10 20:06:59 -04:00
write_binary_bytes (
writer ,
original_bin ,
& metadata ,
2024-10-24 15:48:48 -04:00
npm_snapshot . map ( | s | s . into_serialized ( ) ) ,
& remote_modules_store ,
vfs ,
2024-08-01 00:15:13 -04:00
compile_flags ,
2023-05-10 20:06:59 -04:00
)
}
2024-10-24 15:48:48 -04:00
fn build_npm_vfs (
2024-07-03 20:54:33 -04:00
& self ,
root_path : & Path ,
cli_options : & CliOptions ,
) -> Result < VfsBuilder , AnyError > {
2023-12-06 16:25:24 -05:00
fn maybe_warn_different_system ( system_info : & NpmSystemInfo ) {
if system_info ! = & NpmSystemInfo ::default ( ) {
log ::warn! ( " {} The node_modules directory may be incompatible with the target system. " , crate ::colors ::yellow ( " Warning " ) ) ;
}
}
2023-09-29 09:26:25 -04:00
match self . npm_resolver . as_inner ( ) {
InnerCliNpmResolverRef ::Managed ( npm_resolver ) = > {
2023-10-03 19:05:06 -04:00
if let Some ( node_modules_path ) = npm_resolver . root_node_modules_path ( ) {
2023-12-06 16:25:24 -05:00
maybe_warn_different_system ( & self . npm_system_info ) ;
2024-07-03 20:54:33 -04:00
let mut builder = VfsBuilder ::new ( root_path . to_path_buf ( ) ) ? ;
2023-11-29 09:32:23 -05:00
builder . add_dir_recursive ( node_modules_path ) ? ;
2023-09-29 09:26:25 -04:00
Ok ( builder )
} else {
// DO NOT include the user's registry url as it may contain credentials,
// but also don't make this dependent on the registry url
2024-10-24 15:48:48 -04:00
let global_cache_root_path = npm_resolver . global_cache_root_folder ( ) ;
let mut builder = VfsBuilder ::new ( global_cache_root_path ) ? ;
2024-08-19 12:41:11 -04:00
let mut packages =
npm_resolver . all_system_packages ( & self . npm_system_info ) ;
packages . sort_by ( | a , b | a . id . cmp ( & b . id ) ) ; // determinism
for package in packages {
2023-09-29 09:26:25 -04:00
let folder =
npm_resolver . resolve_pkg_folder_from_pkg_id ( & package . id ) ? ;
builder . add_dir_recursive ( & folder ) ? ;
}
2024-07-03 20:54:33 -04:00
2024-10-24 15:48:48 -04:00
// Flatten all the registries folders into a single ".deno_compile_node_modules/localhost" folder
2024-07-03 20:54:33 -04:00
// that will be used by denort when loading the npm cache. This avoids us exposing
// the user's private registry information and means we don't have to bother
// serializing all the different registry config into the binary.
builder . with_root_dir ( | root_dir | {
2024-10-24 15:48:48 -04:00
root_dir . name = " .deno_compile_node_modules " . to_string ( ) ;
2024-07-03 20:54:33 -04:00
let mut new_entries = Vec ::with_capacity ( root_dir . entries . len ( ) ) ;
let mut localhost_entries = IndexMap ::new ( ) ;
for entry in std ::mem ::take ( & mut root_dir . entries ) {
match entry {
VfsEntry ::Dir ( dir ) = > {
for entry in dir . entries {
log ::debug! (
" Flattening {} into node_modules " ,
entry . name ( )
) ;
if let Some ( existing ) =
localhost_entries . insert ( entry . name ( ) . to_string ( ) , entry )
{
panic! (
" Unhandled scenario where a duplicate entry was found: {:?} " ,
existing
) ;
}
}
}
VfsEntry ::File ( _ ) | VfsEntry ::Symlink ( _ ) = > {
new_entries . push ( entry ) ;
}
}
}
new_entries . push ( VfsEntry ::Dir ( VirtualDirectory {
name : " localhost " . to_string ( ) ,
entries : localhost_entries . into_iter ( ) . map ( | ( _ , v ) | v ) . collect ( ) ,
} ) ) ;
// needs to be sorted by name
new_entries . sort_by ( | a , b | a . name ( ) . cmp ( b . name ( ) ) ) ;
root_dir . entries = new_entries ;
} ) ;
2024-10-24 15:48:48 -04:00
builder . set_new_root_path ( root_path . to_path_buf ( ) ) ? ;
2023-09-29 09:26:25 -04:00
Ok ( builder )
}
}
2024-07-03 20:54:33 -04:00
InnerCliNpmResolverRef ::Byonm ( _ ) = > {
2023-12-06 16:25:24 -05:00
maybe_warn_different_system ( & self . npm_system_info ) ;
2024-07-03 20:54:33 -04:00
let mut builder = VfsBuilder ::new ( root_path . to_path_buf ( ) ) ? ;
2024-07-19 15:56:07 -04:00
for pkg_json in cli_options . workspace ( ) . package_jsons ( ) {
2024-07-03 20:54:33 -04:00
builder . add_file_at_path ( & pkg_json . path ) ? ;
2023-11-29 09:32:23 -05:00
}
2024-07-03 20:54:33 -04:00
// traverse and add all the node_modules directories in the workspace
let mut pending_dirs = VecDeque ::new ( ) ;
pending_dirs . push_back (
2024-07-19 15:56:07 -04:00
cli_options . workspace ( ) . root_dir ( ) . to_file_path ( ) . unwrap ( ) ,
2024-07-03 20:54:33 -04:00
) ;
while let Some ( pending_dir ) = pending_dirs . pop_front ( ) {
2024-08-19 12:41:11 -04:00
let mut entries = fs ::read_dir ( & pending_dir )
. with_context ( | | {
format! ( " Failed reading: {} " , pending_dir . display ( ) )
} ) ?
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
entries . sort_by_cached_key ( | entry | entry . file_name ( ) ) ; // determinism
2024-07-03 20:54:33 -04:00
for entry in entries {
let path = entry . path ( ) ;
if ! path . is_dir ( ) {
continue ;
}
if path . ends_with ( " node_modules " ) {
builder . add_dir_recursive ( & path ) ? ;
} else {
pending_dirs . push_back ( path ) ;
}
}
2023-11-29 09:32:23 -05:00
}
Ok ( builder )
2023-05-10 20:06:59 -04:00
}
}
2023-04-19 17:50:56 -04:00
}
}
2023-07-28 11:46:26 -04:00
2024-07-09 17:33:41 -04:00
/// This function returns the environment variables specified
/// in the passed environment file.
fn get_file_env_vars (
filename : String ,
2024-08-19 12:41:11 -04:00
) -> Result < IndexMap < String , String > , dotenvy ::Error > {
let mut file_env_vars = IndexMap ::new ( ) ;
2024-07-09 17:33:41 -04:00
for item in dotenvy ::from_filename_iter ( filename ) ? {
let Ok ( ( key , val ) ) = item else {
continue ; // this failure will be warned about on load
} ;
file_env_vars . insert ( key , val ) ;
}
Ok ( file_env_vars )
}
2023-07-28 11:46:26 -04:00
/// This function sets the subsystem field in the PE header to 2 (GUI subsystem)
/// For more information about the PE header: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
fn set_windows_binary_to_gui ( bin : & mut [ u8 ] ) -> Result < ( ) , AnyError > {
// Get the PE header offset located in an i32 found at offset 60
// See: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only
let start_pe = u32 ::from_le_bytes ( ( bin [ 60 .. 64 ] ) . try_into ( ) ? ) ;
// Get image type (PE32 or PE32+) indicates whether the binary is 32 or 64 bit
// The used offset and size values can be found here:
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only
let start_32 = start_pe as usize + 28 ;
let magic_32 =
u16 ::from_le_bytes ( bin [ ( start_32 ) .. ( start_32 + 2 ) ] . try_into ( ) ? ) ;
let start_64 = start_pe as usize + 24 ;
let magic_64 =
u16 ::from_le_bytes ( bin [ ( start_64 ) .. ( start_64 + 2 ) ] . try_into ( ) ? ) ;
// Take the standard fields size for the current architecture (32 or 64 bit)
// This is the ofset for the Windows-Specific fields
let standard_fields_size = if magic_32 = = 0x10b {
28
} else if magic_64 = = 0x20b {
24
} else {
bail! ( " Could not find a matching magic field in the PE header " )
} ;
// Set the subsystem field (offset 68) to 2 (GUI subsystem)
// For all possible options, see: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-windows-specific-fields-image-only
let subsystem_offset = 68 ;
let subsystem_start =
start_pe as usize + standard_fields_size + subsystem_offset ;
let subsystem : u16 = 2 ;
bin [ ( subsystem_start ) .. ( subsystem_start + 2 ) ]
. copy_from_slice ( & subsystem . to_le_bytes ( ) ) ;
Ok ( ( ) )
}