mirror of
https://github.com/denoland/deno.git
synced 2024-11-28 16:20:57 -05:00
perf(compile): pass module source data from binary directly to v8 (#26494)
This changes denort to pass a static reference of the moude source bytes found in the binary to v8 instead of copying it.
This commit is contained in:
parent
3d23be13e0
commit
4d731f5ef8
14 changed files with 1148 additions and 481 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -1196,7 +1196,6 @@ dependencies = [
|
||||||
"dprint-plugin-markdown",
|
"dprint-plugin-markdown",
|
||||||
"dprint-plugin-typescript",
|
"dprint-plugin-typescript",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"eszip",
|
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"faster-hex",
|
"faster-hex",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
@ -2895,29 +2894,6 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "31ae425815400e5ed474178a7a22e275a9687086a12ca63ec793ff292d8fdae8"
|
checksum = "31ae425815400e5ed474178a7a22e275a9687086a12ca63ec793ff292d8fdae8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "eszip"
|
|
||||||
version = "0.79.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8eb55c89bdde75a3826a79d49c9d847623ae7fbdb2695b542982982da990d33e"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-trait",
|
|
||||||
"base64 0.21.7",
|
|
||||||
"deno_ast",
|
|
||||||
"deno_graph",
|
|
||||||
"deno_npm",
|
|
||||||
"deno_semver",
|
|
||||||
"futures",
|
|
||||||
"hashlink 0.8.4",
|
|
||||||
"indexmap",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
|
||||||
"thiserror",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible-iterator"
|
name = "fallible-iterator"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -3529,15 +3505,6 @@ dependencies = [
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashlink"
|
|
||||||
version = "0.8.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
|
@ -5815,7 +5782,7 @@ dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"fallible-streaming-iterator",
|
"fallible-streaming-iterator",
|
||||||
"hashlink 0.9.1",
|
"hashlink",
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
|
@ -84,7 +84,6 @@ deno_runtime = { workspace = true, features = ["include_js_files_for_snapshottin
|
||||||
deno_semver.workspace = true
|
deno_semver.workspace = true
|
||||||
deno_task_shell = "=0.18.1"
|
deno_task_shell = "=0.18.1"
|
||||||
deno_terminal.workspace = true
|
deno_terminal.workspace = true
|
||||||
eszip = "=0.79.1"
|
|
||||||
libsui = "0.4.0"
|
libsui = "0.4.0"
|
||||||
node_resolver.workspace = true
|
node_resolver.workspace = true
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ impl Emitter {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// todo(https://github.com/denoland/deno_media_type/pull/12): use is_emittable()
|
||||||
let is_emittable = matches!(
|
let is_emittable = matches!(
|
||||||
module.media_type,
|
module.media_type,
|
||||||
MediaType::TypeScript
|
MediaType::TypeScript
|
||||||
|
|
|
@ -762,6 +762,7 @@ impl CliFactory {
|
||||||
let cli_options = self.cli_options()?;
|
let cli_options = self.cli_options()?;
|
||||||
Ok(DenoCompileBinaryWriter::new(
|
Ok(DenoCompileBinaryWriter::new(
|
||||||
self.deno_dir()?,
|
self.deno_dir()?,
|
||||||
|
self.emitter()?,
|
||||||
self.file_fetcher()?,
|
self.file_fetcher()?,
|
||||||
self.http_client_provider(),
|
self.http_client_provider(),
|
||||||
self.npm_resolver().await?.as_ref(),
|
self.npm_resolver().await?.as_ref(),
|
||||||
|
|
|
@ -88,11 +88,10 @@ fn main() {
|
||||||
let standalone = standalone::extract_standalone(Cow::Owned(args));
|
let standalone = standalone::extract_standalone(Cow::Owned(args));
|
||||||
let future = async move {
|
let future = async move {
|
||||||
match standalone {
|
match standalone {
|
||||||
Ok(Some(future)) => {
|
Ok(Some(data)) => {
|
||||||
let (metadata, eszip) = future.await?;
|
util::logger::init(data.metadata.log_level);
|
||||||
util::logger::init(metadata.log_level);
|
load_env_vars(&data.metadata.env_vars_from_env_file);
|
||||||
load_env_vars(&metadata.env_vars_from_env_file);
|
let exit_code = standalone::run(data).await?;
|
||||||
let exit_code = standalone::run(eszip, metadata).await?;
|
|
||||||
std::process::exit(exit_code);
|
std::process::exit(exit_code);
|
||||||
}
|
}
|
||||||
Ok(None) => Ok(()),
|
Ok(None) => Ok(()),
|
||||||
|
|
|
@ -9,14 +9,18 @@ use std::ffi::OsString;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::io::ErrorKind;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Seek;
|
use std::io::Seek;
|
||||||
use std::io::SeekFrom;
|
use std::io::SeekFrom;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::ops::Range;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use deno_ast::MediaType;
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
use deno_config::workspace::PackageJsonDepResolution;
|
use deno_config::workspace::PackageJsonDepResolution;
|
||||||
use deno_config::workspace::ResolverWorkspaceJsrPackage;
|
use deno_config::workspace::ResolverWorkspaceJsrPackage;
|
||||||
|
@ -30,13 +34,22 @@ use deno_core::futures::AsyncReadExt;
|
||||||
use deno_core::futures::AsyncSeekExt;
|
use deno_core::futures::AsyncSeekExt;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::url::Url;
|
use deno_core::url::Url;
|
||||||
|
use deno_graph::source::RealFileSystem;
|
||||||
|
use deno_graph::ModuleGraph;
|
||||||
|
use deno_npm::resolution::SerializedNpmResolutionSnapshot;
|
||||||
|
use deno_npm::resolution::SerializedNpmResolutionSnapshotPackage;
|
||||||
|
use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot;
|
||||||
|
use deno_npm::NpmPackageId;
|
||||||
use deno_npm::NpmSystemInfo;
|
use deno_npm::NpmSystemInfo;
|
||||||
|
use deno_runtime::deno_fs;
|
||||||
|
use deno_runtime::deno_fs::FileSystem;
|
||||||
|
use deno_runtime::deno_fs::RealFs;
|
||||||
|
use deno_runtime::deno_io::fs::FsError;
|
||||||
use deno_runtime::deno_node::PackageJson;
|
use deno_runtime::deno_node::PackageJson;
|
||||||
use deno_semver::npm::NpmVersionReqParseError;
|
use deno_semver::npm::NpmVersionReqParseError;
|
||||||
use deno_semver::package::PackageReq;
|
use deno_semver::package::PackageReq;
|
||||||
use deno_semver::Version;
|
use deno_semver::Version;
|
||||||
use deno_semver::VersionReqSpecifierParseError;
|
use deno_semver::VersionReqSpecifierParseError;
|
||||||
use eszip::EszipRelativeFileBaseUrl;
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use log::Level;
|
use log::Level;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -49,6 +62,7 @@ use crate::args::NpmInstallDepsProvider;
|
||||||
use crate::args::PermissionFlags;
|
use crate::args::PermissionFlags;
|
||||||
use crate::args::UnstableConfig;
|
use crate::args::UnstableConfig;
|
||||||
use crate::cache::DenoDir;
|
use crate::cache::DenoDir;
|
||||||
|
use crate::emit::Emitter;
|
||||||
use crate::file_fetcher::FileFetcher;
|
use crate::file_fetcher::FileFetcher;
|
||||||
use crate::http_util::HttpClientProvider;
|
use crate::http_util::HttpClientProvider;
|
||||||
use crate::npm::CliNpmResolver;
|
use crate::npm::CliNpmResolver;
|
||||||
|
@ -60,12 +74,63 @@ use crate::util::fs::canonicalize_path_maybe_not_exists;
|
||||||
use crate::util::progress_bar::ProgressBar;
|
use crate::util::progress_bar::ProgressBar;
|
||||||
use crate::util::progress_bar::ProgressBarStyle;
|
use crate::util::progress_bar::ProgressBarStyle;
|
||||||
|
|
||||||
|
use super::file_system::DenoCompileFileSystem;
|
||||||
|
use super::serialization::deserialize_binary_data_section;
|
||||||
|
use super::serialization::serialize_binary_data_section;
|
||||||
|
use super::serialization::DenoCompileModuleData;
|
||||||
|
use super::serialization::DeserializedDataSection;
|
||||||
|
use super::serialization::RemoteModulesStore;
|
||||||
|
use super::serialization::RemoteModulesStoreBuilder;
|
||||||
use super::virtual_fs::FileBackedVfs;
|
use super::virtual_fs::FileBackedVfs;
|
||||||
use super::virtual_fs::VfsBuilder;
|
use super::virtual_fs::VfsBuilder;
|
||||||
use super::virtual_fs::VfsRoot;
|
use super::virtual_fs::VfsRoot;
|
||||||
use super::virtual_fs::VirtualDirectory;
|
use super::virtual_fs::VirtualDirectory;
|
||||||
|
|
||||||
const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd";
|
/// A URL that can be designated as the base for relative URLs.
|
||||||
|
///
|
||||||
|
/// After creation, this URL may be used to get the key for a
|
||||||
|
/// module in the binary.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct StandaloneRelativeFileBaseUrl<'a>(&'a Url);
|
||||||
|
|
||||||
|
impl<'a> From<&'a Url> for StandaloneRelativeFileBaseUrl<'a> {
|
||||||
|
fn from(url: &'a Url) -> Self {
|
||||||
|
Self(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StandaloneRelativeFileBaseUrl<'a> {
|
||||||
|
pub fn new(url: &'a Url) -> Self {
|
||||||
|
debug_assert_eq!(url.scheme(), "file");
|
||||||
|
Self(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the module map key of the provided specifier.
|
||||||
|
///
|
||||||
|
/// * Descendant file specifiers will be made relative to the base.
|
||||||
|
/// * Non-descendant file specifiers will stay as-is (absolute).
|
||||||
|
/// * Non-file specifiers will stay as-is.
|
||||||
|
pub fn specifier_key<'b>(&self, target: &'b Url) -> Cow<'b, str> {
|
||||||
|
if target.scheme() != "file" {
|
||||||
|
return Cow::Borrowed(target.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.0.make_relative(target) {
|
||||||
|
Some(relative) => {
|
||||||
|
if relative.starts_with("../") {
|
||||||
|
Cow::Borrowed(target.as_str())
|
||||||
|
} else {
|
||||||
|
Cow::Owned(relative)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Cow::Borrowed(target.as_str()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &Url {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub enum NodeModules {
|
pub enum NodeModules {
|
||||||
|
@ -120,78 +185,23 @@ pub struct Metadata {
|
||||||
pub unstable_config: UnstableConfig,
|
pub unstable_config: UnstableConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_npm_vfs(root_dir_path: PathBuf) -> Result<FileBackedVfs, AnyError> {
|
|
||||||
let data = libsui::find_section("d3n0l4nd").unwrap();
|
|
||||||
|
|
||||||
// We do the first part sync so it can complete quickly
|
|
||||||
let trailer: [u8; TRAILER_SIZE] = data[0..TRAILER_SIZE].try_into().unwrap();
|
|
||||||
let trailer = match Trailer::parse(&trailer)? {
|
|
||||||
None => panic!("Could not find trailer"),
|
|
||||||
Some(trailer) => trailer,
|
|
||||||
};
|
|
||||||
let data = &data[TRAILER_SIZE..];
|
|
||||||
|
|
||||||
let vfs_data =
|
|
||||||
&data[trailer.npm_vfs_pos as usize..trailer.npm_files_pos as usize];
|
|
||||||
let mut dir: VirtualDirectory = serde_json::from_slice(vfs_data)?;
|
|
||||||
|
|
||||||
// align the name of the directory with the root dir
|
|
||||||
dir.name = root_dir_path
|
|
||||||
.file_name()
|
|
||||||
.unwrap()
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let fs_root = VfsRoot {
|
|
||||||
dir,
|
|
||||||
root_path: root_dir_path,
|
|
||||||
start_file_offset: trailer.npm_files_pos,
|
|
||||||
};
|
|
||||||
Ok(FileBackedVfs::new(data.to_vec(), fs_root))
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
eszip: eszip::EszipV2,
|
npm_snapshot: Option<SerializedNpmResolutionSnapshot>,
|
||||||
npm_vfs: Option<&VirtualDirectory>,
|
remote_modules: &RemoteModulesStoreBuilder,
|
||||||
npm_files: &Vec<Vec<u8>>,
|
vfs: VfsBuilder,
|
||||||
compile_flags: &CompileFlags,
|
compile_flags: &CompileFlags,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
let metadata = serde_json::to_string(metadata)?.as_bytes().to_vec();
|
let data_section_bytes =
|
||||||
let npm_vfs = serde_json::to_string(&npm_vfs)?.as_bytes().to_vec();
|
serialize_binary_data_section(metadata, npm_snapshot, remote_modules, vfs)?;
|
||||||
let eszip_archive = eszip.into_bytes();
|
|
||||||
|
|
||||||
let mut writer = Vec::new();
|
|
||||||
|
|
||||||
// write the trailer, which includes the positions
|
|
||||||
// of the data blocks in the file
|
|
||||||
writer.write_all(&{
|
|
||||||
let metadata_pos = eszip_archive.len() as u64;
|
|
||||||
let npm_vfs_pos = metadata_pos + (metadata.len() as u64);
|
|
||||||
let npm_files_pos = npm_vfs_pos + (npm_vfs.len() as u64);
|
|
||||||
Trailer {
|
|
||||||
eszip_pos: 0,
|
|
||||||
metadata_pos,
|
|
||||||
npm_vfs_pos,
|
|
||||||
npm_files_pos,
|
|
||||||
}
|
|
||||||
.as_bytes()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
writer.write_all(&eszip_archive)?;
|
|
||||||
writer.write_all(&metadata)?;
|
|
||||||
writer.write_all(&npm_vfs)?;
|
|
||||||
for file in npm_files {
|
|
||||||
writer.write_all(file)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let target = compile_flags.resolve_target();
|
let target = compile_flags.resolve_target();
|
||||||
if target.contains("linux") {
|
if target.contains("linux") {
|
||||||
libsui::Elf::new(&original_bin).append(
|
libsui::Elf::new(&original_bin).append(
|
||||||
"d3n0l4nd",
|
"d3n0l4nd",
|
||||||
&writer,
|
&data_section_bytes,
|
||||||
&mut file_writer,
|
&mut file_writer,
|
||||||
)?;
|
)?;
|
||||||
} else if target.contains("windows") {
|
} else if target.contains("windows") {
|
||||||
|
@ -201,11 +211,11 @@ fn write_binary_bytes(
|
||||||
pe = pe.set_icon(&icon)?;
|
pe = pe.set_icon(&icon)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
pe.write_resource("d3n0l4nd", writer)?
|
pe.write_resource("d3n0l4nd", data_section_bytes)?
|
||||||
.build(&mut file_writer)?;
|
.build(&mut file_writer)?;
|
||||||
} else if target.contains("darwin") {
|
} else if target.contains("darwin") {
|
||||||
libsui::Macho::from(original_bin)?
|
libsui::Macho::from(original_bin)?
|
||||||
.write_section("d3n0l4nd", writer)?
|
.write_section("d3n0l4nd", data_section_bytes)?
|
||||||
.build_and_sign(&mut file_writer)?;
|
.build_and_sign(&mut file_writer)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -221,6 +231,63 @@ pub fn is_standalone_binary(exe_path: &Path) -> bool {
|
||||||
|| libsui::utils::is_macho(&data)
|
|| libsui::utils::is_macho(&data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct StandaloneData {
|
||||||
|
pub fs: Arc<dyn deno_fs::FileSystem>,
|
||||||
|
pub metadata: Metadata,
|
||||||
|
pub modules: StandaloneModules,
|
||||||
|
pub npm_snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
|
||||||
|
pub root_path: PathBuf,
|
||||||
|
pub vfs: Arc<FileBackedVfs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StandaloneModules {
|
||||||
|
remote_modules: RemoteModulesStore,
|
||||||
|
vfs: Arc<FileBackedVfs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StandaloneModules {
|
||||||
|
pub fn resolve_specifier<'a>(
|
||||||
|
&'a self,
|
||||||
|
specifier: &'a ModuleSpecifier,
|
||||||
|
) -> Result<Option<&'a ModuleSpecifier>, AnyError> {
|
||||||
|
if specifier.scheme() == "file" {
|
||||||
|
Ok(Some(specifier))
|
||||||
|
} else {
|
||||||
|
self.remote_modules.resolve_specifier(specifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<'a>(
|
||||||
|
&'a self,
|
||||||
|
specifier: &'a ModuleSpecifier,
|
||||||
|
) -> Result<Option<DenoCompileModuleData<'a>>, AnyError> {
|
||||||
|
if specifier.scheme() == "file" {
|
||||||
|
let path = deno_path_util::url_to_file_path(specifier)?;
|
||||||
|
let bytes = match self.vfs.file_entry(&path) {
|
||||||
|
Ok(entry) => self.vfs.read_file_all(entry)?,
|
||||||
|
Err(err) if err.kind() == ErrorKind::NotFound => {
|
||||||
|
let bytes = match RealFs.read_file_sync(&path, None) {
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
Err(FsError::Io(err)) if err.kind() == ErrorKind::NotFound => {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
|
Cow::Owned(bytes)
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
|
Ok(Some(DenoCompileModuleData {
|
||||||
|
media_type: MediaType::from_specifier(specifier),
|
||||||
|
specifier,
|
||||||
|
data: bytes,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
self.remote_modules.read(specifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This function will try to run this binary as a standalone binary
|
/// This function will try to run this binary as a standalone binary
|
||||||
/// produced by `deno compile`. It determines if this is a standalone
|
/// produced by `deno compile`. It determines if this is a standalone
|
||||||
/// binary by skipping over the trailer width at the end of the file,
|
/// binary by skipping over the trailer width at the end of the file,
|
||||||
|
@ -228,110 +295,66 @@ pub fn is_standalone_binary(exe_path: &Path) -> bool {
|
||||||
/// the bundle is executed. If not, this function exits with `Ok(None)`.
|
/// the bundle is executed. If not, this function exits with `Ok(None)`.
|
||||||
pub fn extract_standalone(
|
pub fn extract_standalone(
|
||||||
cli_args: Cow<Vec<OsString>>,
|
cli_args: Cow<Vec<OsString>>,
|
||||||
) -> Result<
|
) -> Result<Option<StandaloneData>, AnyError> {
|
||||||
Option<impl Future<Output = Result<(Metadata, eszip::EszipV2), AnyError>>>,
|
|
||||||
AnyError,
|
|
||||||
> {
|
|
||||||
let Some(data) = libsui::find_section("d3n0l4nd") else {
|
let Some(data) = libsui::find_section("d3n0l4nd") else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
// We do the first part sync so it can complete quickly
|
let DeserializedDataSection {
|
||||||
let trailer = match Trailer::parse(&data[0..TRAILER_SIZE])? {
|
mut metadata,
|
||||||
|
npm_snapshot,
|
||||||
|
remote_modules,
|
||||||
|
mut vfs_dir,
|
||||||
|
vfs_files_data,
|
||||||
|
} = match deserialize_binary_data_section(data)? {
|
||||||
|
Some(data_section) => data_section,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some(trailer) => trailer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let root_path = {
|
||||||
|
let maybe_current_exe = std::env::current_exe().ok();
|
||||||
|
let current_exe_name = maybe_current_exe
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|p| p.file_name())
|
||||||
|
.map(|p| p.to_string_lossy())
|
||||||
|
// should never happen
|
||||||
|
.unwrap_or_else(|| Cow::Borrowed("binary"));
|
||||||
|
std::env::temp_dir().join(format!("deno-compile-{}", current_exe_name))
|
||||||
|
};
|
||||||
let cli_args = cli_args.into_owned();
|
let cli_args = cli_args.into_owned();
|
||||||
// If we have an eszip, read it out
|
|
||||||
Ok(Some(async move {
|
|
||||||
let bufreader =
|
|
||||||
deno_core::futures::io::BufReader::new(&data[TRAILER_SIZE..]);
|
|
||||||
|
|
||||||
let (eszip, loader) = eszip::EszipV2::parse(bufreader)
|
|
||||||
.await
|
|
||||||
.context("Failed to parse eszip header")?;
|
|
||||||
|
|
||||||
let bufreader = loader.await.context("Failed to parse eszip archive")?;
|
|
||||||
|
|
||||||
let mut metadata = String::new();
|
|
||||||
|
|
||||||
bufreader
|
|
||||||
.take(trailer.metadata_len())
|
|
||||||
.read_to_string(&mut metadata)
|
|
||||||
.await
|
|
||||||
.context("Failed to read metadata from the current executable")?;
|
|
||||||
|
|
||||||
let mut metadata: Metadata = serde_json::from_str(&metadata).unwrap();
|
|
||||||
metadata.argv.reserve(cli_args.len() - 1);
|
metadata.argv.reserve(cli_args.len() - 1);
|
||||||
for arg in cli_args.into_iter().skip(1) {
|
for arg in cli_args.into_iter().skip(1) {
|
||||||
metadata.argv.push(arg.into_string().unwrap());
|
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();
|
||||||
|
|
||||||
Ok((metadata, eszip))
|
let fs_root = VfsRoot {
|
||||||
|
dir: vfs_dir,
|
||||||
|
root_path: root_path.clone(),
|
||||||
|
start_file_offset: 0,
|
||||||
|
};
|
||||||
|
Arc::new(FileBackedVfs::new(Cow::Borrowed(vfs_files_data), fs_root))
|
||||||
|
};
|
||||||
|
let fs: Arc<dyn deno_fs::FileSystem> =
|
||||||
|
Arc::new(DenoCompileFileSystem::new(vfs.clone()));
|
||||||
|
Ok(Some(StandaloneData {
|
||||||
|
fs,
|
||||||
|
metadata,
|
||||||
|
modules: StandaloneModules {
|
||||||
|
remote_modules,
|
||||||
|
vfs: vfs.clone(),
|
||||||
|
},
|
||||||
|
npm_snapshot,
|
||||||
|
root_path,
|
||||||
|
vfs,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const TRAILER_SIZE: usize = std::mem::size_of::<Trailer>() + 8; // 8 bytes for the magic trailer string
|
|
||||||
|
|
||||||
struct Trailer {
|
|
||||||
eszip_pos: u64,
|
|
||||||
metadata_pos: u64,
|
|
||||||
npm_vfs_pos: u64,
|
|
||||||
npm_files_pos: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Trailer {
|
|
||||||
pub fn parse(trailer: &[u8]) -> Result<Option<Trailer>, AnyError> {
|
|
||||||
let (magic_trailer, rest) = trailer.split_at(8);
|
|
||||||
if magic_trailer != MAGIC_TRAILER {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (eszip_archive_pos, rest) = rest.split_at(8);
|
|
||||||
let (metadata_pos, rest) = rest.split_at(8);
|
|
||||||
let (npm_vfs_pos, npm_files_pos) = rest.split_at(8);
|
|
||||||
let eszip_archive_pos = u64_from_bytes(eszip_archive_pos)?;
|
|
||||||
let metadata_pos = u64_from_bytes(metadata_pos)?;
|
|
||||||
let npm_vfs_pos = u64_from_bytes(npm_vfs_pos)?;
|
|
||||||
let npm_files_pos = u64_from_bytes(npm_files_pos)?;
|
|
||||||
Ok(Some(Trailer {
|
|
||||||
eszip_pos: eszip_archive_pos,
|
|
||||||
metadata_pos,
|
|
||||||
npm_vfs_pos,
|
|
||||||
npm_files_pos,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn metadata_len(&self) -> u64 {
|
|
||||||
self.npm_vfs_pos - self.metadata_pos
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn npm_vfs_len(&self) -> u64 {
|
|
||||||
self.npm_files_pos - self.npm_vfs_pos
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_bytes(&self) -> Vec<u8> {
|
|
||||||
let mut trailer = MAGIC_TRAILER.to_vec();
|
|
||||||
trailer.write_all(&self.eszip_pos.to_be_bytes()).unwrap();
|
|
||||||
trailer.write_all(&self.metadata_pos.to_be_bytes()).unwrap();
|
|
||||||
trailer.write_all(&self.npm_vfs_pos.to_be_bytes()).unwrap();
|
|
||||||
trailer
|
|
||||||
.write_all(&self.npm_files_pos.to_be_bytes())
|
|
||||||
.unwrap();
|
|
||||||
trailer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn u64_from_bytes(arr: &[u8]) -> Result<u64, AnyError> {
|
|
||||||
let fixed_arr: &[u8; 8] = arr
|
|
||||||
.try_into()
|
|
||||||
.context("Failed to convert the buffer into a fixed-size array")?;
|
|
||||||
Ok(u64::from_be_bytes(*fixed_arr))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DenoCompileBinaryWriter<'a> {
|
pub struct DenoCompileBinaryWriter<'a> {
|
||||||
deno_dir: &'a DenoDir,
|
deno_dir: &'a DenoDir,
|
||||||
|
emitter: &'a Emitter,
|
||||||
file_fetcher: &'a FileFetcher,
|
file_fetcher: &'a FileFetcher,
|
||||||
http_client_provider: &'a HttpClientProvider,
|
http_client_provider: &'a HttpClientProvider,
|
||||||
npm_resolver: &'a dyn CliNpmResolver,
|
npm_resolver: &'a dyn CliNpmResolver,
|
||||||
|
@ -343,6 +366,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
deno_dir: &'a DenoDir,
|
deno_dir: &'a DenoDir,
|
||||||
|
emitter: &'a Emitter,
|
||||||
file_fetcher: &'a FileFetcher,
|
file_fetcher: &'a FileFetcher,
|
||||||
http_client_provider: &'a HttpClientProvider,
|
http_client_provider: &'a HttpClientProvider,
|
||||||
npm_resolver: &'a dyn CliNpmResolver,
|
npm_resolver: &'a dyn CliNpmResolver,
|
||||||
|
@ -351,6 +375,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
deno_dir,
|
deno_dir,
|
||||||
|
emitter,
|
||||||
file_fetcher,
|
file_fetcher,
|
||||||
http_client_provider,
|
http_client_provider,
|
||||||
npm_resolver,
|
npm_resolver,
|
||||||
|
@ -362,8 +387,8 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
pub async fn write_bin(
|
pub async fn write_bin(
|
||||||
&self,
|
&self,
|
||||||
writer: File,
|
writer: File,
|
||||||
eszip: eszip::EszipV2,
|
graph: &ModuleGraph,
|
||||||
root_dir_url: EszipRelativeFileBaseUrl<'_>,
|
root_dir_url: StandaloneRelativeFileBaseUrl<'_>,
|
||||||
entrypoint: &ModuleSpecifier,
|
entrypoint: &ModuleSpecifier,
|
||||||
compile_flags: &CompileFlags,
|
compile_flags: &CompileFlags,
|
||||||
cli_options: &CliOptions,
|
cli_options: &CliOptions,
|
||||||
|
@ -390,15 +415,17 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.write_standalone_binary(
|
self
|
||||||
|
.write_standalone_binary(
|
||||||
writer,
|
writer,
|
||||||
original_binary,
|
original_binary,
|
||||||
eszip,
|
graph,
|
||||||
root_dir_url,
|
root_dir_url,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
cli_options,
|
cli_options,
|
||||||
compile_flags,
|
compile_flags,
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_base_binary(
|
async fn get_base_binary(
|
||||||
|
@ -493,12 +520,12 @@ 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)]
|
||||||
fn write_standalone_binary(
|
async fn write_standalone_binary(
|
||||||
&self,
|
&self,
|
||||||
writer: File,
|
writer: File,
|
||||||
original_bin: Vec<u8>,
|
original_bin: Vec<u8>,
|
||||||
mut eszip: eszip::EszipV2,
|
graph: &ModuleGraph,
|
||||||
root_dir_url: EszipRelativeFileBaseUrl<'_>,
|
root_dir_url: StandaloneRelativeFileBaseUrl<'_>,
|
||||||
entrypoint: &ModuleSpecifier,
|
entrypoint: &ModuleSpecifier,
|
||||||
cli_options: &CliOptions,
|
cli_options: &CliOptions,
|
||||||
compile_flags: &CompileFlags,
|
compile_flags: &CompileFlags,
|
||||||
|
@ -512,19 +539,17 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let root_path = root_dir_url.inner().to_file_path().unwrap();
|
let root_path = root_dir_url.inner().to_file_path().unwrap();
|
||||||
let (npm_vfs, npm_files, node_modules) = match self.npm_resolver.as_inner()
|
let (maybe_npm_vfs, node_modules, npm_snapshot) = match self
|
||||||
|
.npm_resolver
|
||||||
|
.as_inner()
|
||||||
{
|
{
|
||||||
InnerCliNpmResolverRef::Managed(managed) => {
|
InnerCliNpmResolverRef::Managed(managed) => {
|
||||||
let snapshot =
|
let snapshot =
|
||||||
managed.serialized_valid_snapshot_for_system(&self.npm_system_info);
|
managed.serialized_valid_snapshot_for_system(&self.npm_system_info);
|
||||||
if !snapshot.as_serialized().packages.is_empty() {
|
if !snapshot.as_serialized().packages.is_empty() {
|
||||||
let (root_dir, files) = self
|
let npm_vfs_builder = self.build_npm_vfs(&root_path, cli_options)?;
|
||||||
.build_vfs(&root_path, cli_options)?
|
|
||||||
.into_dir_and_files();
|
|
||||||
eszip.add_npm_snapshot(snapshot);
|
|
||||||
(
|
(
|
||||||
Some(root_dir),
|
Some(npm_vfs_builder),
|
||||||
files,
|
|
||||||
Some(NodeModules::Managed {
|
Some(NodeModules::Managed {
|
||||||
node_modules_dir: self.npm_resolver.root_node_modules_path().map(
|
node_modules_dir: self.npm_resolver.root_node_modules_path().map(
|
||||||
|path| {
|
|path| {
|
||||||
|
@ -536,18 +561,16 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
Some(snapshot),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(None, Vec::new(), None)
|
(None, None, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InnerCliNpmResolverRef::Byonm(resolver) => {
|
InnerCliNpmResolverRef::Byonm(resolver) => {
|
||||||
let (root_dir, files) = self
|
let npm_vfs_builder = self.build_npm_vfs(&root_path, cli_options)?;
|
||||||
.build_vfs(&root_path, cli_options)?
|
|
||||||
.into_dir_and_files();
|
|
||||||
(
|
(
|
||||||
Some(root_dir),
|
Some(npm_vfs_builder),
|
||||||
files,
|
|
||||||
Some(NodeModules::Byonm {
|
Some(NodeModules::Byonm {
|
||||||
root_node_modules_dir: resolver.root_node_modules_path().map(
|
root_node_modules_dir: resolver.root_node_modules_path().map(
|
||||||
|node_modules_dir| {
|
|node_modules_dir| {
|
||||||
|
@ -560,9 +583,67 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let mut vfs = if let Some(npm_vfs) = maybe_npm_vfs {
|
||||||
|
npm_vfs
|
||||||
|
} else {
|
||||||
|
VfsBuilder::new(root_path.clone())?
|
||||||
|
};
|
||||||
|
let mut remote_modules_store = RemoteModulesStoreBuilder::default();
|
||||||
|
for module in graph.modules() {
|
||||||
|
if module.specifier().scheme() == "data" {
|
||||||
|
continue; // don't store data urls as an entry as they're in the code
|
||||||
|
}
|
||||||
|
let (maybe_source, media_type) = match module {
|
||||||
|
deno_graph::Module::Js(m) => {
|
||||||
|
// todo(https://github.com/denoland/deno_media_type/pull/12): use is_emittable()
|
||||||
|
let is_emittable = matches!(
|
||||||
|
m.media_type,
|
||||||
|
MediaType::TypeScript
|
||||||
|
| MediaType::Mts
|
||||||
|
| MediaType::Cts
|
||||||
|
| MediaType::Jsx
|
||||||
|
| MediaType::Tsx
|
||||||
|
);
|
||||||
|
let source = if is_emittable {
|
||||||
|
let source = self
|
||||||
|
.emitter
|
||||||
|
.emit_parsed_source(&m.specifier, m.media_type, &m.source)
|
||||||
|
.await?;
|
||||||
|
source.to_vec()
|
||||||
|
} else {
|
||||||
|
m.source.as_bytes().to_vec()
|
||||||
|
};
|
||||||
|
(Some(source), m.media_type)
|
||||||
|
}
|
||||||
|
deno_graph::Module::Json(m) => {
|
||||||
|
(Some(m.source.as_bytes().to_vec()), m.media_type)
|
||||||
|
}
|
||||||
|
deno_graph::Module::Npm(_)
|
||||||
|
| deno_graph::Module::Node(_)
|
||||||
|
| deno_graph::Module::External(_) => (None, MediaType::Unknown),
|
||||||
|
};
|
||||||
|
if module.specifier().scheme() == "file" {
|
||||||
|
let file_path = deno_path_util::url_to_file_path(module.specifier())?;
|
||||||
|
vfs
|
||||||
|
.add_file_with_data(
|
||||||
|
&file_path,
|
||||||
|
match maybe_source {
|
||||||
|
Some(source) => source,
|
||||||
|
None => RealFs.read_file_sync(&file_path, None)?,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!("Failed adding '{}'", file_path.display())
|
||||||
|
})?;
|
||||||
|
} else if let Some(source) = maybe_source {
|
||||||
|
remote_modules_store.add(module.specifier(), media_type, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remote_modules_store.add_redirects(&graph.redirects);
|
||||||
|
|
||||||
let env_vars_from_env_file = match cli_options.env_file_name() {
|
let env_vars_from_env_file = match cli_options.env_file_name() {
|
||||||
Some(env_filename) => {
|
Some(env_filename) => {
|
||||||
|
@ -636,14 +717,14 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
writer,
|
writer,
|
||||||
original_bin,
|
original_bin,
|
||||||
&metadata,
|
&metadata,
|
||||||
eszip,
|
npm_snapshot.map(|s| s.into_serialized()),
|
||||||
npm_vfs.as_ref(),
|
&remote_modules_store,
|
||||||
&npm_files,
|
vfs,
|
||||||
compile_flags,
|
compile_flags,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_vfs(
|
fn build_npm_vfs(
|
||||||
&self,
|
&self,
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
cli_options: &CliOptions,
|
cli_options: &CliOptions,
|
||||||
|
@ -664,8 +745,8 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
} else {
|
} else {
|
||||||
// DO NOT include the user's registry url as it may contain credentials,
|
// DO NOT include the user's registry url as it may contain credentials,
|
||||||
// but also don't make this dependent on the registry url
|
// but also don't make this dependent on the registry url
|
||||||
let root_path = npm_resolver.global_cache_root_folder();
|
let global_cache_root_path = npm_resolver.global_cache_root_folder();
|
||||||
let mut builder = VfsBuilder::new(root_path)?;
|
let mut builder = VfsBuilder::new(global_cache_root_path)?;
|
||||||
let mut packages =
|
let mut packages =
|
||||||
npm_resolver.all_system_packages(&self.npm_system_info);
|
npm_resolver.all_system_packages(&self.npm_system_info);
|
||||||
packages.sort_by(|a, b| a.id.cmp(&b.id)); // determinism
|
packages.sort_by(|a, b| a.id.cmp(&b.id)); // determinism
|
||||||
|
@ -675,12 +756,12 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
builder.add_dir_recursive(&folder)?;
|
builder.add_dir_recursive(&folder)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten all the registries folders into a single "node_modules/localhost" folder
|
// Flatten all the registries folders into a single ".deno_compile_node_modules/localhost" folder
|
||||||
// that will be used by denort when loading the npm cache. This avoids us exposing
|
// that will be used by denort when loading the npm cache. This avoids us exposing
|
||||||
// the user's private registry information and means we don't have to bother
|
// the user's private registry information and means we don't have to bother
|
||||||
// serializing all the different registry config into the binary.
|
// serializing all the different registry config into the binary.
|
||||||
builder.with_root_dir(|root_dir| {
|
builder.with_root_dir(|root_dir| {
|
||||||
root_dir.name = "node_modules".to_string();
|
root_dir.name = ".deno_compile_node_modules".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 std::mem::take(&mut root_dir.entries) {
|
||||||
|
@ -715,6 +796,8 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
root_dir.entries = new_entries;
|
root_dir.entries = new_entries;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.set_new_root_path(root_path.to_path_buf())?;
|
||||||
|
|
||||||
Ok(builder)
|
Ok(builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,8 @@ use super::virtual_fs::FileBackedVfs;
|
||||||
pub struct DenoCompileFileSystem(Arc<FileBackedVfs>);
|
pub struct DenoCompileFileSystem(Arc<FileBackedVfs>);
|
||||||
|
|
||||||
impl DenoCompileFileSystem {
|
impl DenoCompileFileSystem {
|
||||||
pub fn new(vfs: FileBackedVfs) -> Self {
|
pub fn new(vfs: Arc<FileBackedVfs>) -> Self {
|
||||||
Self(Arc::new(vfs))
|
Self(vfs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_if_in_vfs(&self, path: &Path) -> FsResult<()> {
|
fn error_if_in_vfs(&self, path: &Path) -> FsResult<()> {
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
use binary::StandaloneData;
|
||||||
|
use binary::StandaloneModules;
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
use deno_cache_dir::npm::NpmCacheDir;
|
use deno_cache_dir::npm::NpmCacheDir;
|
||||||
use deno_config::workspace::MappedResolution;
|
use deno_config::workspace::MappedResolution;
|
||||||
|
@ -38,7 +40,6 @@ use deno_runtime::permissions::RuntimePermissionDescriptorParser;
|
||||||
use deno_runtime::WorkerExecutionMode;
|
use deno_runtime::WorkerExecutionMode;
|
||||||
use deno_runtime::WorkerLogLevel;
|
use deno_runtime::WorkerLogLevel;
|
||||||
use deno_semver::npm::NpmPackageReqReference;
|
use deno_semver::npm::NpmPackageReqReference;
|
||||||
use eszip::EszipRelativeFileBaseUrl;
|
|
||||||
use import_map::parse_from_json;
|
use import_map::parse_from_json;
|
||||||
use node_resolver::analyze::NodeCodeTranslator;
|
use node_resolver::analyze::NodeCodeTranslator;
|
||||||
use node_resolver::NodeResolutionMode;
|
use node_resolver::NodeResolutionMode;
|
||||||
|
@ -54,6 +55,7 @@ use crate::args::CacheSetting;
|
||||||
use crate::args::NpmInstallDepsProvider;
|
use crate::args::NpmInstallDepsProvider;
|
||||||
use crate::args::StorageKeyResolver;
|
use crate::args::StorageKeyResolver;
|
||||||
use crate::cache::Caches;
|
use crate::cache::Caches;
|
||||||
|
use crate::cache::DenoCacheEnvFsAdapter;
|
||||||
use crate::cache::DenoDirProvider;
|
use crate::cache::DenoDirProvider;
|
||||||
use crate::cache::NodeAnalysisCache;
|
use crate::cache::NodeAnalysisCache;
|
||||||
use crate::cache::RealDenoCacheEnv;
|
use crate::cache::RealDenoCacheEnv;
|
||||||
|
@ -78,52 +80,18 @@ use crate::worker::ModuleLoaderFactory;
|
||||||
|
|
||||||
pub mod binary;
|
pub mod binary;
|
||||||
mod file_system;
|
mod file_system;
|
||||||
|
mod serialization;
|
||||||
mod virtual_fs;
|
mod virtual_fs;
|
||||||
|
|
||||||
pub use binary::extract_standalone;
|
pub use binary::extract_standalone;
|
||||||
pub use binary::is_standalone_binary;
|
pub use binary::is_standalone_binary;
|
||||||
pub use binary::DenoCompileBinaryWriter;
|
pub use binary::DenoCompileBinaryWriter;
|
||||||
|
|
||||||
use self::binary::load_npm_vfs;
|
|
||||||
use self::binary::Metadata;
|
use self::binary::Metadata;
|
||||||
use self::file_system::DenoCompileFileSystem;
|
use self::file_system::DenoCompileFileSystem;
|
||||||
|
|
||||||
struct WorkspaceEszipModule {
|
|
||||||
specifier: ModuleSpecifier,
|
|
||||||
inner: eszip::Module,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WorkspaceEszip {
|
|
||||||
eszip: eszip::EszipV2,
|
|
||||||
root_dir_url: Arc<ModuleSpecifier>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkspaceEszip {
|
|
||||||
pub fn get_module(
|
|
||||||
&self,
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
) -> Option<WorkspaceEszipModule> {
|
|
||||||
if specifier.scheme() == "file" {
|
|
||||||
let specifier_key = EszipRelativeFileBaseUrl::new(&self.root_dir_url)
|
|
||||||
.specifier_key(specifier);
|
|
||||||
let module = self.eszip.get_module(&specifier_key)?;
|
|
||||||
let specifier = self.root_dir_url.join(&module.specifier).unwrap();
|
|
||||||
Some(WorkspaceEszipModule {
|
|
||||||
specifier,
|
|
||||||
inner: module,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let module = self.eszip.get_module(specifier.as_str())?;
|
|
||||||
Some(WorkspaceEszipModule {
|
|
||||||
specifier: ModuleSpecifier::parse(&module.specifier).unwrap(),
|
|
||||||
inner: module,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SharedModuleLoaderState {
|
struct SharedModuleLoaderState {
|
||||||
eszip: WorkspaceEszip,
|
modules: StandaloneModules,
|
||||||
workspace_resolver: WorkspaceResolver,
|
workspace_resolver: WorkspaceResolver,
|
||||||
node_resolver: Arc<CliNodeResolver>,
|
node_resolver: Arc<CliNodeResolver>,
|
||||||
npm_module_loader: Arc<NpmModuleLoader>,
|
npm_module_loader: Arc<NpmModuleLoader>,
|
||||||
|
@ -249,8 +217,10 @@ impl ModuleLoader for EmbeddedModuleLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
if specifier.scheme() == "jsr" {
|
if specifier.scheme() == "jsr" {
|
||||||
if let Some(module) = self.shared.eszip.get_module(&specifier) {
|
if let Some(specifier) =
|
||||||
return Ok(module.specifier);
|
self.shared.modules.resolve_specifier(&specifier)?
|
||||||
|
{
|
||||||
|
return Ok(specifier.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,54 +315,28 @@ impl ModuleLoader for EmbeddedModuleLoader {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(module) = self.shared.eszip.get_module(original_specifier) else {
|
match self.shared.modules.read(original_specifier) {
|
||||||
return deno_core::ModuleLoadResponse::Sync(Err(type_error(format!(
|
Ok(Some(module)) => {
|
||||||
"{MODULE_NOT_FOUND}: {}",
|
let (module_specifier, module_type, module_source) =
|
||||||
original_specifier
|
module.into_for_v8();
|
||||||
))));
|
deno_core::ModuleLoadResponse::Sync(Ok(
|
||||||
};
|
deno_core::ModuleSource::new_with_redirect(
|
||||||
let original_specifier = original_specifier.clone();
|
module_type,
|
||||||
|
module_source,
|
||||||
deno_core::ModuleLoadResponse::Async(
|
original_specifier,
|
||||||
async move {
|
module_specifier,
|
||||||
let code = module.inner.source().await.ok_or_else(|| {
|
|
||||||
type_error(format!("Module not found: {}", original_specifier))
|
|
||||||
})?;
|
|
||||||
let code = arc_u8_to_arc_str(code)
|
|
||||||
.map_err(|_| type_error("Module source is not utf-8"))?;
|
|
||||||
Ok(deno_core::ModuleSource::new_with_redirect(
|
|
||||||
match module.inner.kind {
|
|
||||||
eszip::ModuleKind::JavaScript => ModuleType::JavaScript,
|
|
||||||
eszip::ModuleKind::Json => ModuleType::Json,
|
|
||||||
eszip::ModuleKind::Jsonc => {
|
|
||||||
return Err(type_error("jsonc modules not supported"))
|
|
||||||
}
|
|
||||||
eszip::ModuleKind::OpaqueData => {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ModuleSourceCode::String(code.into()),
|
|
||||||
&original_specifier,
|
|
||||||
&module.specifier,
|
|
||||||
None,
|
None,
|
||||||
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
.boxed_local(),
|
Ok(None) => deno_core::ModuleLoadResponse::Sync(Err(type_error(
|
||||||
)
|
format!("{MODULE_NOT_FOUND}: {}", original_specifier),
|
||||||
|
))),
|
||||||
|
Err(err) => deno_core::ModuleLoadResponse::Sync(Err(type_error(
|
||||||
|
format!("{:?}", err),
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arc_u8_to_arc_str(
|
|
||||||
arc_u8: Arc<[u8]>,
|
|
||||||
) -> Result<Arc<str>, std::str::Utf8Error> {
|
|
||||||
// Check that the string is valid UTF-8.
|
|
||||||
std::str::from_utf8(&arc_u8)?;
|
|
||||||
// SAFETY: the string is valid UTF-8, and the layout Arc<[u8]> is the same as
|
|
||||||
// Arc<str>. This is proven by the From<Arc<str>> impl for Arc<[u8]> from the
|
|
||||||
// standard library.
|
|
||||||
Ok(unsafe {
|
|
||||||
std::mem::transmute::<std::sync::Arc<[u8]>, std::sync::Arc<str>>(arc_u8)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StandaloneModuleLoaderFactory {
|
struct StandaloneModuleLoaderFactory {
|
||||||
|
@ -439,13 +383,15 @@ impl RootCertStoreProvider for StandaloneRootCertStoreProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(
|
pub async fn run(data: StandaloneData) -> Result<i32, AnyError> {
|
||||||
mut eszip: eszip::EszipV2,
|
let StandaloneData {
|
||||||
metadata: Metadata,
|
fs,
|
||||||
) -> Result<i32, AnyError> {
|
metadata,
|
||||||
let current_exe_path = std::env::current_exe().unwrap();
|
modules,
|
||||||
let current_exe_name =
|
npm_snapshot,
|
||||||
current_exe_path.file_name().unwrap().to_string_lossy();
|
root_path,
|
||||||
|
vfs,
|
||||||
|
} = data;
|
||||||
let deno_dir_provider = Arc::new(DenoDirProvider::new(None));
|
let deno_dir_provider = Arc::new(DenoDirProvider::new(None));
|
||||||
let root_cert_store_provider = Arc::new(StandaloneRootCertStoreProvider {
|
let root_cert_store_provider = Arc::new(StandaloneRootCertStoreProvider {
|
||||||
ca_stores: metadata.ca_stores,
|
ca_stores: metadata.ca_stores,
|
||||||
|
@ -459,35 +405,16 @@ pub async fn run(
|
||||||
));
|
));
|
||||||
// use a dummy npm registry url
|
// use a dummy npm registry url
|
||||||
let npm_registry_url = ModuleSpecifier::parse("https://localhost/").unwrap();
|
let npm_registry_url = ModuleSpecifier::parse("https://localhost/").unwrap();
|
||||||
let root_path =
|
|
||||||
std::env::temp_dir().join(format!("deno-compile-{}", current_exe_name));
|
|
||||||
let root_dir_url =
|
let root_dir_url =
|
||||||
Arc::new(ModuleSpecifier::from_directory_path(&root_path).unwrap());
|
Arc::new(ModuleSpecifier::from_directory_path(&root_path).unwrap());
|
||||||
let main_module = root_dir_url.join(&metadata.entrypoint_key).unwrap();
|
let main_module = root_dir_url.join(&metadata.entrypoint_key).unwrap();
|
||||||
let root_node_modules_path = root_path.join("node_modules");
|
let npm_global_cache_dir = root_path.join(".deno_compile_node_modules");
|
||||||
let npm_cache_dir = NpmCacheDir::new(
|
|
||||||
&RealDenoCacheEnv,
|
|
||||||
root_node_modules_path.clone(),
|
|
||||||
vec![npm_registry_url.clone()],
|
|
||||||
);
|
|
||||||
let npm_global_cache_dir = npm_cache_dir.get_cache_location();
|
|
||||||
let cache_setting = CacheSetting::Only;
|
let cache_setting = CacheSetting::Only;
|
||||||
let (fs, npm_resolver, maybe_vfs_root) = match metadata.node_modules {
|
let npm_resolver = match metadata.node_modules {
|
||||||
Some(binary::NodeModules::Managed { node_modules_dir }) => {
|
Some(binary::NodeModules::Managed { node_modules_dir }) => {
|
||||||
// this will always have a snapshot
|
let snapshot = npm_snapshot.unwrap();
|
||||||
let snapshot = eszip.take_npm_snapshot().unwrap();
|
|
||||||
let vfs_root_dir_path = if node_modules_dir.is_some() {
|
|
||||||
root_path.clone()
|
|
||||||
} else {
|
|
||||||
npm_cache_dir.root_dir().to_owned()
|
|
||||||
};
|
|
||||||
let vfs = load_npm_vfs(vfs_root_dir_path.clone())
|
|
||||||
.context("Failed to load npm vfs.")?;
|
|
||||||
let maybe_node_modules_path = node_modules_dir
|
let maybe_node_modules_path = node_modules_dir
|
||||||
.map(|node_modules_dir| vfs_root_dir_path.join(node_modules_dir));
|
.map(|node_modules_dir| root_path.join(node_modules_dir));
|
||||||
let fs = Arc::new(DenoCompileFileSystem::new(vfs))
|
|
||||||
as Arc<dyn deno_fs::FileSystem>;
|
|
||||||
let npm_resolver =
|
|
||||||
create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed(
|
create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed(
|
||||||
CliNpmResolverManagedCreateOptions {
|
CliNpmResolverManagedCreateOptions {
|
||||||
snapshot: CliNpmResolverManagedSnapshotOption::Specified(Some(
|
snapshot: CliNpmResolverManagedSnapshotOption::Specified(Some(
|
||||||
|
@ -517,31 +444,22 @@ pub async fn run(
|
||||||
lifecycle_scripts: Default::default(),
|
lifecycle_scripts: Default::default(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.await?;
|
.await?
|
||||||
(fs, npm_resolver, Some(vfs_root_dir_path))
|
|
||||||
}
|
}
|
||||||
Some(binary::NodeModules::Byonm {
|
Some(binary::NodeModules::Byonm {
|
||||||
root_node_modules_dir,
|
root_node_modules_dir,
|
||||||
}) => {
|
}) => {
|
||||||
let vfs_root_dir_path = root_path.clone();
|
|
||||||
let vfs = load_npm_vfs(vfs_root_dir_path.clone())
|
|
||||||
.context("Failed to load vfs.")?;
|
|
||||||
let root_node_modules_dir =
|
let root_node_modules_dir =
|
||||||
root_node_modules_dir.map(|p| vfs.root().join(p));
|
root_node_modules_dir.map(|p| vfs.root().join(p));
|
||||||
let fs = Arc::new(DenoCompileFileSystem::new(vfs))
|
create_cli_npm_resolver(CliNpmResolverCreateOptions::Byonm(
|
||||||
as Arc<dyn deno_fs::FileSystem>;
|
CliByonmNpmResolverCreateOptions {
|
||||||
let npm_resolver = create_cli_npm_resolver(
|
|
||||||
CliNpmResolverCreateOptions::Byonm(CliByonmNpmResolverCreateOptions {
|
|
||||||
fs: CliDenoResolverFs(fs.clone()),
|
fs: CliDenoResolverFs(fs.clone()),
|
||||||
root_node_modules_dir,
|
root_node_modules_dir,
|
||||||
}),
|
},
|
||||||
)
|
))
|
||||||
.await?;
|
.await?
|
||||||
(fs, npm_resolver, Some(vfs_root_dir_path))
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let fs = Arc::new(deno_fs::RealFs) as Arc<dyn deno_fs::FileSystem>;
|
|
||||||
let npm_resolver =
|
|
||||||
create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed(
|
create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed(
|
||||||
CliNpmResolverManagedCreateOptions {
|
CliNpmResolverManagedCreateOptions {
|
||||||
snapshot: CliNpmResolverManagedSnapshotOption::Specified(None),
|
snapshot: CliNpmResolverManagedSnapshotOption::Specified(None),
|
||||||
|
@ -557,14 +475,13 @@ pub async fn run(
|
||||||
// this is only used for installing packages, which isn't necessary with deno compile
|
// this is only used for installing packages, which isn't necessary with deno compile
|
||||||
NpmInstallDepsProvider::empty(),
|
NpmInstallDepsProvider::empty(),
|
||||||
),
|
),
|
||||||
// Packages from different registries are already inlined in the ESZip,
|
// Packages from different registries are already inlined in the binary,
|
||||||
// so no need to create actual `.npmrc` configuration.
|
// so no need to create actual `.npmrc` configuration.
|
||||||
npmrc: create_default_npmrc(),
|
npmrc: create_default_npmrc(),
|
||||||
lifecycle_scripts: Default::default(),
|
lifecycle_scripts: Default::default(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.await?;
|
.await?
|
||||||
(fs, npm_resolver, None)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -645,10 +562,7 @@ pub async fn run(
|
||||||
};
|
};
|
||||||
let module_loader_factory = StandaloneModuleLoaderFactory {
|
let module_loader_factory = StandaloneModuleLoaderFactory {
|
||||||
shared: Arc::new(SharedModuleLoaderState {
|
shared: Arc::new(SharedModuleLoaderState {
|
||||||
eszip: WorkspaceEszip {
|
modules,
|
||||||
eszip,
|
|
||||||
root_dir_url,
|
|
||||||
},
|
|
||||||
workspace_resolver,
|
workspace_resolver,
|
||||||
node_resolver: cli_node_resolver.clone(),
|
node_resolver: cli_node_resolver.clone(),
|
||||||
npm_module_loader: Arc::new(NpmModuleLoader::new(
|
npm_module_loader: Arc::new(NpmModuleLoader::new(
|
||||||
|
@ -663,19 +577,17 @@ pub async fn run(
|
||||||
let permissions = {
|
let permissions = {
|
||||||
let mut permissions =
|
let mut permissions =
|
||||||
metadata.permissions.to_options(/* cli_arg_urls */ &[]);
|
metadata.permissions.to_options(/* cli_arg_urls */ &[]);
|
||||||
// if running with an npm vfs, grant read access to it
|
// grant read access to the vfs
|
||||||
if let Some(vfs_root) = maybe_vfs_root {
|
|
||||||
match &mut permissions.allow_read {
|
match &mut permissions.allow_read {
|
||||||
Some(vec) if vec.is_empty() => {
|
Some(vec) if vec.is_empty() => {
|
||||||
// do nothing, already granted
|
// do nothing, already granted
|
||||||
}
|
}
|
||||||
Some(vec) => {
|
Some(vec) => {
|
||||||
vec.push(vfs_root.to_string_lossy().to_string());
|
vec.push(root_path.to_string_lossy().to_string());
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
permissions.allow_read =
|
permissions.allow_read =
|
||||||
Some(vec![vfs_root.to_string_lossy().to_string()]);
|
Some(vec![root_path.to_string_lossy().to_string()]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
642
cli/standalone/serialization.rs
Normal file
642
cli/standalone/serialization.rs
Normal file
|
@ -0,0 +1,642 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use deno_ast::MediaType;
|
||||||
|
use deno_core::anyhow::bail;
|
||||||
|
use deno_core::anyhow::Context;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use deno_core::serde_json;
|
||||||
|
use deno_core::url::Url;
|
||||||
|
use deno_core::FastString;
|
||||||
|
use deno_core::ModuleSourceCode;
|
||||||
|
use deno_core::ModuleType;
|
||||||
|
use deno_npm::resolution::SerializedNpmResolutionSnapshot;
|
||||||
|
use deno_npm::resolution::SerializedNpmResolutionSnapshotPackage;
|
||||||
|
use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot;
|
||||||
|
use deno_npm::NpmPackageId;
|
||||||
|
use deno_semver::package::PackageReq;
|
||||||
|
|
||||||
|
use crate::standalone::virtual_fs::VirtualDirectory;
|
||||||
|
|
||||||
|
use super::binary::Metadata;
|
||||||
|
use super::virtual_fs::VfsBuilder;
|
||||||
|
|
||||||
|
const MAGIC_BYTES: &[u8; 8] = b"d3n0l4nd";
|
||||||
|
|
||||||
|
/// Binary format:
|
||||||
|
/// * d3n0l4nd
|
||||||
|
/// * <metadata_len><metadata>
|
||||||
|
/// * <npm_snapshot_len><npm_snapshot>
|
||||||
|
/// * <remote_modules_len><remote_modules>
|
||||||
|
/// * <vfs_headers_len><vfs_headers>
|
||||||
|
/// * <vfs_file_data_len><vfs_file_data>
|
||||||
|
/// * d3n0l4nd
|
||||||
|
pub fn serialize_binary_data_section(
|
||||||
|
metadata: &Metadata,
|
||||||
|
npm_snapshot: Option<SerializedNpmResolutionSnapshot>,
|
||||||
|
remote_modules: &RemoteModulesStoreBuilder,
|
||||||
|
vfs: VfsBuilder,
|
||||||
|
) -> Result<Vec<u8>, AnyError> {
|
||||||
|
fn write_bytes_with_len(bytes: &mut Vec<u8>, data: &[u8]) {
|
||||||
|
bytes.extend_from_slice(&(data.len() as u64).to_le_bytes());
|
||||||
|
bytes.extend_from_slice(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
bytes.extend_from_slice(MAGIC_BYTES);
|
||||||
|
|
||||||
|
// 1. Metadata
|
||||||
|
{
|
||||||
|
let metadata = serde_json::to_string(metadata)?;
|
||||||
|
write_bytes_with_len(&mut bytes, metadata.as_bytes());
|
||||||
|
}
|
||||||
|
// 2. Npm snapshot
|
||||||
|
{
|
||||||
|
let npm_snapshot =
|
||||||
|
npm_snapshot.map(serialize_npm_snapshot).unwrap_or_default();
|
||||||
|
write_bytes_with_len(&mut bytes, &npm_snapshot);
|
||||||
|
}
|
||||||
|
// 3. Remote modules
|
||||||
|
{
|
||||||
|
let update_index = bytes.len();
|
||||||
|
bytes.extend_from_slice(&(0_u64).to_le_bytes());
|
||||||
|
let start_index = bytes.len();
|
||||||
|
remote_modules.write(&mut bytes)?;
|
||||||
|
let length = bytes.len() - start_index;
|
||||||
|
let length_bytes = (length as u64).to_le_bytes();
|
||||||
|
bytes[update_index..update_index + length_bytes.len()]
|
||||||
|
.copy_from_slice(&length_bytes);
|
||||||
|
}
|
||||||
|
// 4. VFS
|
||||||
|
{
|
||||||
|
let (vfs, vfs_files) = vfs.into_dir_and_files();
|
||||||
|
let vfs = serde_json::to_string(&vfs)?;
|
||||||
|
write_bytes_with_len(&mut bytes, vfs.as_bytes());
|
||||||
|
let vfs_bytes_len = vfs_files.iter().map(|f| f.len() as u64).sum::<u64>();
|
||||||
|
bytes.extend_from_slice(&vfs_bytes_len.to_le_bytes());
|
||||||
|
for file in &vfs_files {
|
||||||
|
bytes.extend_from_slice(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the magic bytes at the end so we can use it
|
||||||
|
// to make sure we've deserialized correctly
|
||||||
|
bytes.extend_from_slice(MAGIC_BYTES);
|
||||||
|
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeserializedDataSection {
|
||||||
|
pub metadata: Metadata,
|
||||||
|
pub npm_snapshot: Option<ValidSerializedNpmResolutionSnapshot>,
|
||||||
|
pub remote_modules: RemoteModulesStore,
|
||||||
|
pub vfs_dir: VirtualDirectory,
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
let (magic_bytes, input) = input.split_at(MAGIC_BYTES.len());
|
||||||
|
if magic_bytes != MAGIC_BYTES {
|
||||||
|
return Ok((input, false));
|
||||||
|
}
|
||||||
|
Ok((input, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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")?;
|
||||||
|
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")?;
|
||||||
|
// 4. VFS
|
||||||
|
let (input, data) = read_bytes_with_len(input).context("vfs")?;
|
||||||
|
let vfs_dir: VirtualDirectory =
|
||||||
|
serde_json::from_slice(data).context("deserializing vfs data")?;
|
||||||
|
let (input, vfs_files_data) =
|
||||||
|
read_bytes_with_len(input).context("reading vfs files data")?;
|
||||||
|
|
||||||
|
// finally ensure we read the magic bytes at the end
|
||||||
|
let (_input, found) = read_magic_bytes(input)?;
|
||||||
|
if !found {
|
||||||
|
bail!("Could not find magic bytes at the end of the data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(DeserializedDataSection {
|
||||||
|
metadata,
|
||||||
|
npm_snapshot,
|
||||||
|
remote_modules,
|
||||||
|
vfs_dir,
|
||||||
|
vfs_files_data,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RemoteModulesStoreBuilder {
|
||||||
|
specifiers: Vec<(String, u64)>,
|
||||||
|
data: Vec<(MediaType, 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>) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_redirects(&mut self, redirects: &BTreeMap<Url, Url>) {
|
||||||
|
self.redirects.reserve(redirects.len());
|
||||||
|
for (from, to) in redirects {
|
||||||
|
let from = from.to_string();
|
||||||
|
let to = to.to_string();
|
||||||
|
self.redirects_len += (4 + from.len() + 4 + to.len()) as u64;
|
||||||
|
self.redirects.push((from, to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&self, writer: &mut dyn Write) -> Result<(), AnyError> {
|
||||||
|
writer.write_all(&(self.specifiers.len() as u32).to_le_bytes())?;
|
||||||
|
writer.write_all(&(self.redirects.len() as u32).to_le_bytes())?;
|
||||||
|
for (specifier, offset) in &self.specifiers {
|
||||||
|
writer.write_all(&(specifier.len() as u32).to_le_bytes())?;
|
||||||
|
writer.write_all(specifier.as_bytes())?;
|
||||||
|
writer.write_all(&offset.to_le_bytes())?;
|
||||||
|
}
|
||||||
|
for (from, to) in &self.redirects {
|
||||||
|
writer.write_all(&(from.len() as u32).to_le_bytes())?;
|
||||||
|
writer.write_all(from.as_bytes())?;
|
||||||
|
writer.write_all(&(to.len() as u32).to_le_bytes())?;
|
||||||
|
writer.write_all(to.as_bytes())?;
|
||||||
|
}
|
||||||
|
for (media_type, data) in &self.data {
|
||||||
|
writer.write_all(&[serialize_media_type(*media_type)])?;
|
||||||
|
writer.write_all(&(data.len() as u64).to_le_bytes())?;
|
||||||
|
writer.write_all(data)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DenoCompileModuleData<'a> {
|
||||||
|
pub specifier: &'a Url,
|
||||||
|
pub media_type: MediaType,
|
||||||
|
pub data: Cow<'static, [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DenoCompileModuleData<'a> {
|
||||||
|
pub fn into_for_v8(self) -> (&'a Url, ModuleType, ModuleSourceCode) {
|
||||||
|
fn into_bytes(data: Cow<'static, [u8]>) -> ModuleSourceCode {
|
||||||
|
ModuleSourceCode::Bytes(match data {
|
||||||
|
Cow::Borrowed(d) => d.into(),
|
||||||
|
Cow::Owned(d) => d.into_boxed_slice().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_string_unsafe(data: Cow<'static, [u8]>) -> ModuleSourceCode {
|
||||||
|
// todo(https://github.com/denoland/deno_core/pull/943): store whether
|
||||||
|
// the string is ascii or not ahead of time so we can avoid the is_ascii()
|
||||||
|
// check in FastString::from_static
|
||||||
|
match data {
|
||||||
|
Cow::Borrowed(d) => ModuleSourceCode::String(
|
||||||
|
// SAFETY: we know this is a valid utf8 string
|
||||||
|
unsafe { FastString::from_static(std::str::from_utf8_unchecked(d)) },
|
||||||
|
),
|
||||||
|
Cow::Owned(d) => ModuleSourceCode::Bytes(d.into_boxed_slice().into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (media_type, source) = match self.media_type {
|
||||||
|
MediaType::JavaScript
|
||||||
|
| MediaType::Jsx
|
||||||
|
| MediaType::Mjs
|
||||||
|
| MediaType::Cjs
|
||||||
|
| MediaType::TypeScript
|
||||||
|
| MediaType::Mts
|
||||||
|
| MediaType::Cts
|
||||||
|
| MediaType::Dts
|
||||||
|
| MediaType::Dmts
|
||||||
|
| MediaType::Dcts
|
||||||
|
| MediaType::Tsx => {
|
||||||
|
(ModuleType::JavaScript, into_string_unsafe(self.data))
|
||||||
|
}
|
||||||
|
MediaType::Json => (ModuleType::Json, into_string_unsafe(self.data)),
|
||||||
|
MediaType::Wasm => (ModuleType::Wasm, into_bytes(self.data)),
|
||||||
|
// just assume javascript if we made it here
|
||||||
|
MediaType::TsBuildInfo | MediaType::SourceMap | MediaType::Unknown => {
|
||||||
|
(ModuleType::JavaScript, into_bytes(self.data))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(self.specifier, media_type, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RemoteModulesStoreSpecifierValue {
|
||||||
|
Data(usize),
|
||||||
|
Redirect(Url),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RemoteModulesStore {
|
||||||
|
specifiers: HashMap<Url, RemoteModulesStoreSpecifierValue>,
|
||||||
|
files_data: &'static [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteModulesStore {
|
||||||
|
fn build(data: &'static [u8]) -> Result<Self, AnyError> {
|
||||||
|
fn read_specifier(input: &[u8]) -> Result<(&[u8], (Url, u64)), AnyError> {
|
||||||
|
let (input, specifier) = read_string_lossy(input)?;
|
||||||
|
let specifier = Url::parse(&specifier)?;
|
||||||
|
let (input, offset) = read_u64(input)?;
|
||||||
|
Ok((input, (specifier, offset)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_redirect(input: &[u8]) -> Result<(&[u8], (Url, Url)), AnyError> {
|
||||||
|
let (input, from) = read_string_lossy(input)?;
|
||||||
|
let from = Url::parse(&from)?;
|
||||||
|
let (input, to) = read_string_lossy(input)?;
|
||||||
|
let to = Url::parse(&to)?;
|
||||||
|
Ok((input, (from, to)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_headers(
|
||||||
|
input: &[u8],
|
||||||
|
) -> Result<(&[u8], HashMap<Url, RemoteModulesStoreSpecifierValue>), AnyError>
|
||||||
|
{
|
||||||
|
let (input, specifiers_len) = read_u32_as_usize(input)?;
|
||||||
|
let (mut input, redirects_len) = read_u32_as_usize(input)?;
|
||||||
|
let mut specifiers =
|
||||||
|
HashMap::with_capacity(specifiers_len + redirects_len);
|
||||||
|
for _ in 0..specifiers_len {
|
||||||
|
let (current_input, (specifier, offset)) =
|
||||||
|
read_specifier(input).context("reading specifier")?;
|
||||||
|
input = current_input;
|
||||||
|
specifiers.insert(
|
||||||
|
specifier,
|
||||||
|
RemoteModulesStoreSpecifierValue::Data(offset as usize),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..redirects_len {
|
||||||
|
let (current_input, (from, to)) = read_redirect(input)?;
|
||||||
|
input = current_input;
|
||||||
|
specifiers.insert(from, RemoteModulesStoreSpecifierValue::Redirect(to));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((input, specifiers))
|
||||||
|
}
|
||||||
|
|
||||||
|
let (files_data, specifiers) = read_headers(data)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
specifiers,
|
||||||
|
files_data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_specifier<'a>(
|
||||||
|
&'a self,
|
||||||
|
specifier: &'a Url,
|
||||||
|
) -> Result<Option<&'a Url>, AnyError> {
|
||||||
|
let mut count = 0;
|
||||||
|
let mut current = specifier;
|
||||||
|
loop {
|
||||||
|
if count > 10 {
|
||||||
|
bail!("Too many redirects resolving '{}'", specifier);
|
||||||
|
}
|
||||||
|
match self.specifiers.get(current) {
|
||||||
|
Some(RemoteModulesStoreSpecifierValue::Redirect(to)) => {
|
||||||
|
current = to;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
Some(RemoteModulesStoreSpecifierValue::Data(_)) => {
|
||||||
|
return Ok(Some(current));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<'a>(
|
||||||
|
&'a self,
|
||||||
|
specifier: &'a Url,
|
||||||
|
) -> Result<Option<DenoCompileModuleData<'a>>, AnyError> {
|
||||||
|
let mut count = 0;
|
||||||
|
let mut current = specifier;
|
||||||
|
loop {
|
||||||
|
if count > 10 {
|
||||||
|
bail!("Too many redirects resolving '{}'", specifier);
|
||||||
|
}
|
||||||
|
match self.specifiers.get(current) {
|
||||||
|
Some(RemoteModulesStoreSpecifierValue::Redirect(to)) => {
|
||||||
|
current = to;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
Some(RemoteModulesStoreSpecifierValue::Data(offset)) => {
|
||||||
|
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 {
|
||||||
|
specifier,
|
||||||
|
media_type,
|
||||||
|
data: Cow::Borrowed(data),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_npm_snapshot(
|
||||||
|
mut snapshot: SerializedNpmResolutionSnapshot,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
fn append_string(bytes: &mut Vec<u8>, string: &str) {
|
||||||
|
let len = string.len() as u32;
|
||||||
|
bytes.extend_from_slice(&len.to_le_bytes());
|
||||||
|
bytes.extend_from_slice(string.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.packages.sort_by(|a, b| a.id.cmp(&b.id)); // determinism
|
||||||
|
let ids_to_stored_ids = snapshot
|
||||||
|
.packages
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, pkg)| (&pkg.id, i as u32))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
let mut root_packages: Vec<_> = snapshot.root_packages.iter().collect();
|
||||||
|
root_packages.sort();
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
|
||||||
|
bytes.extend_from_slice(&(snapshot.packages.len() as u32).to_le_bytes());
|
||||||
|
for pkg in &snapshot.packages {
|
||||||
|
append_string(&mut bytes, &pkg.id.as_serialized());
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.extend_from_slice(&(root_packages.len() as u32).to_le_bytes());
|
||||||
|
for (req, id) in root_packages {
|
||||||
|
append_string(&mut bytes, &req.to_string());
|
||||||
|
let id = ids_to_stored_ids.get(&id).unwrap();
|
||||||
|
bytes.extend_from_slice(&id.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
for pkg in &snapshot.packages {
|
||||||
|
let deps_len = pkg.dependencies.len() as u32;
|
||||||
|
bytes.extend_from_slice(&deps_len.to_le_bytes());
|
||||||
|
let mut deps: Vec<_> = pkg.dependencies.iter().collect();
|
||||||
|
deps.sort();
|
||||||
|
for (req, id) in deps {
|
||||||
|
append_string(&mut bytes, req);
|
||||||
|
let id = ids_to_stored_ids.get(&id).unwrap();
|
||||||
|
bytes.extend_from_slice(&id.to_le_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_npm_snapshot(
|
||||||
|
input: &[u8],
|
||||||
|
) -> Result<ValidSerializedNpmResolutionSnapshot, AnyError> {
|
||||||
|
fn parse_id(input: &[u8]) -> Result<(&[u8], NpmPackageId), AnyError> {
|
||||||
|
let (input, id) = read_string_lossy(input)?;
|
||||||
|
let id = NpmPackageId::from_serialized(&id)?;
|
||||||
|
Ok((input, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)] // clippy bug
|
||||||
|
fn parse_root_package<'a>(
|
||||||
|
id_to_npm_id: &'a impl Fn(usize) -> Result<NpmPackageId, AnyError>,
|
||||||
|
) -> impl Fn(&[u8]) -> Result<(&[u8], (PackageReq, NpmPackageId)), AnyError> + 'a
|
||||||
|
{
|
||||||
|
|input| {
|
||||||
|
let (input, req) = read_string_lossy(input)?;
|
||||||
|
let req = PackageReq::from_str(&req)?;
|
||||||
|
let (input, id) = read_u32_as_usize(input)?;
|
||||||
|
Ok((input, (req, id_to_npm_id(id)?)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)] // clippy bug
|
||||||
|
fn parse_package_dep<'a>(
|
||||||
|
id_to_npm_id: &'a impl Fn(usize) -> Result<NpmPackageId, AnyError>,
|
||||||
|
) -> impl Fn(&[u8]) -> Result<(&[u8], (String, NpmPackageId)), AnyError> + 'a
|
||||||
|
{
|
||||||
|
|input| {
|
||||||
|
let (input, req) = read_string_lossy(input)?;
|
||||||
|
let (input, id) = read_u32_as_usize(input)?;
|
||||||
|
Ok((input, (req.into_owned(), id_to_npm_id(id)?)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_package<'a>(
|
||||||
|
input: &'a [u8],
|
||||||
|
id: NpmPackageId,
|
||||||
|
id_to_npm_id: &impl Fn(usize) -> Result<NpmPackageId, AnyError>,
|
||||||
|
) -> Result<(&'a [u8], SerializedNpmResolutionSnapshotPackage), AnyError> {
|
||||||
|
let (input, deps_len) = read_u32_as_usize(input)?;
|
||||||
|
let (input, dependencies) =
|
||||||
|
parse_hashmap_n_times(input, deps_len, parse_package_dep(id_to_npm_id))?;
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
SerializedNpmResolutionSnapshotPackage {
|
||||||
|
id,
|
||||||
|
system: Default::default(),
|
||||||
|
dist: Default::default(),
|
||||||
|
dependencies,
|
||||||
|
optional_dependencies: Default::default(),
|
||||||
|
bin: None,
|
||||||
|
scripts: Default::default(),
|
||||||
|
deprecated: Default::default(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
let (input, packages_len) = read_u32_as_usize(input)?;
|
||||||
|
|
||||||
|
// get a hashmap of all the npm package ids to their serialized ids
|
||||||
|
let (input, data_ids_to_npm_ids) =
|
||||||
|
parse_vec_n_times(input, packages_len, parse_id)
|
||||||
|
.context("deserializing id")?;
|
||||||
|
let data_id_to_npm_id = |id: usize| {
|
||||||
|
data_ids_to_npm_ids
|
||||||
|
.get(id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| deno_core::anyhow::anyhow!("Invalid npm package id"))
|
||||||
|
};
|
||||||
|
|
||||||
|
let (input, root_packages_len) = read_u32_as_usize(input)?;
|
||||||
|
let (input, root_packages) = parse_hashmap_n_times(
|
||||||
|
input,
|
||||||
|
root_packages_len,
|
||||||
|
parse_root_package(&data_id_to_npm_id),
|
||||||
|
)
|
||||||
|
.context("deserializing root package")?;
|
||||||
|
let (input, packages) =
|
||||||
|
parse_vec_n_times_with_index(input, packages_len, |input, index| {
|
||||||
|
parse_package(input, data_id_to_npm_id(index)?, &data_id_to_npm_id)
|
||||||
|
})
|
||||||
|
.context("deserializing package")?;
|
||||||
|
|
||||||
|
if !input.is_empty() {
|
||||||
|
bail!("Unexpected data left over");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
SerializedNpmResolutionSnapshot {
|
||||||
|
packages,
|
||||||
|
root_packages,
|
||||||
|
}
|
||||||
|
// this is ok because we have already verified that all the
|
||||||
|
// identifiers found in the snapshot are valid via the
|
||||||
|
// npm package id -> npm package id mapping
|
||||||
|
.into_valid_unsafe(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_media_type(media_type: MediaType) -> u8 {
|
||||||
|
match media_type {
|
||||||
|
MediaType::JavaScript => 0,
|
||||||
|
MediaType::Jsx => 1,
|
||||||
|
MediaType::Mjs => 2,
|
||||||
|
MediaType::Cjs => 3,
|
||||||
|
MediaType::TypeScript => 4,
|
||||||
|
MediaType::Mts => 5,
|
||||||
|
MediaType::Cts => 6,
|
||||||
|
MediaType::Dts => 7,
|
||||||
|
MediaType::Dmts => 8,
|
||||||
|
MediaType::Dcts => 9,
|
||||||
|
MediaType::Tsx => 10,
|
||||||
|
MediaType::Json => 11,
|
||||||
|
MediaType::Wasm => 12,
|
||||||
|
MediaType::TsBuildInfo => 13,
|
||||||
|
MediaType::SourceMap => 14,
|
||||||
|
MediaType::Unknown => 15,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_media_type(value: u8) -> Result<MediaType, AnyError> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(MediaType::JavaScript),
|
||||||
|
1 => Ok(MediaType::Jsx),
|
||||||
|
2 => Ok(MediaType::Mjs),
|
||||||
|
3 => Ok(MediaType::Cjs),
|
||||||
|
4 => Ok(MediaType::TypeScript),
|
||||||
|
5 => Ok(MediaType::Mts),
|
||||||
|
6 => Ok(MediaType::Cts),
|
||||||
|
7 => Ok(MediaType::Dts),
|
||||||
|
8 => Ok(MediaType::Dmts),
|
||||||
|
9 => Ok(MediaType::Dcts),
|
||||||
|
10 => Ok(MediaType::Tsx),
|
||||||
|
11 => Ok(MediaType::Json),
|
||||||
|
12 => Ok(MediaType::Wasm),
|
||||||
|
13 => Ok(MediaType::TsBuildInfo),
|
||||||
|
14 => Ok(MediaType::SourceMap),
|
||||||
|
15 => Ok(MediaType::Unknown),
|
||||||
|
_ => bail!("Unknown media type value: {}", value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_hashmap_n_times<TKey: std::cmp::Eq + std::hash::Hash, TValue>(
|
||||||
|
mut input: &[u8],
|
||||||
|
times: usize,
|
||||||
|
parse: impl Fn(&[u8]) -> Result<(&[u8], (TKey, TValue)), AnyError>,
|
||||||
|
) -> Result<(&[u8], HashMap<TKey, TValue>), AnyError> {
|
||||||
|
let mut results = HashMap::with_capacity(times);
|
||||||
|
for _ in 0..times {
|
||||||
|
let result = parse(input);
|
||||||
|
let (new_input, (key, value)) = result?;
|
||||||
|
results.insert(key, value);
|
||||||
|
input = new_input;
|
||||||
|
}
|
||||||
|
Ok((input, results))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_vec_n_times<TResult>(
|
||||||
|
input: &[u8],
|
||||||
|
times: usize,
|
||||||
|
parse: impl Fn(&[u8]) -> Result<(&[u8], TResult), AnyError>,
|
||||||
|
) -> Result<(&[u8], Vec<TResult>), AnyError> {
|
||||||
|
parse_vec_n_times_with_index(input, times, |input, _index| parse(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_vec_n_times_with_index<TResult>(
|
||||||
|
mut input: &[u8],
|
||||||
|
times: usize,
|
||||||
|
parse: impl Fn(&[u8], usize) -> Result<(&[u8], TResult), AnyError>,
|
||||||
|
) -> Result<(&[u8], Vec<TResult>), AnyError> {
|
||||||
|
let mut results = Vec::with_capacity(times);
|
||||||
|
for i in 0..times {
|
||||||
|
let result = parse(input, i);
|
||||||
|
let (new_input, result) = result?;
|
||||||
|
results.push(result);
|
||||||
|
input = new_input;
|
||||||
|
}
|
||||||
|
Ok((input, results))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bytes(input: &[u8], len: usize) -> Result<(&[u8], &[u8]), AnyError> {
|
||||||
|
if input.len() < len {
|
||||||
|
bail!("Unexpected end of data.",);
|
||||||
|
}
|
||||||
|
let (len_bytes, input) = input.split_at(len);
|
||||||
|
Ok((input, len_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
Ok((input, String::from_utf8_lossy(data_bytes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u32_as_usize(input: &[u8]) -> Result<(&[u8], usize), AnyError> {
|
||||||
|
let (input, len_bytes) = read_bytes(input, 4)?;
|
||||||
|
let len = u32::from_le_bytes(len_bytes.try_into()?);
|
||||||
|
Ok((input, len as usize))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u64(input: &[u8]) -> Result<(&[u8], u64), AnyError> {
|
||||||
|
let (input, len_bytes) = read_bytes(input, 8)?;
|
||||||
|
let len = u64::from_le_bytes(len_bytes.try_into()?);
|
||||||
|
Ok((input, len))
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Seek;
|
use std::io::Seek;
|
||||||
use std::io::SeekFrom;
|
use std::io::SeekFrom;
|
||||||
|
use std::ops::Range;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@ -67,6 +68,26 @@ impl VfsBuilder {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_new_root_path(
|
||||||
|
&mut self,
|
||||||
|
root_path: PathBuf,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
let root_path = canonicalize_path(&root_path)?;
|
||||||
|
self.root_path = root_path;
|
||||||
|
self.root_dir = VirtualDirectory {
|
||||||
|
name: self
|
||||||
|
.root_path
|
||||||
|
.file_stem()
|
||||||
|
.map(|s| s.to_string_lossy().into_owned())
|
||||||
|
.unwrap_or("root".to_string()),
|
||||||
|
entries: vec![VfsEntry::Dir(VirtualDirectory {
|
||||||
|
name: std::mem::take(&mut self.root_dir.name),
|
||||||
|
entries: std::mem::take(&mut self.root_dir.entries),
|
||||||
|
})],
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_root_dir<R>(
|
pub fn with_root_dir<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
with_root: impl FnOnce(&mut VirtualDirectory) -> R,
|
with_root: impl FnOnce(&mut VirtualDirectory) -> R,
|
||||||
|
@ -119,7 +140,7 @@ impl VfsBuilder {
|
||||||
// inline the symlink and make the target file
|
// inline the symlink and make the target file
|
||||||
let file_bytes = std::fs::read(&target)
|
let file_bytes = std::fs::read(&target)
|
||||||
.with_context(|| format!("Reading {}", path.display()))?;
|
.with_context(|| format!("Reading {}", path.display()))?;
|
||||||
self.add_file(&path, file_bytes)?;
|
self.add_file_with_data_inner(&path, file_bytes)?;
|
||||||
} else {
|
} else {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"{} Symlink target is outside '{}'. Excluding symlink at '{}' with target '{}'.",
|
"{} Symlink target is outside '{}'. Excluding symlink at '{}' with target '{}'.",
|
||||||
|
@ -191,16 +212,32 @@ impl VfsBuilder {
|
||||||
self.add_file_at_path_not_symlink(&target_path)
|
self.add_file_at_path_not_symlink(&target_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_file_at_path_not_symlink(
|
fn add_file_at_path_not_symlink(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
let file_bytes = std::fs::read(path)
|
let file_bytes = std::fs::read(path)
|
||||||
.with_context(|| format!("Reading {}", path.display()))?;
|
.with_context(|| format!("Reading {}", path.display()))?;
|
||||||
self.add_file(path, file_bytes)
|
self.add_file_with_data_inner(path, file_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_file(&mut self, path: &Path, data: Vec<u8>) -> Result<(), AnyError> {
|
pub fn add_file_with_data(
|
||||||
|
&mut self,
|
||||||
|
path: &Path,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
let target_path = canonicalize_path(path)?;
|
||||||
|
if target_path != path {
|
||||||
|
self.add_symlink(path, &target_path)?;
|
||||||
|
}
|
||||||
|
self.add_file_with_data_inner(&target_path, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_file_with_data_inner(
|
||||||
|
&mut self,
|
||||||
|
path: &Path,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
log::debug!("Adding file '{}'", path.display());
|
log::debug!("Adding file '{}'", path.display());
|
||||||
let checksum = util::checksum::gen(&[&data]);
|
let checksum = util::checksum::gen(&[&data]);
|
||||||
let offset = if let Some(offset) = self.file_offsets.get(&checksum) {
|
let offset = if let Some(offset) = self.file_offsets.get(&checksum) {
|
||||||
|
@ -249,8 +286,15 @@ impl VfsBuilder {
|
||||||
path.display(),
|
path.display(),
|
||||||
target.display()
|
target.display()
|
||||||
);
|
);
|
||||||
let dest = self.path_relative_root(target)?;
|
let relative_target = self.path_relative_root(target)?;
|
||||||
if dest == self.path_relative_root(path)? {
|
let relative_path = match self.path_relative_root(path) {
|
||||||
|
Ok(path) => path,
|
||||||
|
Err(StripRootError { .. }) => {
|
||||||
|
// ignore if the original path is outside the root directory
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if relative_target == relative_path {
|
||||||
// it's the same, ignore
|
// it's the same, ignore
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -263,7 +307,7 @@ impl VfsBuilder {
|
||||||
insert_index,
|
insert_index,
|
||||||
VfsEntry::Symlink(VirtualSymlink {
|
VfsEntry::Symlink(VirtualSymlink {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
dest_parts: dest
|
dest_parts: relative_target
|
||||||
.components()
|
.components()
|
||||||
.map(|c| c.as_os_str().to_string_lossy().to_string())
|
.map(|c| c.as_os_str().to_string_lossy().to_string())
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
|
@ -751,14 +795,14 @@ impl deno_io::fs::File for FileBackedVfsFile {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FileBackedVfs {
|
pub struct FileBackedVfs {
|
||||||
file: Mutex<Vec<u8>>,
|
vfs_data: Cow<'static, [u8]>,
|
||||||
fs_root: VfsRoot,
|
fs_root: VfsRoot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileBackedVfs {
|
impl FileBackedVfs {
|
||||||
pub fn new(file: Vec<u8>, fs_root: VfsRoot) -> Self {
|
pub fn new(data: Cow<'static, [u8]>, fs_root: VfsRoot) -> Self {
|
||||||
Self {
|
Self {
|
||||||
file: Mutex::new(file),
|
vfs_data: data,
|
||||||
fs_root,
|
fs_root,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -827,10 +871,15 @@ impl FileBackedVfs {
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_file_all(&self, file: &VirtualFile) -> std::io::Result<Vec<u8>> {
|
pub fn read_file_all(
|
||||||
let mut buf = vec![0; file.len as usize];
|
&self,
|
||||||
self.read_file(file, 0, &mut buf)?;
|
file: &VirtualFile,
|
||||||
Ok(buf)
|
) -> std::io::Result<Cow<'static, [u8]>> {
|
||||||
|
let read_range = self.get_read_range(file, 0, file.len)?;
|
||||||
|
match &self.vfs_data {
|
||||||
|
Cow::Borrowed(data) => Ok(Cow::Borrowed(&data[read_range])),
|
||||||
|
Cow::Owned(data) => Ok(Cow::Owned(data[read_range].to_vec())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_file(
|
pub fn read_file(
|
||||||
|
@ -839,18 +888,27 @@ impl FileBackedVfs {
|
||||||
pos: u64,
|
pos: u64,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
) -> std::io::Result<usize> {
|
) -> std::io::Result<usize> {
|
||||||
let data = self.file.lock();
|
let read_range = self.get_read_range(file, pos, buf.len() as u64)?;
|
||||||
|
buf.copy_from_slice(&self.vfs_data[read_range]);
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_read_range(
|
||||||
|
&self,
|
||||||
|
file: &VirtualFile,
|
||||||
|
pos: u64,
|
||||||
|
len: u64,
|
||||||
|
) -> std::io::Result<Range<usize>> {
|
||||||
|
let data = &self.vfs_data;
|
||||||
let start = self.fs_root.start_file_offset + file.offset + pos;
|
let start = self.fs_root.start_file_offset + file.offset + pos;
|
||||||
let end = start + buf.len() as u64;
|
let end = start + len;
|
||||||
if end > data.len() as u64 {
|
if end > data.len() as u64 {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(
|
||||||
std::io::ErrorKind::UnexpectedEof,
|
std::io::ErrorKind::UnexpectedEof,
|
||||||
"unexpected EOF",
|
"unexpected EOF",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
Ok(start as usize..end as usize)
|
||||||
buf.copy_from_slice(&data[start as usize..end as usize]);
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dir_entry(&self, path: &Path) -> std::io::Result<&VirtualDirectory> {
|
pub fn dir_entry(&self, path: &Path) -> std::io::Result<&VirtualDirectory> {
|
||||||
|
@ -888,7 +946,7 @@ mod test {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn read_file(vfs: &FileBackedVfs, path: &Path) -> String {
|
fn read_file(vfs: &FileBackedVfs, path: &Path) -> String {
|
||||||
let file = vfs.file_entry(path).unwrap();
|
let file = vfs.file_entry(path).unwrap();
|
||||||
String::from_utf8(vfs.read_file_all(file).unwrap()).unwrap()
|
String::from_utf8(vfs.read_file_all(file).unwrap().into_owned()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -901,20 +959,23 @@ mod test {
|
||||||
let src_path = src_path.to_path_buf();
|
let src_path = src_path.to_path_buf();
|
||||||
let mut builder = VfsBuilder::new(src_path.clone()).unwrap();
|
let mut builder = VfsBuilder::new(src_path.clone()).unwrap();
|
||||||
builder
|
builder
|
||||||
.add_file(&src_path.join("a.txt"), "data".into())
|
.add_file_with_data_inner(&src_path.join("a.txt"), "data".into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_file(&src_path.join("b.txt"), "data".into())
|
.add_file_with_data_inner(&src_path.join("b.txt"), "data".into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(builder.files.len(), 1); // because duplicate data
|
assert_eq!(builder.files.len(), 1); // because duplicate data
|
||||||
builder
|
builder
|
||||||
.add_file(&src_path.join("c.txt"), "c".into())
|
.add_file_with_data_inner(&src_path.join("c.txt"), "c".into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_file(&src_path.join("sub_dir").join("d.txt"), "d".into())
|
.add_file_with_data_inner(
|
||||||
|
&src_path.join("sub_dir").join("d.txt"),
|
||||||
|
"d".into(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_file(&src_path.join("e.txt"), "e".into())
|
.add_file_with_data_inner(&src_path.join("e.txt"), "e".into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_symlink(
|
.add_symlink(
|
||||||
|
@ -1031,7 +1092,7 @@ mod test {
|
||||||
(
|
(
|
||||||
dest_path.to_path_buf(),
|
dest_path.to_path_buf(),
|
||||||
FileBackedVfs::new(
|
FileBackedVfs::new(
|
||||||
data,
|
Cow::Owned(data),
|
||||||
VfsRoot {
|
VfsRoot {
|
||||||
dir: root_dir,
|
dir: root_dir,
|
||||||
root_path: dest_path.to_path_buf(),
|
root_path: dest_path.to_path_buf(),
|
||||||
|
@ -1082,7 +1143,7 @@ mod test {
|
||||||
let temp_path = temp_dir.path().canonicalize();
|
let temp_path = temp_dir.path().canonicalize();
|
||||||
let mut builder = VfsBuilder::new(temp_path.to_path_buf()).unwrap();
|
let mut builder = VfsBuilder::new(temp_path.to_path_buf()).unwrap();
|
||||||
builder
|
builder
|
||||||
.add_file(
|
.add_file_with_data_inner(
|
||||||
temp_path.join("a.txt").as_path(),
|
temp_path.join("a.txt").as_path(),
|
||||||
"0123456789".to_string().into_bytes(),
|
"0123456789".to_string().into_bytes(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::args::CompileFlags;
|
||||||
use crate::args::Flags;
|
use crate::args::Flags;
|
||||||
use crate::factory::CliFactory;
|
use crate::factory::CliFactory;
|
||||||
use crate::http_util::HttpClientProvider;
|
use crate::http_util::HttpClientProvider;
|
||||||
|
use crate::standalone::binary::StandaloneRelativeFileBaseUrl;
|
||||||
use crate::standalone::is_standalone_binary;
|
use crate::standalone::is_standalone_binary;
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
use deno_core::anyhow::bail;
|
use deno_core::anyhow::bail;
|
||||||
|
@ -14,7 +15,6 @@ use deno_core::error::AnyError;
|
||||||
use deno_core::resolve_url_or_path;
|
use deno_core::resolve_url_or_path;
|
||||||
use deno_graph::GraphKind;
|
use deno_graph::GraphKind;
|
||||||
use deno_terminal::colors;
|
use deno_terminal::colors;
|
||||||
use eszip::EszipRelativeFileBaseUrl;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -29,7 +29,6 @@ pub async fn compile(
|
||||||
let factory = CliFactory::from_flags(flags);
|
let factory = CliFactory::from_flags(flags);
|
||||||
let cli_options = factory.cli_options()?;
|
let cli_options = factory.cli_options()?;
|
||||||
let module_graph_creator = factory.module_graph_creator().await?;
|
let module_graph_creator = factory.module_graph_creator().await?;
|
||||||
let parsed_source_cache = factory.parsed_source_cache();
|
|
||||||
let binary_writer = factory.create_compile_binary_writer().await?;
|
let binary_writer = factory.create_compile_binary_writer().await?;
|
||||||
let http_client = factory.http_client_provider();
|
let http_client = factory.http_client_provider();
|
||||||
let module_specifier = cli_options.resolve_main_module()?;
|
let module_specifier = cli_options.resolve_main_module()?;
|
||||||
|
@ -80,7 +79,7 @@ pub async fn compile(
|
||||||
let graph = if cli_options.type_check_mode().is_true() {
|
let graph = if cli_options.type_check_mode().is_true() {
|
||||||
// In this case, the previous graph creation did type checking, which will
|
// In this case, the previous graph creation did type checking, which will
|
||||||
// create a module graph with types information in it. We don't want to
|
// create a module graph with types information in it. We don't want to
|
||||||
// store that in the eszip so create a code only module graph from scratch.
|
// store that in the binary so create a code only module graph from scratch.
|
||||||
module_graph_creator
|
module_graph_creator
|
||||||
.create_graph(GraphKind::CodeOnly, module_roots)
|
.create_graph(GraphKind::CodeOnly, module_roots)
|
||||||
.await?
|
.await?
|
||||||
|
@ -91,11 +90,6 @@ pub async fn compile(
|
||||||
let ts_config_for_emit = cli_options
|
let ts_config_for_emit = cli_options
|
||||||
.resolve_ts_config_for_emit(deno_config::deno_json::TsConfigType::Emit)?;
|
.resolve_ts_config_for_emit(deno_config::deno_json::TsConfigType::Emit)?;
|
||||||
check_warn_tsconfig(&ts_config_for_emit);
|
check_warn_tsconfig(&ts_config_for_emit);
|
||||||
let (transpile_options, emit_options) =
|
|
||||||
crate::args::ts_config_to_transpile_and_emit_options(
|
|
||||||
ts_config_for_emit.ts_config,
|
|
||||||
)?;
|
|
||||||
let parser = parsed_source_cache.as_capturing_parser();
|
|
||||||
let root_dir_url = resolve_root_dir_from_specifiers(
|
let root_dir_url = resolve_root_dir_from_specifiers(
|
||||||
cli_options.workspace().root_dir(),
|
cli_options.workspace().root_dir(),
|
||||||
graph.specifiers().map(|(s, _)| s).chain(
|
graph.specifiers().map(|(s, _)| s).chain(
|
||||||
|
@ -106,17 +100,6 @@ pub async fn compile(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
log::debug!("Binary root dir: {}", root_dir_url);
|
log::debug!("Binary root dir: {}", root_dir_url);
|
||||||
let root_dir_url = EszipRelativeFileBaseUrl::new(&root_dir_url);
|
|
||||||
let eszip = eszip::EszipV2::from_graph(eszip::FromGraphOptions {
|
|
||||||
graph,
|
|
||||||
parser,
|
|
||||||
transpile_options,
|
|
||||||
emit_options,
|
|
||||||
// make all the modules relative to the root folder
|
|
||||||
relative_file_base: Some(root_dir_url),
|
|
||||||
npm_packages: None,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"{} {} to {}",
|
"{} {} to {}",
|
||||||
colors::green("Compile"),
|
colors::green("Compile"),
|
||||||
|
@ -143,15 +126,18 @@ pub async fn compile(
|
||||||
let write_result = binary_writer
|
let write_result = binary_writer
|
||||||
.write_bin(
|
.write_bin(
|
||||||
file,
|
file,
|
||||||
eszip,
|
&graph,
|
||||||
root_dir_url,
|
StandaloneRelativeFileBaseUrl::from(&root_dir_url),
|
||||||
module_specifier,
|
module_specifier,
|
||||||
&compile_flags,
|
&compile_flags,
|
||||||
cli_options,
|
cli_options,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!("Writing temporary file '{}'", temp_path.display())
|
format!(
|
||||||
|
"Writing deno compile executable to temporary file '{}'",
|
||||||
|
temp_path.display()
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// set it as executable
|
// set it as executable
|
||||||
|
|
|
@ -103,6 +103,21 @@ pub fn arc_str_to_bytes(arc_str: Arc<str>) -> Arc<[u8]> {
|
||||||
unsafe { Arc::from_raw(raw as *const [u8]) }
|
unsafe { Arc::from_raw(raw as *const [u8]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts an `Arc<u8>` to an `Arc<str>` if able.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn arc_u8_to_arc_str(
|
||||||
|
arc_u8: Arc<[u8]>,
|
||||||
|
) -> Result<Arc<str>, std::str::Utf8Error> {
|
||||||
|
// Check that the string is valid UTF-8.
|
||||||
|
std::str::from_utf8(&arc_u8)?;
|
||||||
|
// SAFETY: the string is valid UTF-8, and the layout Arc<[u8]> is the same as
|
||||||
|
// Arc<str>. This is proven by the From<Arc<str>> impl for Arc<[u8]> from the
|
||||||
|
// standard library.
|
||||||
|
Ok(unsafe {
|
||||||
|
std::mem::transmute::<std::sync::Arc<[u8]>, std::sync::Arc<str>>(arc_u8)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"unstable": ["byonm"]
|
"nodeModulesDir": "manual"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
error: Module not found: file:///[WILDLINE]/add.js
|
error: Uncaught SyntaxError: The requested module './add.js' does not provide an export named 'add'
|
||||||
|
at <anonymous> (file:///[WILDLINE])
|
||||||
|
|
Loading…
Reference in a new issue