1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -05:00

fix(compile): be more deterministic when compiling the same code in different directories (#27395)

Additionaly, this no longer unnecessarily stores the source twice for
file specifiers and fixes some sourcemap issues.

Closes https://github.com/denoland/deno/issues/27284
This commit is contained in:
David Sherret 2024-12-19 12:53:52 -05:00 committed by GitHub
parent 350d9dce41
commit 074444ab6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 563 additions and 269 deletions

View file

@ -1363,9 +1363,9 @@ impl CliOptions {
Ok(DenoLintConfig { Ok(DenoLintConfig {
default_jsx_factory: (!transpile_options.jsx_automatic) default_jsx_factory: (!transpile_options.jsx_automatic)
.then(|| transpile_options.jsx_factory.clone()), .then_some(transpile_options.jsx_factory),
default_jsx_fragment_factory: (!transpile_options.jsx_automatic) default_jsx_fragment_factory: (!transpile_options.jsx_automatic)
.then(|| transpile_options.jsx_fragment_factory.clone()), .then_some(transpile_options.jsx_fragment_factory),
}) })
} }

View file

@ -5,6 +5,7 @@ use crate::cache::FastInsecureHasher;
use crate::cache::ParsedSourceCache; use crate::cache::ParsedSourceCache;
use crate::resolver::CjsTracker; use crate::resolver::CjsTracker;
use deno_ast::EmittedSourceText;
use deno_ast::ModuleKind; use deno_ast::ModuleKind;
use deno_ast::SourceMapOption; use deno_ast::SourceMapOption;
use deno_ast::SourceRange; use deno_ast::SourceRange;
@ -132,6 +133,7 @@ impl Emitter {
&transpile_and_emit_options.0, &transpile_and_emit_options.0,
&transpile_and_emit_options.1, &transpile_and_emit_options.1,
) )
.map(|r| r.text)
} }
}) })
.await .await
@ -166,7 +168,8 @@ impl Emitter {
source.clone(), source.clone(),
&self.transpile_and_emit_options.0, &self.transpile_and_emit_options.0,
&self.transpile_and_emit_options.1, &self.transpile_and_emit_options.1,
)?; )?
.text;
helper.post_emit_parsed_source( helper.post_emit_parsed_source(
specifier, specifier,
&transpiled_source, &transpiled_source,
@ -177,6 +180,31 @@ impl Emitter {
} }
} }
pub fn emit_parsed_source_for_deno_compile(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
module_kind: deno_ast::ModuleKind,
source: &Arc<str>,
) -> Result<(String, String), AnyError> {
let mut emit_options = self.transpile_and_emit_options.1.clone();
emit_options.inline_sources = false;
emit_options.source_map = SourceMapOption::Separate;
// strip off the path to have more deterministic builds as we don't care
// about the source name because we manually provide the source map to v8
emit_options.source_map_base = Some(deno_path_util::url_parent(specifier));
let source = EmitParsedSourceHelper::transpile(
&self.parsed_source_cache,
specifier,
media_type,
module_kind,
source.clone(),
&self.transpile_and_emit_options.0,
&emit_options,
)?;
Ok((source.text, source.source_map.unwrap()))
}
/// Expects a file URL, panics otherwise. /// Expects a file URL, panics otherwise.
pub async fn load_and_emit_for_hmr( pub async fn load_and_emit_for_hmr(
&self, &self,
@ -282,7 +310,7 @@ impl<'a> EmitParsedSourceHelper<'a> {
source: Arc<str>, source: Arc<str>,
transpile_options: &deno_ast::TranspileOptions, transpile_options: &deno_ast::TranspileOptions,
emit_options: &deno_ast::EmitOptions, emit_options: &deno_ast::EmitOptions,
) -> Result<String, AnyError> { ) -> Result<EmittedSourceText, AnyError> {
// nothing else needs the parsed source at this point, so remove from // nothing else needs the parsed source at this point, so remove from
// the cache in order to not transpile owned // the cache in order to not transpile owned
let parsed_source = parsed_source_cache let parsed_source = parsed_source_cache
@ -302,8 +330,7 @@ impl<'a> EmitParsedSourceHelper<'a> {
source source
} }
}; };
debug_assert!(transpiled_source.source_map.is_none()); Ok(transpiled_source)
Ok(transpiled_source.text)
} }
pub fn post_emit_parsed_source( pub fn post_emit_parsed_source(

View file

@ -91,6 +91,7 @@ use super::serialization::DenoCompileModuleData;
use super::serialization::DeserializedDataSection; use super::serialization::DeserializedDataSection;
use super::serialization::RemoteModulesStore; use super::serialization::RemoteModulesStore;
use super::serialization::RemoteModulesStoreBuilder; use super::serialization::RemoteModulesStoreBuilder;
use super::serialization::SourceMapStore;
use super::virtual_fs::output_vfs; use super::virtual_fs::output_vfs;
use super::virtual_fs::BuiltVfs; use super::virtual_fs::BuiltVfs;
use super::virtual_fs::FileBackedVfs; use super::virtual_fs::FileBackedVfs;
@ -98,6 +99,7 @@ use super::virtual_fs::VfsBuilder;
use super::virtual_fs::VfsFileSubDataKind; use super::virtual_fs::VfsFileSubDataKind;
use super::virtual_fs::VfsRoot; use super::virtual_fs::VfsRoot;
use super::virtual_fs::VirtualDirectory; use super::virtual_fs::VirtualDirectory;
use super::virtual_fs::VirtualDirectoryEntries;
use super::virtual_fs::WindowsSystemRootablePath; use super::virtual_fs::WindowsSystemRootablePath;
pub static DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME: &str = pub static DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME: &str =
@ -203,17 +205,24 @@ pub struct Metadata {
pub otel_config: OtelConfig, pub otel_config: OtelConfig,
} }
#[allow(clippy::too_many_arguments)]
fn write_binary_bytes( fn write_binary_bytes(
mut file_writer: File, mut file_writer: File,
original_bin: Vec<u8>, original_bin: Vec<u8>,
metadata: &Metadata, metadata: &Metadata,
npm_snapshot: Option<SerializedNpmResolutionSnapshot>, npm_snapshot: Option<SerializedNpmResolutionSnapshot>,
remote_modules: &RemoteModulesStoreBuilder, remote_modules: &RemoteModulesStoreBuilder,
source_map_store: &SourceMapStore,
vfs: &BuiltVfs, vfs: &BuiltVfs,
compile_flags: &CompileFlags, compile_flags: &CompileFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let data_section_bytes = let data_section_bytes = serialize_binary_data_section(
serialize_binary_data_section(metadata, npm_snapshot, remote_modules, vfs) metadata,
npm_snapshot,
remote_modules,
source_map_store,
vfs,
)
.context("Serializing binary data section.")?; .context("Serializing binary data section.")?;
let target = compile_flags.resolve_target(); let target = compile_flags.resolve_target();
@ -256,6 +265,7 @@ pub struct StandaloneData {
pub modules: StandaloneModules, pub modules: StandaloneModules,
pub npm_snapshot: Option<ValidSerializedNpmResolutionSnapshot>, pub npm_snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
pub root_path: PathBuf, pub root_path: PathBuf,
pub source_maps: SourceMapStore,
pub vfs: Arc<FileBackedVfs>, pub vfs: Arc<FileBackedVfs>,
} }
@ -283,13 +293,12 @@ impl StandaloneModules {
pub fn read<'a>( pub fn read<'a>(
&'a self, &'a self,
specifier: &'a ModuleSpecifier, specifier: &'a ModuleSpecifier,
kind: VfsFileSubDataKind,
) -> Result<Option<DenoCompileModuleData<'a>>, AnyError> { ) -> Result<Option<DenoCompileModuleData<'a>>, AnyError> {
if specifier.scheme() == "file" { if specifier.scheme() == "file" {
let path = deno_path_util::url_to_file_path(specifier)?; let path = deno_path_util::url_to_file_path(specifier)?;
let bytes = match self.vfs.file_entry(&path) { let bytes = match self.vfs.file_entry(&path) {
Ok(entry) => self Ok(entry) => self.vfs.read_file_all(entry, kind)?,
.vfs
.read_file_all(entry, VfsFileSubDataKind::ModuleGraph)?,
Err(err) if err.kind() == ErrorKind::NotFound => { Err(err) if err.kind() == ErrorKind::NotFound => {
match RealFs.read_file_sync(&path, None) { match RealFs.read_file_sync(&path, None) {
Ok(bytes) => bytes, Ok(bytes) => bytes,
@ -307,7 +316,18 @@ impl StandaloneModules {
data: bytes, data: bytes,
})) }))
} else { } else {
self.remote_modules.read(specifier) self.remote_modules.read(specifier).map(|maybe_entry| {
maybe_entry.map(|entry| DenoCompileModuleData {
media_type: entry.media_type,
specifier: entry.specifier,
data: match kind {
VfsFileSubDataKind::Raw => entry.data,
VfsFileSubDataKind::ModuleGraph => {
entry.transpiled_data.unwrap_or(entry.data)
}
},
})
})
} }
} }
} }
@ -328,7 +348,8 @@ pub fn extract_standalone(
mut metadata, mut metadata,
npm_snapshot, npm_snapshot,
remote_modules, remote_modules,
mut vfs_dir, source_maps,
vfs_root_entries,
vfs_files_data, vfs_files_data,
} = match deserialize_binary_data_section(data)? { } = match deserialize_binary_data_section(data)? {
Some(data_section) => data_section, Some(data_section) => data_section,
@ -351,11 +372,12 @@ pub fn extract_standalone(
metadata.argv.push(arg.into_string().unwrap()); metadata.argv.push(arg.into_string().unwrap());
} }
let vfs = { 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 { let fs_root = VfsRoot {
dir: vfs_dir, dir: VirtualDirectory {
// align the name of the directory with the root dir
name: root_path.file_name().unwrap().to_string_lossy().to_string(),
entries: vfs_root_entries,
},
root_path: root_path.clone(), root_path: root_path.clone(),
start_file_offset: 0, start_file_offset: 0,
}; };
@ -372,6 +394,7 @@ pub fn extract_standalone(
}, },
npm_snapshot, npm_snapshot,
root_path, root_path,
source_maps,
vfs, vfs,
})) }))
} }
@ -451,7 +474,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
) )
} }
} }
self.write_standalone_binary(options, original_binary).await self.write_standalone_binary(options, original_binary)
} }
async fn get_base_binary( async fn get_base_binary(
@ -554,7 +577,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
/// This functions creates a standalone deno binary by appending a bundle /// This functions creates a standalone deno binary by appending a bundle
/// and magic trailer to the currently executing binary. /// and magic trailer to the currently executing binary.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn write_standalone_binary( fn write_standalone_binary(
&self, &self,
options: WriteBinOptions<'_>, options: WriteBinOptions<'_>,
original_bin: Vec<u8>, original_bin: Vec<u8>,
@ -598,71 +621,81 @@ impl<'a> DenoCompileBinaryWriter<'a> {
.with_context(|| format!("Including {}", path.display()))?; .with_context(|| format!("Including {}", path.display()))?;
} }
let mut remote_modules_store = RemoteModulesStoreBuilder::default(); let mut remote_modules_store = RemoteModulesStoreBuilder::default();
let mut code_cache_key_hasher = if self.cli_options.code_cache_enabled() { let mut source_maps = Vec::with_capacity(graph.specifiers_count());
Some(FastInsecureHasher::new_deno_versioned()) // todo(dsherret): transpile in parallel
} else {
None
};
for module in graph.modules() { for module in graph.modules() {
if module.specifier().scheme() == "data" { if module.specifier().scheme() == "data" {
continue; // don't store data urls as an entry as they're in the code continue; // don't store data urls as an entry as they're in the code
} }
if let Some(hasher) = &mut code_cache_key_hasher { let (maybe_original_source, maybe_transpiled, media_type) = match module {
if let Some(source) = module.source() {
hasher.write(module.specifier().as_str().as_bytes());
hasher.write(source.as_bytes());
}
}
let (maybe_source, media_type) = match module {
deno_graph::Module::Js(m) => { deno_graph::Module::Js(m) => {
let source = if m.media_type.is_emittable() { let original_bytes = m.source.as_bytes().to_vec();
let maybe_transpiled = if m.media_type.is_emittable() {
let is_cjs = self.cjs_tracker.is_cjs_with_known_is_script( let is_cjs = self.cjs_tracker.is_cjs_with_known_is_script(
&m.specifier, &m.specifier,
m.media_type, m.media_type,
m.is_script, m.is_script,
)?; )?;
let module_kind = ModuleKind::from_is_cjs(is_cjs); let module_kind = ModuleKind::from_is_cjs(is_cjs);
let source = self let (source, source_map) =
.emitter self.emitter.emit_parsed_source_for_deno_compile(
.emit_parsed_source(
&m.specifier, &m.specifier,
m.media_type, m.media_type,
module_kind, module_kind,
&m.source, &m.source,
) )?;
.await?; if source != m.source.as_ref() {
source.into_bytes() source_maps.push((&m.specifier, source_map));
Some(source.into_bytes())
} else { } else {
m.source.as_bytes().to_vec() None
}
} else {
None
}; };
(Some(source), m.media_type) (Some(original_bytes), maybe_transpiled, m.media_type)
} }
deno_graph::Module::Json(m) => { deno_graph::Module::Json(m) => {
(Some(m.source.as_bytes().to_vec()), m.media_type) (Some(m.source.as_bytes().to_vec()), None, m.media_type)
} }
deno_graph::Module::Wasm(m) => { deno_graph::Module::Wasm(m) => {
(Some(m.source.to_vec()), MediaType::Wasm) (Some(m.source.to_vec()), None, MediaType::Wasm)
} }
deno_graph::Module::Npm(_) deno_graph::Module::Npm(_)
| deno_graph::Module::Node(_) | deno_graph::Module::Node(_)
| deno_graph::Module::External(_) => (None, MediaType::Unknown), | deno_graph::Module::External(_) => (None, None, MediaType::Unknown),
}; };
if let Some(original_source) = maybe_original_source {
if module.specifier().scheme() == "file" { if module.specifier().scheme() == "file" {
let file_path = deno_path_util::url_to_file_path(module.specifier())?; let file_path = deno_path_util::url_to_file_path(module.specifier())?;
vfs vfs
.add_file_with_data( .add_file_with_data(
&file_path, &file_path,
match maybe_source { original_source,
Some(source) => source, VfsFileSubDataKind::Raw,
None => RealFs.read_file_sync(&file_path, None)?.into_owned(), )
}, .with_context(|| {
format!("Failed adding '{}'", file_path.display())
})?;
if let Some(transpiled_source) = maybe_transpiled {
vfs
.add_file_with_data(
&file_path,
transpiled_source,
VfsFileSubDataKind::ModuleGraph, VfsFileSubDataKind::ModuleGraph,
) )
.with_context(|| { .with_context(|| {
format!("Failed adding '{}'", file_path.display()) format!("Failed adding '{}'", file_path.display())
})?; })?;
} else if let Some(source) = maybe_source { }
remote_modules_store.add(module.specifier(), media_type, source); } else {
remote_modules_store.add(
module.specifier(),
media_type,
original_source,
maybe_transpiled,
);
}
} }
} }
remote_modules_store.add_redirects(&graph.redirects); remote_modules_store.add_redirects(&graph.redirects);
@ -695,6 +728,28 @@ impl<'a> DenoCompileBinaryWriter<'a> {
None => StandaloneRelativeFileBaseUrl::WindowsSystemRoot, None => StandaloneRelativeFileBaseUrl::WindowsSystemRoot,
}; };
let code_cache_key = if self.cli_options.code_cache_enabled() {
let mut hasher = FastInsecureHasher::new_deno_versioned();
for module in graph.modules() {
if let Some(source) = module.source() {
hasher
.write(root_dir_url.specifier_key(module.specifier()).as_bytes());
hasher.write(source.as_bytes());
}
}
Some(hasher.finish())
} else {
None
};
let mut source_map_store = SourceMapStore::with_capacity(source_maps.len());
for (specifier, source_map) in source_maps {
source_map_store.add(
Cow::Owned(root_dir_url.specifier_key(specifier).into_owned()),
Cow::Owned(source_map),
);
}
let node_modules = match self.npm_resolver.as_inner() { let node_modules = match self.npm_resolver.as_inner() {
InnerCliNpmResolverRef::Managed(_) => { InnerCliNpmResolverRef::Managed(_) => {
npm_snapshot.as_ref().map(|_| NodeModules::Managed { npm_snapshot.as_ref().map(|_| NodeModules::Managed {
@ -742,7 +797,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
let metadata = Metadata { let metadata = Metadata {
argv: compile_flags.args.clone(), argv: compile_flags.args.clone(),
seed: self.cli_options.seed(), seed: self.cli_options.seed(),
code_cache_key: code_cache_key_hasher.map(|h| h.finish()), code_cache_key,
location: self.cli_options.location_flag().clone(), location: self.cli_options.location_flag().clone(),
permissions: self.cli_options.permission_flags().clone(), permissions: self.cli_options.permission_flags().clone(),
v8_flags: self.cli_options.v8_flags().clone(), v8_flags: self.cli_options.v8_flags().clone(),
@ -809,6 +864,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
&metadata, &metadata,
npm_snapshot.map(|s| s.into_serialized()), npm_snapshot.map(|s| s.into_serialized()),
&remote_modules_store, &remote_modules_store,
&source_map_store,
&vfs, &vfs,
compile_flags, compile_flags,
) )
@ -903,10 +959,10 @@ impl<'a> DenoCompileBinaryWriter<'a> {
root_dir.name = DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME.to_string(); root_dir.name = DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME.to_string();
let mut new_entries = Vec::with_capacity(root_dir.entries.len()); let mut new_entries = Vec::with_capacity(root_dir.entries.len());
let mut localhost_entries = IndexMap::new(); let mut localhost_entries = IndexMap::new();
for entry in std::mem::take(&mut root_dir.entries) { for entry in root_dir.entries.take_inner() {
match entry { match entry {
VfsEntry::Dir(dir) => { VfsEntry::Dir(mut dir) => {
for entry in dir.entries { for entry in dir.entries.take_inner() {
log::debug!("Flattening {} into node_modules", entry.name()); log::debug!("Flattening {} into node_modules", entry.name());
if let Some(existing) = if let Some(existing) =
localhost_entries.insert(entry.name().to_string(), entry) localhost_entries.insert(entry.name().to_string(), entry)
@ -925,11 +981,11 @@ impl<'a> DenoCompileBinaryWriter<'a> {
} }
new_entries.push(VfsEntry::Dir(VirtualDirectory { new_entries.push(VfsEntry::Dir(VirtualDirectory {
name: "localhost".to_string(), name: "localhost".to_string(),
entries: localhost_entries.into_iter().map(|(_, v)| v).collect(), entries: VirtualDirectoryEntries::new(
localhost_entries.into_iter().map(|(_, v)| v).collect(),
),
})); }));
// needs to be sorted by name root_dir.entries = VirtualDirectoryEntries::new(new_entries);
new_entries.sort_by(|a, b| a.name().cmp(b.name()));
root_dir.entries = new_entries;
// it's better to not expose the user's cache directory, so take it out // it's better to not expose the user's cache directory, so take it out
// of there // of there
@ -937,10 +993,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
let parent_dir = vfs.get_dir_mut(parent).unwrap(); let parent_dir = vfs.get_dir_mut(parent).unwrap();
let index = parent_dir let index = parent_dir
.entries .entries
.iter() .binary_search(DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME)
.position(|entry| {
entry.name() == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME
})
.unwrap(); .unwrap();
let npm_global_cache_dir_entry = parent_dir.entries.remove(index); let npm_global_cache_dir_entry = parent_dir.entries.remove(index);
@ -950,11 +1003,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
Cow::Borrowed(DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME); Cow::Borrowed(DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME);
for ancestor in parent.ancestors() { for ancestor in parent.ancestors() {
let dir = vfs.get_dir_mut(ancestor).unwrap(); let dir = vfs.get_dir_mut(ancestor).unwrap();
if let Some(index) = dir if let Ok(index) = dir.entries.binary_search(&last_name) {
.entries
.iter()
.position(|entry| entry.name() == last_name)
{
dir.entries.remove(index); dir.entries.remove(index);
} }
last_name = Cow::Owned(dir.name.clone()); last_name = Cow::Owned(dir.name.clone());
@ -965,7 +1014,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
// now build the vfs and add the global cache dir entry there // now build the vfs and add the global cache dir entry there
let mut built_vfs = vfs.build(); let mut built_vfs = vfs.build();
built_vfs.root.insert_entry(npm_global_cache_dir_entry); built_vfs.entries.insert(npm_global_cache_dir_entry);
built_vfs built_vfs
} }
InnerCliNpmResolverRef::Byonm(_) => vfs.build(), InnerCliNpmResolverRef::Byonm(_) => vfs.build(),

View file

@ -55,6 +55,7 @@ use node_resolver::errors::ClosestPkgJsonError;
use node_resolver::NodeResolutionKind; use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode; use node_resolver::ResolutionMode;
use serialization::DenoCompileModuleSource; use serialization::DenoCompileModuleSource;
use serialization::SourceMapStore;
use std::borrow::Cow; use std::borrow::Cow;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
@ -122,6 +123,7 @@ struct SharedModuleLoaderState {
npm_module_loader: Arc<NpmModuleLoader>, npm_module_loader: Arc<NpmModuleLoader>,
npm_req_resolver: Arc<CliNpmReqResolver>, npm_req_resolver: Arc<CliNpmReqResolver>,
npm_resolver: Arc<dyn CliNpmResolver>, npm_resolver: Arc<dyn CliNpmResolver>,
source_maps: SourceMapStore,
vfs: Arc<FileBackedVfs>, vfs: Arc<FileBackedVfs>,
workspace_resolver: WorkspaceResolver, workspace_resolver: WorkspaceResolver,
} }
@ -396,7 +398,11 @@ impl ModuleLoader for EmbeddedModuleLoader {
); );
} }
match self.shared.modules.read(original_specifier) { match self
.shared
.modules
.read(original_specifier, VfsFileSubDataKind::ModuleGraph)
{
Ok(Some(module)) => { Ok(Some(module)) => {
let media_type = module.media_type; let media_type = module.media_type;
let (module_specifier, module_type, module_source) = let (module_specifier, module_type, module_source) =
@ -495,6 +501,46 @@ impl ModuleLoader for EmbeddedModuleLoader {
} }
std::future::ready(()).boxed_local() std::future::ready(()).boxed_local()
} }
fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
if file_name.starts_with("file:///") {
let url =
deno_path_util::url_from_directory_path(self.shared.vfs.root()).ok()?;
let file_url = ModuleSpecifier::parse(file_name).ok()?;
let relative_path = url.make_relative(&file_url)?;
self.shared.source_maps.get(&relative_path)
} else {
self.shared.source_maps.get(file_name)
}
// todo(https://github.com/denoland/deno_core/pull/1007): don't clone
.map(|s| s.as_bytes().to_vec())
}
fn get_source_mapped_source_line(
&self,
file_name: &str,
line_number: usize,
) -> Option<String> {
let specifier = ModuleSpecifier::parse(file_name).ok()?;
let data = self
.shared
.modules
.read(&specifier, VfsFileSubDataKind::Raw)
.ok()??;
let source = String::from_utf8_lossy(&data.data);
// Do NOT use .lines(): it skips the terminating empty line.
// (due to internally using_terminator() instead of .split())
let lines: Vec<&str> = source.split('\n').collect();
if line_number >= lines.len() {
Some(format!(
"{} Couldn't format source line: Line {} is out of bounds (source may have changed at runtime)",
crate::colors::yellow("Warning"), line_number + 1,
))
} else {
Some(lines[line_number].to_string())
}
}
} }
impl NodeRequireLoader for EmbeddedModuleLoader { impl NodeRequireLoader for EmbeddedModuleLoader {
@ -590,6 +636,7 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
modules, modules,
npm_snapshot, npm_snapshot,
root_path, root_path,
source_maps,
vfs, vfs,
} = data; } = data;
let deno_dir_provider = Arc::new(DenoDirProvider::new(None)); let deno_dir_provider = Arc::new(DenoDirProvider::new(None));
@ -841,6 +888,7 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
)), )),
npm_resolver: npm_resolver.clone(), npm_resolver: npm_resolver.clone(),
npm_req_resolver, npm_req_resolver,
source_maps,
vfs, vfs,
workspace_resolver, workspace_resolver,
}), }),

View file

@ -6,6 +6,7 @@ use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use deno_ast::swc::common::source_map;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::anyhow::bail; use deno_core::anyhow::bail;
use deno_core::anyhow::Context; use deno_core::anyhow::Context;
@ -20,12 +21,14 @@ use deno_npm::resolution::SerializedNpmResolutionSnapshotPackage;
use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot;
use deno_npm::NpmPackageId; use deno_npm::NpmPackageId;
use deno_semver::package::PackageReq; use deno_semver::package::PackageReq;
use indexmap::IndexMap;
use crate::standalone::virtual_fs::VirtualDirectory; use crate::standalone::virtual_fs::VirtualDirectory;
use super::binary::Metadata; use super::binary::Metadata;
use super::virtual_fs::BuiltVfs; use super::virtual_fs::BuiltVfs;
use super::virtual_fs::VfsBuilder; use super::virtual_fs::VfsBuilder;
use super::virtual_fs::VirtualDirectoryEntries;
const MAGIC_BYTES: &[u8; 8] = b"d3n0l4nd"; const MAGIC_BYTES: &[u8; 8] = b"d3n0l4nd";
@ -33,21 +36,22 @@ const MAGIC_BYTES: &[u8; 8] = b"d3n0l4nd";
/// * d3n0l4nd /// * d3n0l4nd
/// * <metadata_len><metadata> /// * <metadata_len><metadata>
/// * <npm_snapshot_len><npm_snapshot> /// * <npm_snapshot_len><npm_snapshot>
/// * <remote_modules_len><remote_modules> /// * <remote_modules>
/// * <vfs_headers_len><vfs_headers> /// * <vfs_headers_len><vfs_headers>
/// * <vfs_file_data_len><vfs_file_data> /// * <vfs_file_data_len><vfs_file_data>
/// * <source_map_data>
/// * d3n0l4nd /// * d3n0l4nd
pub fn serialize_binary_data_section( pub fn serialize_binary_data_section(
metadata: &Metadata, metadata: &Metadata,
npm_snapshot: Option<SerializedNpmResolutionSnapshot>, npm_snapshot: Option<SerializedNpmResolutionSnapshot>,
remote_modules: &RemoteModulesStoreBuilder, remote_modules: &RemoteModulesStoreBuilder,
source_map_store: &SourceMapStore,
vfs: &BuiltVfs, vfs: &BuiltVfs,
) -> Result<Vec<u8>, AnyError> { ) -> Result<Vec<u8>, AnyError> {
let metadata = serde_json::to_string(metadata)?; let metadata = serde_json::to_string(metadata)?;
let npm_snapshot = let npm_snapshot =
npm_snapshot.map(serialize_npm_snapshot).unwrap_or_default(); npm_snapshot.map(serialize_npm_snapshot).unwrap_or_default();
let remote_modules_len = Cell::new(0_u64); let serialized_vfs = serde_json::to_string(&vfs.entries)?;
let serialized_vfs = serde_json::to_string(&vfs.root)?;
let bytes = capacity_builder::BytesBuilder::build(|builder| { let bytes = capacity_builder::BytesBuilder::build(|builder| {
builder.append(MAGIC_BYTES); builder.append(MAGIC_BYTES);
@ -63,10 +67,7 @@ pub fn serialize_binary_data_section(
} }
// 3. Remote modules // 3. Remote modules
{ {
builder.append_le(remote_modules_len.get()); // this will be properly initialized on the second pass
let start_index = builder.len();
remote_modules.write(builder); remote_modules.write(builder);
remote_modules_len.set((builder.len() - start_index) as u64);
} }
// 4. VFS // 4. VFS
{ {
@ -78,6 +79,16 @@ pub fn serialize_binary_data_section(
builder.append(file); builder.append(file);
} }
} }
// 5. Source maps
{
builder.append_le(source_map_store.data.len() as u32);
for (specifier, source_map) in &source_map_store.data {
builder.append_le(specifier.len() as u32);
builder.append(specifier);
builder.append_le(source_map.len() as u32);
builder.append(source_map);
}
}
// write the magic bytes at the end so we can use it // write the magic bytes at the end so we can use it
// to make sure we've deserialized correctly // to make sure we've deserialized correctly
@ -91,19 +102,14 @@ pub struct DeserializedDataSection {
pub metadata: Metadata, pub metadata: Metadata,
pub npm_snapshot: Option<ValidSerializedNpmResolutionSnapshot>, pub npm_snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
pub remote_modules: RemoteModulesStore, pub remote_modules: RemoteModulesStore,
pub vfs_dir: VirtualDirectory, pub source_maps: SourceMapStore,
pub vfs_root_entries: VirtualDirectoryEntries,
pub vfs_files_data: &'static [u8], pub vfs_files_data: &'static [u8],
} }
pub fn deserialize_binary_data_section( pub fn deserialize_binary_data_section(
data: &'static [u8], data: &'static [u8],
) -> Result<Option<DeserializedDataSection>, AnyError> { ) -> Result<Option<DeserializedDataSection>, AnyError> {
fn read_bytes_with_len(input: &[u8]) -> Result<(&[u8], &[u8]), AnyError> {
let (input, len) = read_u64(input)?;
let (input, data) = read_bytes(input, len as usize)?;
Ok((input, data))
}
fn read_magic_bytes(input: &[u8]) -> Result<(&[u8], bool), AnyError> { fn read_magic_bytes(input: &[u8]) -> Result<(&[u8], bool), AnyError> {
if input.len() < MAGIC_BYTES.len() { if input.len() < MAGIC_BYTES.len() {
bail!("Unexpected end of data. Could not find magic bytes."); bail!("Unexpected end of data. Could not find magic bytes.");
@ -115,34 +121,51 @@ pub fn deserialize_binary_data_section(
Ok((input, true)) Ok((input, true))
} }
#[allow(clippy::type_complexity)]
fn read_source_map_entry(
input: &[u8],
) -> Result<(&[u8], (Cow<str>, Cow<str>)), AnyError> {
let (input, specifier) = read_string_lossy(input)?;
let (input, source_map) = read_string_lossy(input)?;
Ok((input, (specifier, source_map)))
}
let (input, found) = read_magic_bytes(data)?; let (input, found) = read_magic_bytes(data)?;
if !found { if !found {
return Ok(None); return Ok(None);
} }
// 1. Metadata // 1. Metadata
let (input, data) = read_bytes_with_len(input).context("reading metadata")?; let (input, data) =
read_bytes_with_u64_len(input).context("reading metadata")?;
let metadata: Metadata = let metadata: Metadata =
serde_json::from_slice(data).context("deserializing metadata")?; serde_json::from_slice(data).context("deserializing metadata")?;
// 2. Npm snapshot // 2. Npm snapshot
let (input, data) = let (input, data) =
read_bytes_with_len(input).context("reading npm snapshot")?; read_bytes_with_u64_len(input).context("reading npm snapshot")?;
let npm_snapshot = if data.is_empty() { let npm_snapshot = if data.is_empty() {
None None
} else { } else {
Some(deserialize_npm_snapshot(data).context("deserializing npm snapshot")?) Some(deserialize_npm_snapshot(data).context("deserializing npm snapshot")?)
}; };
// 3. Remote modules // 3. Remote modules
let (input, data) = let (input, remote_modules) =
read_bytes_with_len(input).context("reading remote modules data")?; RemoteModulesStore::build(input).context("deserializing remote modules")?;
let remote_modules =
RemoteModulesStore::build(data).context("deserializing remote modules")?;
// 4. VFS // 4. VFS
let (input, data) = read_bytes_with_len(input).context("vfs")?; let (input, data) = read_bytes_with_u64_len(input).context("vfs")?;
let vfs_dir: VirtualDirectory = let vfs_root_entries: VirtualDirectoryEntries =
serde_json::from_slice(data).context("deserializing vfs data")?; serde_json::from_slice(data).context("deserializing vfs data")?;
let (input, vfs_files_data) = let (input, vfs_files_data) =
read_bytes_with_len(input).context("reading vfs files data")?; read_bytes_with_u64_len(input).context("reading vfs files data")?;
// 5. Source maps
let (mut input, source_map_data_len) = read_u32_as_usize(input)?;
let mut source_maps = SourceMapStore::with_capacity(source_map_data_len);
for _ in 0..source_map_data_len {
let (current_input, (specifier, source_map)) =
read_source_map_entry(input)?;
input = current_input;
source_maps.add(specifier, source_map);
}
// finally ensure we read the magic bytes at the end // finally ensure we read the magic bytes at the end
let (_input, found) = read_magic_bytes(input)?; let (_input, found) = read_magic_bytes(input)?;
@ -154,7 +177,8 @@ pub fn deserialize_binary_data_section(
metadata, metadata,
npm_snapshot, npm_snapshot,
remote_modules, remote_modules,
vfs_dir, source_maps,
vfs_root_entries,
vfs_files_data, vfs_files_data,
})) }))
} }
@ -162,19 +186,31 @@ pub fn deserialize_binary_data_section(
#[derive(Default)] #[derive(Default)]
pub struct RemoteModulesStoreBuilder { pub struct RemoteModulesStoreBuilder {
specifiers: Vec<(String, u64)>, specifiers: Vec<(String, u64)>,
data: Vec<(MediaType, Vec<u8>)>, data: Vec<(MediaType, Vec<u8>, Option<Vec<u8>>)>,
data_byte_len: u64, data_byte_len: u64,
redirects: Vec<(String, String)>, redirects: Vec<(String, String)>,
redirects_len: u64, redirects_len: u64,
} }
impl RemoteModulesStoreBuilder { impl RemoteModulesStoreBuilder {
pub fn add(&mut self, specifier: &Url, media_type: MediaType, data: Vec<u8>) { pub fn add(
&mut self,
specifier: &Url,
media_type: MediaType,
data: Vec<u8>,
maybe_transpiled: Option<Vec<u8>>,
) {
log::debug!("Adding '{}' ({})", specifier, media_type); log::debug!("Adding '{}' ({})", specifier, media_type);
let specifier = specifier.to_string(); let specifier = specifier.to_string();
self.specifiers.push((specifier, self.data_byte_len)); self.specifiers.push((specifier, self.data_byte_len));
self.data_byte_len += 1 + 8 + data.len() as u64; // media type (1 byte), data length (8 bytes), data let maybe_transpiled_len = match &maybe_transpiled {
self.data.push((media_type, data)); // data length (4 bytes), data
Some(data) => 4 + data.len() as u64,
None => 0,
};
// media type (1 byte), data length (4 bytes), data, has transpiled (1 byte), transpiled length
self.data_byte_len += 1 + 4 + data.len() as u64 + 1 + maybe_transpiled_len;
self.data.push((media_type, data, maybe_transpiled));
} }
pub fn add_redirects(&mut self, redirects: &BTreeMap<Url, Url>) { pub fn add_redirects(&mut self, redirects: &BTreeMap<Url, Url>) {
@ -193,7 +229,7 @@ impl RemoteModulesStoreBuilder {
builder.append_le(self.redirects.len() as u32); builder.append_le(self.redirects.len() as u32);
for (specifier, offset) in &self.specifiers { for (specifier, offset) in &self.specifiers {
builder.append_le(specifier.len() as u32); builder.append_le(specifier.len() as u32);
builder.append(specifier.as_bytes()); builder.append(specifier);
builder.append_le(*offset); builder.append_le(*offset);
} }
for (from, to) in &self.redirects { for (from, to) in &self.redirects {
@ -202,10 +238,32 @@ impl RemoteModulesStoreBuilder {
builder.append_le(to.len() as u32); builder.append_le(to.len() as u32);
builder.append(to); builder.append(to);
} }
for (media_type, data) in &self.data { builder.append_le(
self
.data
.iter()
.map(|(_, data, maybe_transpiled)| {
1 + 4
+ (data.len() as u64)
+ 1
+ match maybe_transpiled {
Some(transpiled) => 4 + (transpiled.len() as u64),
None => 0,
}
})
.sum::<u64>(),
);
for (media_type, data, maybe_transpiled) in &self.data {
builder.append(serialize_media_type(*media_type)); builder.append(serialize_media_type(*media_type));
builder.append_le(data.len() as u64); builder.append_le(data.len() as u32);
builder.append(data); builder.append(data);
if let Some(transpiled) = maybe_transpiled {
builder.append(1);
builder.append_le(transpiled.len() as u32);
builder.append(transpiled);
} else {
builder.append(0);
}
} }
} }
} }
@ -234,6 +292,30 @@ impl DenoCompileModuleSource {
} }
} }
pub struct SourceMapStore {
data: IndexMap<Cow<'static, str>, Cow<'static, str>>,
}
impl SourceMapStore {
pub fn with_capacity(capacity: usize) -> Self {
Self {
data: IndexMap::with_capacity(capacity),
}
}
pub fn add(
&mut self,
specifier: Cow<'static, str>,
source_map: Cow<'static, str>,
) {
self.data.insert(specifier, source_map);
}
pub fn get(&self, specifier: &str) -> Option<&Cow<'static, str>> {
self.data.get(specifier)
}
}
pub struct DenoCompileModuleData<'a> { pub struct DenoCompileModuleData<'a> {
pub specifier: &'a Url, pub specifier: &'a Url,
pub media_type: MediaType, pub media_type: MediaType,
@ -280,6 +362,13 @@ impl<'a> DenoCompileModuleData<'a> {
} }
} }
pub struct RemoteModuleEntry<'a> {
pub specifier: &'a Url,
pub media_type: MediaType,
pub data: Cow<'static, [u8]>,
pub transpiled_data: Option<Cow<'static, [u8]>>,
}
enum RemoteModulesStoreSpecifierValue { enum RemoteModulesStoreSpecifierValue {
Data(usize), Data(usize),
Redirect(Url), Redirect(Url),
@ -291,7 +380,7 @@ pub struct RemoteModulesStore {
} }
impl RemoteModulesStore { impl RemoteModulesStore {
fn build(data: &'static [u8]) -> Result<Self, AnyError> { fn build(input: &'static [u8]) -> Result<(&'static [u8], Self), AnyError> {
fn read_specifier(input: &[u8]) -> Result<(&[u8], (Url, u64)), AnyError> { fn read_specifier(input: &[u8]) -> Result<(&[u8], (Url, u64)), AnyError> {
let (input, specifier) = read_string_lossy(input)?; let (input, specifier) = read_string_lossy(input)?;
let specifier = Url::parse(&specifier)?; let specifier = Url::parse(&specifier)?;
@ -334,12 +423,16 @@ impl RemoteModulesStore {
Ok((input, specifiers)) Ok((input, specifiers))
} }
let (files_data, specifiers) = read_headers(data)?; let (input, specifiers) = read_headers(input)?;
let (input, files_data) = read_bytes_with_u64_len(input)?;
Ok(Self { Ok((
input,
Self {
specifiers, specifiers,
files_data, files_data,
}) },
))
} }
pub fn resolve_specifier<'a>( pub fn resolve_specifier<'a>(
@ -370,7 +463,7 @@ impl RemoteModulesStore {
pub fn read<'a>( pub fn read<'a>(
&'a self, &'a self,
original_specifier: &'a Url, original_specifier: &'a Url,
) -> Result<Option<DenoCompileModuleData<'a>>, AnyError> { ) -> Result<Option<RemoteModuleEntry<'a>>, AnyError> {
let mut count = 0; let mut count = 0;
let mut specifier = original_specifier; let mut specifier = original_specifier;
loop { loop {
@ -386,12 +479,25 @@ impl RemoteModulesStore {
let input = &self.files_data[*offset..]; let input = &self.files_data[*offset..];
let (input, media_type_byte) = read_bytes(input, 1)?; let (input, media_type_byte) = read_bytes(input, 1)?;
let media_type = deserialize_media_type(media_type_byte[0])?; let media_type = deserialize_media_type(media_type_byte[0])?;
let (input, len) = read_u64(input)?; let (input, data) = read_bytes_with_u32_len(input)?;
let (_input, data) = read_bytes(input, len as usize)?; check_has_len(input, 1)?;
return Ok(Some(DenoCompileModuleData { let (input, has_transpiled) = (&input[1..], input[0]);
let (_, transpiled_data) = match has_transpiled {
0 => (input, None),
1 => {
let (input, data) = read_bytes_with_u32_len(input)?;
(input, Some(data))
}
value => bail!(
"Invalid transpiled data flag: {}. Compiled data is corrupt.",
value
),
};
return Ok(Some(RemoteModuleEntry {
specifier, specifier,
media_type, media_type,
data: Cow::Borrowed(data), data: Cow::Borrowed(data),
transpiled_data: transpiled_data.map(Cow::Borrowed),
})); }));
} }
None => { None => {
@ -630,14 +736,32 @@ fn parse_vec_n_times_with_index<TResult>(
Ok((input, results)) Ok((input, results))
} }
fn read_bytes_with_u64_len(input: &[u8]) -> Result<(&[u8], &[u8]), AnyError> {
let (input, len) = read_u64(input)?;
let (input, data) = read_bytes(input, len as usize)?;
Ok((input, data))
}
fn read_bytes_with_u32_len(input: &[u8]) -> Result<(&[u8], &[u8]), AnyError> {
let (input, len) = read_u32_as_usize(input)?;
let (input, data) = read_bytes(input, len)?;
Ok((input, data))
}
fn read_bytes(input: &[u8], len: usize) -> Result<(&[u8], &[u8]), AnyError> { fn read_bytes(input: &[u8], len: usize) -> Result<(&[u8], &[u8]), AnyError> {
if input.len() < len { check_has_len(input, len)?;
bail!("Unexpected end of data.",);
}
let (len_bytes, input) = input.split_at(len); let (len_bytes, input) = input.split_at(len);
Ok((input, len_bytes)) Ok((input, len_bytes))
} }
#[inline(always)]
fn check_has_len(input: &[u8], len: usize) -> Result<(), AnyError> {
if input.len() < len {
bail!("Unexpected end of data.");
}
Ok(())
}
fn read_string_lossy(input: &[u8]) -> Result<(&[u8], Cow<str>), AnyError> { fn read_string_lossy(input: &[u8]) -> Result<(&[u8], Cow<str>), AnyError> {
let (input, str_len) = read_u32_as_usize(input)?; let (input, str_len) = read_u32_as_usize(input)?;
let (input, data_bytes) = read_bytes(input, str_len)?; let (input, data_bytes) = read_bytes(input, str_len)?;

View file

@ -67,7 +67,7 @@ impl WindowsSystemRootablePath {
#[derive(Debug)] #[derive(Debug)]
pub struct BuiltVfs { pub struct BuiltVfs {
pub root_path: WindowsSystemRootablePath, pub root_path: WindowsSystemRootablePath,
pub root: VirtualDirectory, pub entries: VirtualDirectoryEntries,
pub files: Vec<Vec<u8>>, pub files: Vec<Vec<u8>>,
} }
@ -95,7 +95,7 @@ impl VfsBuilder {
Self { Self {
executable_root: VirtualDirectory { executable_root: VirtualDirectory {
name: "/".to_string(), name: "/".to_string(),
entries: Vec::new(), entries: Default::default(),
}, },
files: Vec::new(), files: Vec::new(),
current_offset: 0, current_offset: 0,
@ -208,23 +208,20 @@ impl VfsBuilder {
continue; continue;
} }
let name = component.as_os_str().to_string_lossy(); let name = component.as_os_str().to_string_lossy();
let index = match current_dir let index = match current_dir.entries.binary_search(&name) {
.entries
.binary_search_by(|e| e.name().cmp(&name))
{
Ok(index) => index, Ok(index) => index,
Err(insert_index) => { Err(insert_index) => {
current_dir.entries.insert( current_dir.entries.0.insert(
insert_index, insert_index,
VfsEntry::Dir(VirtualDirectory { VfsEntry::Dir(VirtualDirectory {
name: name.to_string(), name: name.to_string(),
entries: Vec::new(), entries: Default::default(),
}), }),
); );
insert_index insert_index
} }
}; };
match &mut current_dir.entries[index] { match &mut current_dir.entries.0[index] {
VfsEntry::Dir(dir) => { VfsEntry::Dir(dir) => {
current_dir = dir; current_dir = dir;
} }
@ -248,14 +245,8 @@ impl VfsBuilder {
continue; continue;
} }
let name = component.as_os_str().to_string_lossy(); let name = component.as_os_str().to_string_lossy();
let index = match current_dir let entry = current_dir.entries.get_mut_by_name(&name)?;
.entries match entry {
.binary_search_by(|e| e.name().cmp(&name))
{
Ok(index) => index,
Err(_) => return None,
};
match &mut current_dir.entries[index] {
VfsEntry::Dir(dir) => { VfsEntry::Dir(dir) => {
current_dir = dir; current_dir = dir;
} }
@ -320,9 +311,9 @@ impl VfsBuilder {
offset, offset,
len: data.len() as u64, len: data.len() as u64,
}; };
match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { match dir.entries.binary_search(&name) {
Ok(index) => { Ok(index) => {
let entry = &mut dir.entries[index]; let entry = &mut dir.entries.0[index];
match entry { match entry {
VfsEntry::File(virtual_file) => match sub_data_kind { VfsEntry::File(virtual_file) => match sub_data_kind {
VfsFileSubDataKind::Raw => { VfsFileSubDataKind::Raw => {
@ -336,7 +327,7 @@ impl VfsBuilder {
} }
} }
Err(insert_index) => { Err(insert_index) => {
dir.entries.insert( dir.entries.0.insert(
insert_index, insert_index,
VfsEntry::File(VirtualFile { VfsEntry::File(VirtualFile {
name: name.to_string(), name: name.to_string(),
@ -384,10 +375,10 @@ impl VfsBuilder {
let target = normalize_path(path.parent().unwrap().join(&target)); let target = normalize_path(path.parent().unwrap().join(&target));
let dir = self.add_dir_raw(path.parent().unwrap()); let dir = self.add_dir_raw(path.parent().unwrap());
let name = path.file_name().unwrap().to_string_lossy(); let name = path.file_name().unwrap().to_string_lossy();
match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { match dir.entries.binary_search(&name) {
Ok(_) => {} // previously inserted Ok(_) => {} // previously inserted
Err(insert_index) => { Err(insert_index) => {
dir.entries.insert( dir.entries.0.insert(
insert_index, insert_index,
VfsEntry::Symlink(VirtualSymlink { VfsEntry::Symlink(VirtualSymlink {
name: name.to_string(), name: name.to_string(),
@ -426,7 +417,7 @@ impl VfsBuilder {
dir: &mut VirtualDirectory, dir: &mut VirtualDirectory,
parts: &[String], parts: &[String],
) { ) {
for entry in &mut dir.entries { for entry in &mut dir.entries.0 {
match entry { match entry {
VfsEntry::Dir(dir) => { VfsEntry::Dir(dir) => {
strip_prefix_from_symlinks(dir, parts); strip_prefix_from_symlinks(dir, parts);
@ -454,13 +445,13 @@ impl VfsBuilder {
if self.min_root_dir.as_ref() == Some(&current_path) { if self.min_root_dir.as_ref() == Some(&current_path) {
break; break;
} }
match &current_dir.entries[0] { match &current_dir.entries.0[0] {
VfsEntry::Dir(dir) => { VfsEntry::Dir(dir) => {
if dir.name == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME { if dir.name == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME {
// special directory we want to maintain // special directory we want to maintain
break; break;
} }
match current_dir.entries.remove(0) { match current_dir.entries.0.remove(0) {
VfsEntry::Dir(dir) => { VfsEntry::Dir(dir) => {
current_path = current_path =
WindowsSystemRootablePath::Path(current_path.join(&dir.name)); WindowsSystemRootablePath::Path(current_path.join(&dir.name));
@ -480,7 +471,7 @@ impl VfsBuilder {
} }
BuiltVfs { BuiltVfs {
root_path: current_path, root_path: current_path,
root: current_dir, entries: current_dir.entries,
files: self.files, files: self.files,
} }
} }
@ -506,7 +497,7 @@ pub fn output_vfs(vfs: &BuiltVfs, executable_name: &str) {
return; // no need to compute if won't output return; // no need to compute if won't output
} }
if vfs.root.entries.is_empty() { if vfs.entries.is_empty() {
return; // nothing to output return; // nothing to output
} }
@ -696,7 +687,7 @@ fn vfs_as_display_tree(
fn dir_size(dir: &VirtualDirectory, seen_offsets: &mut HashSet<u64>) -> Size { fn dir_size(dir: &VirtualDirectory, seen_offsets: &mut HashSet<u64>) -> Size {
let mut size = Size::default(); let mut size = Size::default();
for entry in &dir.entries { for entry in dir.entries.iter() {
match entry { match entry {
VfsEntry::Dir(virtual_directory) => { VfsEntry::Dir(virtual_directory) => {
size = size + dir_size(virtual_directory, seen_offsets); size = size + dir_size(virtual_directory, seen_offsets);
@ -760,15 +751,10 @@ fn vfs_as_display_tree(
fn include_all_entries<'a>( fn include_all_entries<'a>(
dir_path: &WindowsSystemRootablePath, dir_path: &WindowsSystemRootablePath,
vfs_dir: &'a VirtualDirectory, entries: &'a VirtualDirectoryEntries,
seen_offsets: &mut HashSet<u64>, seen_offsets: &mut HashSet<u64>,
) -> Vec<DirEntryOutput<'a>> { ) -> Vec<DirEntryOutput<'a>> {
if vfs_dir.name == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME { entries
return show_global_node_modules_dir(vfs_dir, seen_offsets);
}
vfs_dir
.entries
.iter() .iter()
.map(|entry| DirEntryOutput { .map(|entry| DirEntryOutput {
name: Cow::Borrowed(entry.name()), name: Cow::Borrowed(entry.name()),
@ -826,10 +812,12 @@ fn vfs_as_display_tree(
} else { } else {
EntryOutput::Subset(children) EntryOutput::Subset(children)
} }
} else if vfs_dir.name == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME {
EntryOutput::Subset(show_global_node_modules_dir(vfs_dir, seen_offsets))
} else { } else {
EntryOutput::Subset(include_all_entries( EntryOutput::Subset(include_all_entries(
&WindowsSystemRootablePath::Path(dir), &WindowsSystemRootablePath::Path(dir),
vfs_dir, &vfs_dir.entries,
seen_offsets, seen_offsets,
)) ))
} }
@ -839,7 +827,7 @@ fn vfs_as_display_tree(
// user might not have context about what's being shown // user might not have context about what's being shown
let mut seen_offsets = HashSet::with_capacity(vfs.files.len()); let mut seen_offsets = HashSet::with_capacity(vfs.files.len());
let mut child_entries = let mut child_entries =
include_all_entries(&vfs.root_path, &vfs.root, &mut seen_offsets); include_all_entries(&vfs.root_path, &vfs.entries, &mut seen_offsets);
for child_entry in &mut child_entries { for child_entry in &mut child_entries {
child_entry.collapse_leaf_nodes(); child_entry.collapse_leaf_nodes();
} }
@ -961,27 +949,70 @@ impl VfsEntry {
} }
} }
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct VirtualDirectoryEntries(Vec<VfsEntry>);
impl VirtualDirectoryEntries {
pub fn new(mut entries: Vec<VfsEntry>) -> Self {
// needs to be sorted by name
entries.sort_by(|a, b| a.name().cmp(b.name()));
Self(entries)
}
pub fn take_inner(&mut self) -> Vec<VfsEntry> {
std::mem::take(&mut self.0)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn get_by_name(&self, name: &str) -> Option<&VfsEntry> {
self.binary_search(name).ok().map(|index| &self.0[index])
}
pub fn get_mut_by_name(&mut self, name: &str) -> Option<&mut VfsEntry> {
self
.binary_search(name)
.ok()
.map(|index| &mut self.0[index])
}
pub fn binary_search(&self, name: &str) -> Result<usize, usize> {
self.0.binary_search_by(|e| e.name().cmp(name))
}
pub fn insert(&mut self, entry: VfsEntry) {
match self.binary_search(entry.name()) {
Ok(index) => {
self.0[index] = entry;
}
Err(insert_index) => {
self.0.insert(insert_index, entry);
}
}
}
pub fn remove(&mut self, index: usize) -> VfsEntry {
self.0.remove(index)
}
pub fn iter(&self) -> std::slice::Iter<'_, VfsEntry> {
self.0.iter()
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct VirtualDirectory { pub struct VirtualDirectory {
#[serde(rename = "n")] #[serde(rename = "n")]
pub name: String, pub name: String,
// should be sorted by name // should be sorted by name
#[serde(rename = "e")] #[serde(rename = "e")]
pub entries: Vec<VfsEntry>, pub entries: VirtualDirectoryEntries,
}
impl VirtualDirectory {
pub fn insert_entry(&mut self, entry: VfsEntry) {
let name = entry.name();
match self.entries.binary_search_by(|e| e.name().cmp(name)) {
Ok(index) => {
self.entries[index] = entry;
}
Err(insert_index) => {
self.entries.insert(insert_index, entry);
}
}
}
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
@ -1136,20 +1167,13 @@ impl VfsRoot {
} }
}; };
let component = component.to_string_lossy(); let component = component.to_string_lossy();
match current_dir current_entry = current_dir
.entries .entries
.binary_search_by(|e| e.name().cmp(&component)) .get_by_name(&component)
{ .ok_or_else(|| {
Ok(index) => { std::io::Error::new(std::io::ErrorKind::NotFound, "path not found")
current_entry = current_dir.entries[index].as_ref(); })?
} .as_ref();
Err(_) => {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"path not found",
));
}
}
} }
Ok((final_path, current_entry)) Ok((final_path, current_entry))
@ -1706,7 +1730,10 @@ mod test {
FileBackedVfs::new( FileBackedVfs::new(
Cow::Owned(data), Cow::Owned(data),
VfsRoot { VfsRoot {
dir: vfs.root, dir: VirtualDirectory {
name: "".to_string(),
entries: vfs.entries,
},
root_path: dest_path.to_path_buf(), root_path: dest_path.to_path_buf(),
start_file_offset: 0, start_file_offset: 0,
}, },

View file

@ -2,7 +2,6 @@
use deno_core::serde_json; use deno_core::serde_json;
use test_util as util; use test_util as util;
use util::assert_contains;
use util::assert_not_contains; use util::assert_not_contains;
use util::testdata_path; use util::testdata_path;
use util::TestContext; use util::TestContext;
@ -90,78 +89,6 @@ fn standalone_args() {
.assert_exit_code(0); .assert_exit_code(0);
} }
#[test]
fn standalone_error() {
let context = TestContextBuilder::new().build();
let dir = context.temp_dir();
let exe = if cfg!(windows) {
dir.path().join("error.exe")
} else {
dir.path().join("error")
};
context
.new_command()
.args_vec([
"compile",
"--output",
&exe.to_string_lossy(),
"./compile/standalone_error.ts",
])
.run()
.skip_output_check()
.assert_exit_code(0);
let output = context.new_command().name(&exe).split_output().run();
output.assert_exit_code(1);
output.assert_stdout_matches_text("");
let stderr = output.stderr();
// On Windows, we cannot assert the file path (because '\').
// Instead we just check for relevant output.
assert_contains!(stderr, "error: Uncaught (in promise) Error: boom!");
assert_contains!(stderr, "\n at boom (file://");
assert_contains!(stderr, "standalone_error.ts:2:9");
assert_contains!(stderr, "at foo (file://");
assert_contains!(stderr, "standalone_error.ts:5:3");
assert_contains!(stderr, "standalone_error.ts:7:1");
}
#[test]
fn standalone_error_module_with_imports() {
let context = TestContextBuilder::new().build();
let dir = context.temp_dir();
let exe = if cfg!(windows) {
dir.path().join("error.exe")
} else {
dir.path().join("error")
};
context
.new_command()
.args_vec([
"compile",
"--output",
&exe.to_string_lossy(),
"./compile/standalone_error_module_with_imports_1.ts",
])
.run()
.skip_output_check()
.assert_exit_code(0);
let output = context
.new_command()
.name(&exe)
.env("NO_COLOR", "1")
.split_output()
.run();
output.assert_stdout_matches_text("hello\n");
let stderr = output.stderr();
// On Windows, we cannot assert the file path (because '\').
// Instead we just check for relevant output.
assert_contains!(stderr, "error: Uncaught (in promise) Error: boom!");
assert_contains!(stderr, "\n at file://");
assert_contains!(stderr, "standalone_error_module_with_imports_2.ts:2:7");
output.assert_exit_code(1);
}
#[test] #[test]
fn standalone_load_datauri() { fn standalone_load_datauri() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();

View file

@ -1,6 +1,9 @@
{ {
"tempDir": true, "tempDir": true,
"steps": [{ "steps": [{
"args": "run -A cleanup.ts",
"output": "[WILDCARD]"
}, {
"if": "unix", "if": "unix",
"args": "compile --output using_code_cache --log-level=debug main.ts", "args": "compile --output using_code_cache --log-level=debug main.ts",
"output": "[WILDCARD]" "output": "[WILDCARD]"

View file

@ -0,0 +1,11 @@
import { tmpdir } from "node:os";
// cleanup the code cache file from a previous run
try {
if (Deno.build.os === "windows") {
Deno.removeSync(tmpdir() + "\\deno-compile-using_code_cache.exe.cache");
} else {
Deno.removeSync(tmpdir() + "\\deno-compile-using_code_cache.cache");
}
} catch {
}

View file

@ -1,28 +1,31 @@
{ {
"tempDir": true, "tempDir": true,
"steps": [{ "steps": [{
"if": "unix", "args": "run -A setup.ts",
"args": "compile --output main1 main.ts",
"output": "[WILDCARD]" "output": "[WILDCARD]"
}, { }, {
"if": "unix", "if": "unix",
"args": "compile --output main2 main.ts", "args": "compile --no-config --output a/main a/main.ts",
"output": "[WILDCARD]" "output": "[WILDCARD]"
}, { }, {
"if": "unix", "if": "unix",
"args": "run --allow-read=. assert_equal.ts main1 main2", "args": "compile --no-config --output b/main b/main.ts",
"output": "[WILDCARD]"
}, {
"if": "unix",
"args": "run --allow-read=. assert_equal.ts a/main b/main",
"output": "Same\n" "output": "Same\n"
}, { }, {
"if": "windows", "if": "windows",
"args": "compile --output main1.exe main.ts", "args": "compile --no-config --output a/main.exe a/main.ts",
"output": "[WILDCARD]" "output": "[WILDCARD]"
}, { }, {
"if": "windows", "if": "windows",
"args": "compile --output main2.exe main.ts", "args": "compile --no-config --output b/main.exe b/main.ts",
"output": "[WILDCARD]" "output": "[WILDCARD]"
}, { }, {
"if": "windows", "if": "windows",
"args": "run --allow-read=. assert_equal.ts main1.exe main2.exe", "args": "run --allow-read=. assert_equal.ts a/main.exe b/main.exe",
"output": "Same\n" "output": "Same\n"
}] }]
} }

View file

@ -0,0 +1,10 @@
// for setup, we create two directories with the same file in each
// and then when compiling we ensure this directory name has no
// effect on the output
makeCopyDir("a");
makeCopyDir("b");
function makeCopyDir(dirName) {
Deno.mkdirSync(dirName);
Deno.copyFileSync("main.ts", `${dirName}/main.ts`);
}

View file

@ -0,0 +1,24 @@
{
"tempDir": true,
"steps": [{
"if": "unix",
"args": "compile --output main standalone_error.ts",
"output": "[WILDCARD]"
}, {
"if": "unix",
"commandName": "./main",
"args": [],
"output": "output.out",
"exitCode": 1
}, {
"if": "windows",
"args": "compile --output main.exe standalone_error.ts",
"output": "[WILDCARD]"
}, {
"if": "windows",
"commandName": "./main.exe",
"args": [],
"output": "output.out",
"exitCode": 1
}]
}

View file

@ -0,0 +1,6 @@
error: Uncaught (in promise) Error: boom!
throw new Error("boom!");
^
at boom (file:///[WILDLINE]standalone_error.ts:2:9)
at foo (file:///[WILDLINE]standalone_error.ts:6:3)
at file:///[WILDLINE]standalone_error.ts:9:1

View file

@ -0,0 +1,24 @@
{
"tempDir": true,
"steps": [{
"if": "unix",
"args": "compile -A --output main main.ts",
"output": "[WILDCARD]"
}, {
"if": "unix",
"commandName": "./main",
"args": [],
"output": "output.out",
"exitCode": 1
}, {
"if": "windows",
"args": "compile -A --output main.exe main.ts",
"output": "[WILDCARD]"
}, {
"if": "windows",
"commandName": "./main.exe",
"args": [],
"output": "output.out",
"exitCode": 1
}]
}

View file

@ -0,0 +1 @@
import "http://localhost:4545/compile/standalone_error_module_with_imports_1.ts";

View file

@ -0,0 +1,5 @@
hello
error: Uncaught (in promise) Error: boom!
throw new Error(value);
^
at http://localhost:4545/compile/standalone_error_module_with_imports_2.ts:7:7

View file

@ -1,2 +1,7 @@
// file has blank lines to make the input line
// different than the output
console.log("hello"); console.log("hello");
throw new Error("boom!");
const value: string = "boom!";
throw new Error(value);