mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -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:
parent
350d9dce41
commit
074444ab6c
18 changed files with 563 additions and 269 deletions
|
@ -1363,9 +1363,9 @@ impl CliOptions {
|
|||
|
||||
Ok(DenoLintConfig {
|
||||
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)
|
||||
.then(|| transpile_options.jsx_fragment_factory.clone()),
|
||||
.then_some(transpile_options.jsx_fragment_factory),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
35
cli/emit.rs
35
cli/emit.rs
|
@ -5,6 +5,7 @@ use crate::cache::FastInsecureHasher;
|
|||
use crate::cache::ParsedSourceCache;
|
||||
use crate::resolver::CjsTracker;
|
||||
|
||||
use deno_ast::EmittedSourceText;
|
||||
use deno_ast::ModuleKind;
|
||||
use deno_ast::SourceMapOption;
|
||||
use deno_ast::SourceRange;
|
||||
|
@ -132,6 +133,7 @@ impl Emitter {
|
|||
&transpile_and_emit_options.0,
|
||||
&transpile_and_emit_options.1,
|
||||
)
|
||||
.map(|r| r.text)
|
||||
}
|
||||
})
|
||||
.await
|
||||
|
@ -166,7 +168,8 @@ impl Emitter {
|
|||
source.clone(),
|
||||
&self.transpile_and_emit_options.0,
|
||||
&self.transpile_and_emit_options.1,
|
||||
)?;
|
||||
)?
|
||||
.text;
|
||||
helper.post_emit_parsed_source(
|
||||
specifier,
|
||||
&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.
|
||||
pub async fn load_and_emit_for_hmr(
|
||||
&self,
|
||||
|
@ -282,7 +310,7 @@ impl<'a> EmitParsedSourceHelper<'a> {
|
|||
source: Arc<str>,
|
||||
transpile_options: &deno_ast::TranspileOptions,
|
||||
emit_options: &deno_ast::EmitOptions,
|
||||
) -> Result<String, AnyError> {
|
||||
) -> Result<EmittedSourceText, AnyError> {
|
||||
// nothing else needs the parsed source at this point, so remove from
|
||||
// the cache in order to not transpile owned
|
||||
let parsed_source = parsed_source_cache
|
||||
|
@ -302,8 +330,7 @@ impl<'a> EmitParsedSourceHelper<'a> {
|
|||
source
|
||||
}
|
||||
};
|
||||
debug_assert!(transpiled_source.source_map.is_none());
|
||||
Ok(transpiled_source.text)
|
||||
Ok(transpiled_source)
|
||||
}
|
||||
|
||||
pub fn post_emit_parsed_source(
|
||||
|
|
|
@ -91,6 +91,7 @@ use super::serialization::DenoCompileModuleData;
|
|||
use super::serialization::DeserializedDataSection;
|
||||
use super::serialization::RemoteModulesStore;
|
||||
use super::serialization::RemoteModulesStoreBuilder;
|
||||
use super::serialization::SourceMapStore;
|
||||
use super::virtual_fs::output_vfs;
|
||||
use super::virtual_fs::BuiltVfs;
|
||||
use super::virtual_fs::FileBackedVfs;
|
||||
|
@ -98,6 +99,7 @@ use super::virtual_fs::VfsBuilder;
|
|||
use super::virtual_fs::VfsFileSubDataKind;
|
||||
use super::virtual_fs::VfsRoot;
|
||||
use super::virtual_fs::VirtualDirectory;
|
||||
use super::virtual_fs::VirtualDirectoryEntries;
|
||||
use super::virtual_fs::WindowsSystemRootablePath;
|
||||
|
||||
pub static DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME: &str =
|
||||
|
@ -203,17 +205,24 @@ pub struct Metadata {
|
|||
pub otel_config: OtelConfig,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn write_binary_bytes(
|
||||
mut file_writer: File,
|
||||
original_bin: Vec<u8>,
|
||||
metadata: &Metadata,
|
||||
npm_snapshot: Option<SerializedNpmResolutionSnapshot>,
|
||||
remote_modules: &RemoteModulesStoreBuilder,
|
||||
source_map_store: &SourceMapStore,
|
||||
vfs: &BuiltVfs,
|
||||
compile_flags: &CompileFlags,
|
||||
) -> Result<(), AnyError> {
|
||||
let data_section_bytes =
|
||||
serialize_binary_data_section(metadata, npm_snapshot, remote_modules, vfs)
|
||||
let data_section_bytes = serialize_binary_data_section(
|
||||
metadata,
|
||||
npm_snapshot,
|
||||
remote_modules,
|
||||
source_map_store,
|
||||
vfs,
|
||||
)
|
||||
.context("Serializing binary data section.")?;
|
||||
|
||||
let target = compile_flags.resolve_target();
|
||||
|
@ -256,6 +265,7 @@ pub struct StandaloneData {
|
|||
pub modules: StandaloneModules,
|
||||
pub npm_snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
|
||||
pub root_path: PathBuf,
|
||||
pub source_maps: SourceMapStore,
|
||||
pub vfs: Arc<FileBackedVfs>,
|
||||
}
|
||||
|
||||
|
@ -283,13 +293,12 @@ impl StandaloneModules {
|
|||
pub fn read<'a>(
|
||||
&'a self,
|
||||
specifier: &'a ModuleSpecifier,
|
||||
kind: VfsFileSubDataKind,
|
||||
) -> 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, VfsFileSubDataKind::ModuleGraph)?,
|
||||
Ok(entry) => self.vfs.read_file_all(entry, kind)?,
|
||||
Err(err) if err.kind() == ErrorKind::NotFound => {
|
||||
match RealFs.read_file_sync(&path, None) {
|
||||
Ok(bytes) => bytes,
|
||||
|
@ -307,7 +316,18 @@ impl StandaloneModules {
|
|||
data: bytes,
|
||||
}))
|
||||
} 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,
|
||||
npm_snapshot,
|
||||
remote_modules,
|
||||
mut vfs_dir,
|
||||
source_maps,
|
||||
vfs_root_entries,
|
||||
vfs_files_data,
|
||||
} = match deserialize_binary_data_section(data)? {
|
||||
Some(data_section) => data_section,
|
||||
|
@ -351,11 +372,12 @@ pub fn extract_standalone(
|
|||
metadata.argv.push(arg.into_string().unwrap());
|
||||
}
|
||||
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,
|
||||
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(),
|
||||
start_file_offset: 0,
|
||||
};
|
||||
|
@ -372,6 +394,7 @@ pub fn extract_standalone(
|
|||
},
|
||||
npm_snapshot,
|
||||
root_path,
|
||||
source_maps,
|
||||
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(
|
||||
|
@ -554,7 +577,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
|||
/// This functions creates a standalone deno binary by appending a bundle
|
||||
/// and magic trailer to the currently executing binary.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn write_standalone_binary(
|
||||
fn write_standalone_binary(
|
||||
&self,
|
||||
options: WriteBinOptions<'_>,
|
||||
original_bin: Vec<u8>,
|
||||
|
@ -598,71 +621,81 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
|||
.with_context(|| format!("Including {}", path.display()))?;
|
||||
}
|
||||
let mut remote_modules_store = RemoteModulesStoreBuilder::default();
|
||||
let mut code_cache_key_hasher = if self.cli_options.code_cache_enabled() {
|
||||
Some(FastInsecureHasher::new_deno_versioned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut source_maps = Vec::with_capacity(graph.specifiers_count());
|
||||
// todo(dsherret): transpile in parallel
|
||||
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
|
||||
}
|
||||
if let Some(hasher) = &mut code_cache_key_hasher {
|
||||
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 {
|
||||
let (maybe_original_source, maybe_transpiled, media_type) = match module {
|
||||
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(
|
||||
&m.specifier,
|
||||
m.media_type,
|
||||
m.is_script,
|
||||
)?;
|
||||
let module_kind = ModuleKind::from_is_cjs(is_cjs);
|
||||
let source = self
|
||||
.emitter
|
||||
.emit_parsed_source(
|
||||
let (source, source_map) =
|
||||
self.emitter.emit_parsed_source_for_deno_compile(
|
||||
&m.specifier,
|
||||
m.media_type,
|
||||
module_kind,
|
||||
&m.source,
|
||||
)
|
||||
.await?;
|
||||
source.into_bytes()
|
||||
)?;
|
||||
if source != m.source.as_ref() {
|
||||
source_maps.push((&m.specifier, source_map));
|
||||
Some(source.into_bytes())
|
||||
} 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) => {
|
||||
(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) => {
|
||||
(Some(m.source.to_vec()), MediaType::Wasm)
|
||||
(Some(m.source.to_vec()), None, MediaType::Wasm)
|
||||
}
|
||||
deno_graph::Module::Npm(_)
|
||||
| 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" {
|
||||
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)?.into_owned(),
|
||||
},
|
||||
original_source,
|
||||
VfsFileSubDataKind::Raw,
|
||||
)
|
||||
.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,
|
||||
)
|
||||
.with_context(|| {
|
||||
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);
|
||||
|
@ -695,6 +728,28 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
|||
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() {
|
||||
InnerCliNpmResolverRef::Managed(_) => {
|
||||
npm_snapshot.as_ref().map(|_| NodeModules::Managed {
|
||||
|
@ -742,7 +797,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
|||
let metadata = Metadata {
|
||||
argv: compile_flags.args.clone(),
|
||||
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(),
|
||||
permissions: self.cli_options.permission_flags().clone(),
|
||||
v8_flags: self.cli_options.v8_flags().clone(),
|
||||
|
@ -809,6 +864,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
|||
&metadata,
|
||||
npm_snapshot.map(|s| s.into_serialized()),
|
||||
&remote_modules_store,
|
||||
&source_map_store,
|
||||
&vfs,
|
||||
compile_flags,
|
||||
)
|
||||
|
@ -903,10 +959,10 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
|||
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 localhost_entries = IndexMap::new();
|
||||
for entry in std::mem::take(&mut root_dir.entries) {
|
||||
for entry in root_dir.entries.take_inner() {
|
||||
match entry {
|
||||
VfsEntry::Dir(dir) => {
|
||||
for entry in dir.entries {
|
||||
VfsEntry::Dir(mut dir) => {
|
||||
for entry in dir.entries.take_inner() {
|
||||
log::debug!("Flattening {} into node_modules", entry.name());
|
||||
if let Some(existing) =
|
||||
localhost_entries.insert(entry.name().to_string(), entry)
|
||||
|
@ -925,11 +981,11 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
|||
}
|
||||
new_entries.push(VfsEntry::Dir(VirtualDirectory {
|
||||
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
|
||||
new_entries.sort_by(|a, b| a.name().cmp(b.name()));
|
||||
root_dir.entries = new_entries;
|
||||
root_dir.entries = VirtualDirectoryEntries::new(new_entries);
|
||||
|
||||
// it's better to not expose the user's cache directory, so take it out
|
||||
// of there
|
||||
|
@ -937,10 +993,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
|||
let parent_dir = vfs.get_dir_mut(parent).unwrap();
|
||||
let index = parent_dir
|
||||
.entries
|
||||
.iter()
|
||||
.position(|entry| {
|
||||
entry.name() == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME
|
||||
})
|
||||
.binary_search(DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME)
|
||||
.unwrap();
|
||||
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);
|
||||
for ancestor in parent.ancestors() {
|
||||
let dir = vfs.get_dir_mut(ancestor).unwrap();
|
||||
if let Some(index) = dir
|
||||
.entries
|
||||
.iter()
|
||||
.position(|entry| entry.name() == last_name)
|
||||
{
|
||||
if let Ok(index) = dir.entries.binary_search(&last_name) {
|
||||
dir.entries.remove(index);
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
InnerCliNpmResolverRef::Byonm(_) => vfs.build(),
|
||||
|
|
|
@ -55,6 +55,7 @@ use node_resolver::errors::ClosestPkgJsonError;
|
|||
use node_resolver::NodeResolutionKind;
|
||||
use node_resolver::ResolutionMode;
|
||||
use serialization::DenoCompileModuleSource;
|
||||
use serialization::SourceMapStore;
|
||||
use std::borrow::Cow;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
@ -122,6 +123,7 @@ struct SharedModuleLoaderState {
|
|||
npm_module_loader: Arc<NpmModuleLoader>,
|
||||
npm_req_resolver: Arc<CliNpmReqResolver>,
|
||||
npm_resolver: Arc<dyn CliNpmResolver>,
|
||||
source_maps: SourceMapStore,
|
||||
vfs: Arc<FileBackedVfs>,
|
||||
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)) => {
|
||||
let media_type = module.media_type;
|
||||
let (module_specifier, module_type, module_source) =
|
||||
|
@ -495,6 +501,46 @@ impl ModuleLoader for EmbeddedModuleLoader {
|
|||
}
|
||||
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 {
|
||||
|
@ -590,6 +636,7 @@ pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
|
|||
modules,
|
||||
npm_snapshot,
|
||||
root_path,
|
||||
source_maps,
|
||||
vfs,
|
||||
} = data;
|
||||
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_req_resolver,
|
||||
source_maps,
|
||||
vfs,
|
||||
workspace_resolver,
|
||||
}),
|
||||
|
|
|
@ -6,6 +6,7 @@ use std::collections::BTreeMap;
|
|||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
|
||||
use deno_ast::swc::common::source_map;
|
||||
use deno_ast::MediaType;
|
||||
use deno_core::anyhow::bail;
|
||||
use deno_core::anyhow::Context;
|
||||
|
@ -20,12 +21,14 @@ use deno_npm::resolution::SerializedNpmResolutionSnapshotPackage;
|
|||
use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot;
|
||||
use deno_npm::NpmPackageId;
|
||||
use deno_semver::package::PackageReq;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::standalone::virtual_fs::VirtualDirectory;
|
||||
|
||||
use super::binary::Metadata;
|
||||
use super::virtual_fs::BuiltVfs;
|
||||
use super::virtual_fs::VfsBuilder;
|
||||
use super::virtual_fs::VirtualDirectoryEntries;
|
||||
|
||||
const MAGIC_BYTES: &[u8; 8] = b"d3n0l4nd";
|
||||
|
||||
|
@ -33,21 +36,22 @@ const MAGIC_BYTES: &[u8; 8] = b"d3n0l4nd";
|
|||
/// * d3n0l4nd
|
||||
/// * <metadata_len><metadata>
|
||||
/// * <npm_snapshot_len><npm_snapshot>
|
||||
/// * <remote_modules_len><remote_modules>
|
||||
/// * <remote_modules>
|
||||
/// * <vfs_headers_len><vfs_headers>
|
||||
/// * <vfs_file_data_len><vfs_file_data>
|
||||
/// * <source_map_data>
|
||||
/// * d3n0l4nd
|
||||
pub fn serialize_binary_data_section(
|
||||
metadata: &Metadata,
|
||||
npm_snapshot: Option<SerializedNpmResolutionSnapshot>,
|
||||
remote_modules: &RemoteModulesStoreBuilder,
|
||||
source_map_store: &SourceMapStore,
|
||||
vfs: &BuiltVfs,
|
||||
) -> Result<Vec<u8>, AnyError> {
|
||||
let metadata = serde_json::to_string(metadata)?;
|
||||
let npm_snapshot =
|
||||
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.root)?;
|
||||
let serialized_vfs = serde_json::to_string(&vfs.entries)?;
|
||||
|
||||
let bytes = capacity_builder::BytesBuilder::build(|builder| {
|
||||
builder.append(MAGIC_BYTES);
|
||||
|
@ -63,10 +67,7 @@ pub fn serialize_binary_data_section(
|
|||
}
|
||||
// 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_len.set((builder.len() - start_index) as u64);
|
||||
}
|
||||
// 4. VFS
|
||||
{
|
||||
|
@ -78,6 +79,16 @@ pub fn serialize_binary_data_section(
|
|||
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
|
||||
// to make sure we've deserialized correctly
|
||||
|
@ -91,19 +102,14 @@ pub struct DeserializedDataSection {
|
|||
pub metadata: Metadata,
|
||||
pub npm_snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
|
||||
pub remote_modules: RemoteModulesStore,
|
||||
pub vfs_dir: VirtualDirectory,
|
||||
pub source_maps: SourceMapStore,
|
||||
pub vfs_root_entries: VirtualDirectoryEntries,
|
||||
pub vfs_files_data: &'static [u8],
|
||||
}
|
||||
|
||||
pub fn deserialize_binary_data_section(
|
||||
data: &'static [u8],
|
||||
) -> 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> {
|
||||
if input.len() < MAGIC_BYTES.len() {
|
||||
bail!("Unexpected end of data. Could not find magic bytes.");
|
||||
|
@ -115,34 +121,51 @@ pub fn deserialize_binary_data_section(
|
|||
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)?;
|
||||
if !found {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 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 =
|
||||
serde_json::from_slice(data).context("deserializing metadata")?;
|
||||
// 2. Npm snapshot
|
||||
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() {
|
||||
None
|
||||
} else {
|
||||
Some(deserialize_npm_snapshot(data).context("deserializing npm snapshot")?)
|
||||
};
|
||||
// 3. Remote modules
|
||||
let (input, data) =
|
||||
read_bytes_with_len(input).context("reading remote modules data")?;
|
||||
let remote_modules =
|
||||
RemoteModulesStore::build(data).context("deserializing remote modules")?;
|
||||
let (input, remote_modules) =
|
||||
RemoteModulesStore::build(input).context("deserializing remote modules")?;
|
||||
// 4. VFS
|
||||
let (input, data) = read_bytes_with_len(input).context("vfs")?;
|
||||
let vfs_dir: VirtualDirectory =
|
||||
let (input, data) = read_bytes_with_u64_len(input).context("vfs")?;
|
||||
let vfs_root_entries: VirtualDirectoryEntries =
|
||||
serde_json::from_slice(data).context("deserializing vfs 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
|
||||
let (_input, found) = read_magic_bytes(input)?;
|
||||
|
@ -154,7 +177,8 @@ pub fn deserialize_binary_data_section(
|
|||
metadata,
|
||||
npm_snapshot,
|
||||
remote_modules,
|
||||
vfs_dir,
|
||||
source_maps,
|
||||
vfs_root_entries,
|
||||
vfs_files_data,
|
||||
}))
|
||||
}
|
||||
|
@ -162,19 +186,31 @@ pub fn deserialize_binary_data_section(
|
|||
#[derive(Default)]
|
||||
pub struct RemoteModulesStoreBuilder {
|
||||
specifiers: Vec<(String, u64)>,
|
||||
data: Vec<(MediaType, Vec<u8>)>,
|
||||
data: Vec<(MediaType, Vec<u8>, Option<Vec<u8>>)>,
|
||||
data_byte_len: u64,
|
||||
redirects: Vec<(String, String)>,
|
||||
redirects_len: u64,
|
||||
}
|
||||
|
||||
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);
|
||||
let specifier = specifier.to_string();
|
||||
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
|
||||
self.data.push((media_type, data));
|
||||
let maybe_transpiled_len = match &maybe_transpiled {
|
||||
// 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>) {
|
||||
|
@ -193,7 +229,7 @@ impl RemoteModulesStoreBuilder {
|
|||
builder.append_le(self.redirects.len() as u32);
|
||||
for (specifier, offset) in &self.specifiers {
|
||||
builder.append_le(specifier.len() as u32);
|
||||
builder.append(specifier.as_bytes());
|
||||
builder.append(specifier);
|
||||
builder.append_le(*offset);
|
||||
}
|
||||
for (from, to) in &self.redirects {
|
||||
|
@ -202,10 +238,32 @@ impl RemoteModulesStoreBuilder {
|
|||
builder.append_le(to.len() as u32);
|
||||
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_le(data.len() as u64);
|
||||
builder.append_le(data.len() as u32);
|
||||
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 specifier: &'a Url,
|
||||
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 {
|
||||
Data(usize),
|
||||
Redirect(Url),
|
||||
|
@ -291,7 +380,7 @@ pub struct 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> {
|
||||
let (input, specifier) = read_string_lossy(input)?;
|
||||
let specifier = Url::parse(&specifier)?;
|
||||
|
@ -334,12 +423,16 @@ impl RemoteModulesStore {
|
|||
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,
|
||||
files_data,
|
||||
})
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn resolve_specifier<'a>(
|
||||
|
@ -370,7 +463,7 @@ impl RemoteModulesStore {
|
|||
pub fn read<'a>(
|
||||
&'a self,
|
||||
original_specifier: &'a Url,
|
||||
) -> Result<Option<DenoCompileModuleData<'a>>, AnyError> {
|
||||
) -> Result<Option<RemoteModuleEntry<'a>>, AnyError> {
|
||||
let mut count = 0;
|
||||
let mut specifier = original_specifier;
|
||||
loop {
|
||||
|
@ -386,12 +479,25 @@ impl RemoteModulesStore {
|
|||
let input = &self.files_data[*offset..];
|
||||
let (input, media_type_byte) = read_bytes(input, 1)?;
|
||||
let media_type = deserialize_media_type(media_type_byte[0])?;
|
||||
let (input, len) = read_u64(input)?;
|
||||
let (_input, data) = read_bytes(input, len as usize)?;
|
||||
return Ok(Some(DenoCompileModuleData {
|
||||
let (input, data) = read_bytes_with_u32_len(input)?;
|
||||
check_has_len(input, 1)?;
|
||||
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,
|
||||
media_type,
|
||||
data: Cow::Borrowed(data),
|
||||
transpiled_data: transpiled_data.map(Cow::Borrowed),
|
||||
}));
|
||||
}
|
||||
None => {
|
||||
|
@ -630,14 +736,32 @@ fn parse_vec_n_times_with_index<TResult>(
|
|||
Ok((input, results))
|
||||
}
|
||||
|
||||
fn read_bytes(input: &[u8], len: usize) -> Result<(&[u8], &[u8]), AnyError> {
|
||||
if input.len() < len {
|
||||
bail!("Unexpected end of data.",);
|
||||
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> {
|
||||
check_has_len(input, len)?;
|
||||
let (len_bytes, input) = input.split_at(len);
|
||||
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> {
|
||||
let (input, str_len) = read_u32_as_usize(input)?;
|
||||
let (input, data_bytes) = read_bytes(input, str_len)?;
|
||||
|
|
|
@ -67,7 +67,7 @@ impl WindowsSystemRootablePath {
|
|||
#[derive(Debug)]
|
||||
pub struct BuiltVfs {
|
||||
pub root_path: WindowsSystemRootablePath,
|
||||
pub root: VirtualDirectory,
|
||||
pub entries: VirtualDirectoryEntries,
|
||||
pub files: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ impl VfsBuilder {
|
|||
Self {
|
||||
executable_root: VirtualDirectory {
|
||||
name: "/".to_string(),
|
||||
entries: Vec::new(),
|
||||
entries: Default::default(),
|
||||
},
|
||||
files: Vec::new(),
|
||||
current_offset: 0,
|
||||
|
@ -208,23 +208,20 @@ impl VfsBuilder {
|
|||
continue;
|
||||
}
|
||||
let name = component.as_os_str().to_string_lossy();
|
||||
let index = match current_dir
|
||||
.entries
|
||||
.binary_search_by(|e| e.name().cmp(&name))
|
||||
{
|
||||
let index = match current_dir.entries.binary_search(&name) {
|
||||
Ok(index) => index,
|
||||
Err(insert_index) => {
|
||||
current_dir.entries.insert(
|
||||
current_dir.entries.0.insert(
|
||||
insert_index,
|
||||
VfsEntry::Dir(VirtualDirectory {
|
||||
name: name.to_string(),
|
||||
entries: Vec::new(),
|
||||
entries: Default::default(),
|
||||
}),
|
||||
);
|
||||
insert_index
|
||||
}
|
||||
};
|
||||
match &mut current_dir.entries[index] {
|
||||
match &mut current_dir.entries.0[index] {
|
||||
VfsEntry::Dir(dir) => {
|
||||
current_dir = dir;
|
||||
}
|
||||
|
@ -248,14 +245,8 @@ impl VfsBuilder {
|
|||
continue;
|
||||
}
|
||||
let name = component.as_os_str().to_string_lossy();
|
||||
let index = match current_dir
|
||||
.entries
|
||||
.binary_search_by(|e| e.name().cmp(&name))
|
||||
{
|
||||
Ok(index) => index,
|
||||
Err(_) => return None,
|
||||
};
|
||||
match &mut current_dir.entries[index] {
|
||||
let entry = current_dir.entries.get_mut_by_name(&name)?;
|
||||
match entry {
|
||||
VfsEntry::Dir(dir) => {
|
||||
current_dir = dir;
|
||||
}
|
||||
|
@ -320,9 +311,9 @@ impl VfsBuilder {
|
|||
offset,
|
||||
len: data.len() as u64,
|
||||
};
|
||||
match dir.entries.binary_search_by(|e| e.name().cmp(&name)) {
|
||||
match dir.entries.binary_search(&name) {
|
||||
Ok(index) => {
|
||||
let entry = &mut dir.entries[index];
|
||||
let entry = &mut dir.entries.0[index];
|
||||
match entry {
|
||||
VfsEntry::File(virtual_file) => match sub_data_kind {
|
||||
VfsFileSubDataKind::Raw => {
|
||||
|
@ -336,7 +327,7 @@ impl VfsBuilder {
|
|||
}
|
||||
}
|
||||
Err(insert_index) => {
|
||||
dir.entries.insert(
|
||||
dir.entries.0.insert(
|
||||
insert_index,
|
||||
VfsEntry::File(VirtualFile {
|
||||
name: name.to_string(),
|
||||
|
@ -384,10 +375,10 @@ impl VfsBuilder {
|
|||
let target = normalize_path(path.parent().unwrap().join(&target));
|
||||
let dir = self.add_dir_raw(path.parent().unwrap());
|
||||
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
|
||||
Err(insert_index) => {
|
||||
dir.entries.insert(
|
||||
dir.entries.0.insert(
|
||||
insert_index,
|
||||
VfsEntry::Symlink(VirtualSymlink {
|
||||
name: name.to_string(),
|
||||
|
@ -426,7 +417,7 @@ impl VfsBuilder {
|
|||
dir: &mut VirtualDirectory,
|
||||
parts: &[String],
|
||||
) {
|
||||
for entry in &mut dir.entries {
|
||||
for entry in &mut dir.entries.0 {
|
||||
match entry {
|
||||
VfsEntry::Dir(dir) => {
|
||||
strip_prefix_from_symlinks(dir, parts);
|
||||
|
@ -454,13 +445,13 @@ impl VfsBuilder {
|
|||
if self.min_root_dir.as_ref() == Some(¤t_path) {
|
||||
break;
|
||||
}
|
||||
match ¤t_dir.entries[0] {
|
||||
match ¤t_dir.entries.0[0] {
|
||||
VfsEntry::Dir(dir) => {
|
||||
if dir.name == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME {
|
||||
// special directory we want to maintain
|
||||
break;
|
||||
}
|
||||
match current_dir.entries.remove(0) {
|
||||
match current_dir.entries.0.remove(0) {
|
||||
VfsEntry::Dir(dir) => {
|
||||
current_path =
|
||||
WindowsSystemRootablePath::Path(current_path.join(&dir.name));
|
||||
|
@ -480,7 +471,7 @@ impl VfsBuilder {
|
|||
}
|
||||
BuiltVfs {
|
||||
root_path: current_path,
|
||||
root: current_dir,
|
||||
entries: current_dir.entries,
|
||||
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
|
||||
}
|
||||
|
||||
if vfs.root.entries.is_empty() {
|
||||
if vfs.entries.is_empty() {
|
||||
return; // nothing to output
|
||||
}
|
||||
|
||||
|
@ -696,7 +687,7 @@ fn vfs_as_display_tree(
|
|||
|
||||
fn dir_size(dir: &VirtualDirectory, seen_offsets: &mut HashSet<u64>) -> Size {
|
||||
let mut size = Size::default();
|
||||
for entry in &dir.entries {
|
||||
for entry in dir.entries.iter() {
|
||||
match entry {
|
||||
VfsEntry::Dir(virtual_directory) => {
|
||||
size = size + dir_size(virtual_directory, seen_offsets);
|
||||
|
@ -760,15 +751,10 @@ fn vfs_as_display_tree(
|
|||
|
||||
fn include_all_entries<'a>(
|
||||
dir_path: &WindowsSystemRootablePath,
|
||||
vfs_dir: &'a VirtualDirectory,
|
||||
entries: &'a VirtualDirectoryEntries,
|
||||
seen_offsets: &mut HashSet<u64>,
|
||||
) -> Vec<DirEntryOutput<'a>> {
|
||||
if vfs_dir.name == DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME {
|
||||
return show_global_node_modules_dir(vfs_dir, seen_offsets);
|
||||
}
|
||||
|
||||
vfs_dir
|
||||
.entries
|
||||
entries
|
||||
.iter()
|
||||
.map(|entry| DirEntryOutput {
|
||||
name: Cow::Borrowed(entry.name()),
|
||||
|
@ -826,10 +812,12 @@ fn vfs_as_display_tree(
|
|||
} else {
|
||||
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 {
|
||||
EntryOutput::Subset(include_all_entries(
|
||||
&WindowsSystemRootablePath::Path(dir),
|
||||
vfs_dir,
|
||||
&vfs_dir.entries,
|
||||
seen_offsets,
|
||||
))
|
||||
}
|
||||
|
@ -839,7 +827,7 @@ fn vfs_as_display_tree(
|
|||
// user might not have context about what's being shown
|
||||
let mut seen_offsets = HashSet::with_capacity(vfs.files.len());
|
||||
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 {
|
||||
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)]
|
||||
pub struct VirtualDirectory {
|
||||
#[serde(rename = "n")]
|
||||
pub name: String,
|
||||
// should be sorted by name
|
||||
#[serde(rename = "e")]
|
||||
pub entries: Vec<VfsEntry>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub entries: VirtualDirectoryEntries,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
|
@ -1136,20 +1167,13 @@ impl VfsRoot {
|
|||
}
|
||||
};
|
||||
let component = component.to_string_lossy();
|
||||
match current_dir
|
||||
current_entry = current_dir
|
||||
.entries
|
||||
.binary_search_by(|e| e.name().cmp(&component))
|
||||
{
|
||||
Ok(index) => {
|
||||
current_entry = current_dir.entries[index].as_ref();
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"path not found",
|
||||
));
|
||||
}
|
||||
}
|
||||
.get_by_name(&component)
|
||||
.ok_or_else(|| {
|
||||
std::io::Error::new(std::io::ErrorKind::NotFound, "path not found")
|
||||
})?
|
||||
.as_ref();
|
||||
}
|
||||
|
||||
Ok((final_path, current_entry))
|
||||
|
@ -1706,7 +1730,10 @@ mod test {
|
|||
FileBackedVfs::new(
|
||||
Cow::Owned(data),
|
||||
VfsRoot {
|
||||
dir: vfs.root,
|
||||
dir: VirtualDirectory {
|
||||
name: "".to_string(),
|
||||
entries: vfs.entries,
|
||||
},
|
||||
root_path: dest_path.to_path_buf(),
|
||||
start_file_offset: 0,
|
||||
},
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use deno_core::serde_json;
|
||||
use test_util as util;
|
||||
use util::assert_contains;
|
||||
use util::assert_not_contains;
|
||||
use util::testdata_path;
|
||||
use util::TestContext;
|
||||
|
@ -90,78 +89,6 @@ fn standalone_args() {
|
|||
.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]
|
||||
fn standalone_load_datauri() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
{
|
||||
"tempDir": true,
|
||||
"steps": [{
|
||||
"args": "run -A cleanup.ts",
|
||||
"output": "[WILDCARD]"
|
||||
}, {
|
||||
"if": "unix",
|
||||
"args": "compile --output using_code_cache --log-level=debug main.ts",
|
||||
"output": "[WILDCARD]"
|
||||
|
|
11
tests/specs/compile/code_cache/cleanup.ts
Normal file
11
tests/specs/compile/code_cache/cleanup.ts
Normal 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 {
|
||||
}
|
|
@ -1,28 +1,31 @@
|
|||
{
|
||||
"tempDir": true,
|
||||
"steps": [{
|
||||
"if": "unix",
|
||||
"args": "compile --output main1 main.ts",
|
||||
"args": "run -A setup.ts",
|
||||
"output": "[WILDCARD]"
|
||||
}, {
|
||||
"if": "unix",
|
||||
"args": "compile --output main2 main.ts",
|
||||
"args": "compile --no-config --output a/main a/main.ts",
|
||||
"output": "[WILDCARD]"
|
||||
}, {
|
||||
"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"
|
||||
}, {
|
||||
"if": "windows",
|
||||
"args": "compile --output main1.exe main.ts",
|
||||
"args": "compile --no-config --output a/main.exe a/main.ts",
|
||||
"output": "[WILDCARD]"
|
||||
}, {
|
||||
"if": "windows",
|
||||
"args": "compile --output main2.exe main.ts",
|
||||
"args": "compile --no-config --output b/main.exe b/main.ts",
|
||||
"output": "[WILDCARD]"
|
||||
}, {
|
||||
"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"
|
||||
}]
|
||||
}
|
||||
|
|
10
tests/specs/compile/determinism/setup.ts
Normal file
10
tests/specs/compile/determinism/setup.ts
Normal 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`);
|
||||
}
|
24
tests/specs/compile/error/local/__test__.jsonc
Normal file
24
tests/specs/compile/error/local/__test__.jsonc
Normal 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
|
||||
}]
|
||||
}
|
6
tests/specs/compile/error/local/output.out
Normal file
6
tests/specs/compile/error/local/output.out
Normal 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
|
24
tests/specs/compile/error/remote/__test__.jsonc
Normal file
24
tests/specs/compile/error/remote/__test__.jsonc
Normal 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
|
||||
}]
|
||||
}
|
1
tests/specs/compile/error/remote/main.ts
Normal file
1
tests/specs/compile/error/remote/main.ts
Normal file
|
@ -0,0 +1 @@
|
|||
import "http://localhost:4545/compile/standalone_error_module_with_imports_1.ts";
|
5
tests/specs/compile/error/remote/output.out
Normal file
5
tests/specs/compile/error/remote/output.out
Normal 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
|
|
@ -1,2 +1,7 @@
|
|||
// file has blank lines to make the input line
|
||||
// different than the output
|
||||
console.log("hello");
|
||||
throw new Error("boom!");
|
||||
|
||||
const value: string = "boom!";
|
||||
|
||||
throw new Error(value);
|
||||
|
|
Loading…
Reference in a new issue