mirror of
https://github.com/denoland/deno.git
synced 2024-12-26 17:19:06 -05:00
5a91a065b8
This PR implements the Node child_process IPC functionality in Deno on Unix systems. For `fd > 2` a duplex unix pipe is set up between the parent and child processes. Currently implements data passing via the channel in the JSON serialization format.
544 lines
17 KiB
Rust
544 lines
17 KiB
Rust
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use crate::args::get_root_cert_store;
|
|
use crate::args::npm_pkg_req_ref_to_binary_command;
|
|
use crate::args::CaData;
|
|
use crate::args::CacheSetting;
|
|
use crate::args::PackageJsonDepsProvider;
|
|
use crate::args::StorageKeyResolver;
|
|
use crate::cache::Caches;
|
|
use crate::cache::DenoDirProvider;
|
|
use crate::cache::NodeAnalysisCache;
|
|
use crate::file_fetcher::get_source_from_data_url;
|
|
use crate::http_util::HttpClient;
|
|
use crate::module_loader::CjsResolutionStore;
|
|
use crate::module_loader::CliNodeResolver;
|
|
use crate::module_loader::NpmModuleLoader;
|
|
use crate::node::CliCjsCodeAnalyzer;
|
|
use crate::npm::create_cli_npm_resolver;
|
|
use crate::npm::CliNpmResolverByonmCreateOptions;
|
|
use crate::npm::CliNpmResolverCreateOptions;
|
|
use crate::npm::CliNpmResolverManagedCreateOptions;
|
|
use crate::npm::CliNpmResolverManagedPackageJsonInstallerOption;
|
|
use crate::npm::CliNpmResolverManagedSnapshotOption;
|
|
use crate::npm::NpmCacheDir;
|
|
use crate::resolver::MappedSpecifierResolver;
|
|
use crate::util::progress_bar::ProgressBar;
|
|
use crate::util::progress_bar::ProgressBarStyle;
|
|
use crate::util::v8::construct_v8_flags;
|
|
use crate::worker::CliMainWorkerFactory;
|
|
use crate::worker::CliMainWorkerOptions;
|
|
use crate::worker::ModuleLoaderFactory;
|
|
use deno_ast::MediaType;
|
|
use deno_core::anyhow::Context;
|
|
use deno_core::error::generic_error;
|
|
use deno_core::error::type_error;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::futures::FutureExt;
|
|
use deno_core::v8_set_flags;
|
|
use deno_core::FeatureChecker;
|
|
use deno_core::ModuleLoader;
|
|
use deno_core::ModuleSpecifier;
|
|
use deno_core::ModuleType;
|
|
use deno_core::ResolutionKind;
|
|
use deno_runtime::deno_fs;
|
|
use deno_runtime::deno_node::analyze::NodeCodeTranslator;
|
|
use deno_runtime::deno_node::NodeResolver;
|
|
use deno_runtime::deno_tls::rustls::RootCertStore;
|
|
use deno_runtime::deno_tls::RootCertStoreProvider;
|
|
use deno_runtime::permissions::Permissions;
|
|
use deno_runtime::permissions::PermissionsContainer;
|
|
use deno_runtime::WorkerLogLevel;
|
|
use deno_semver::npm::NpmPackageReqReference;
|
|
use import_map::parse_from_json;
|
|
use std::pin::Pin;
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
|
|
mod binary;
|
|
mod file_system;
|
|
mod virtual_fs;
|
|
|
|
pub use binary::extract_standalone;
|
|
pub use binary::is_standalone_binary;
|
|
pub use binary::DenoCompileBinaryWriter;
|
|
|
|
use self::binary::load_npm_vfs;
|
|
use self::binary::Metadata;
|
|
use self::file_system::DenoCompileFileSystem;
|
|
|
|
struct SharedModuleLoaderState {
|
|
eszip: eszip::EszipV2,
|
|
mapped_specifier_resolver: MappedSpecifierResolver,
|
|
node_resolver: Arc<CliNodeResolver>,
|
|
npm_module_loader: Arc<NpmModuleLoader>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct EmbeddedModuleLoader {
|
|
shared: Arc<SharedModuleLoaderState>,
|
|
root_permissions: PermissionsContainer,
|
|
dynamic_permissions: PermissionsContainer,
|
|
}
|
|
|
|
impl ModuleLoader for EmbeddedModuleLoader {
|
|
fn resolve(
|
|
&self,
|
|
specifier: &str,
|
|
referrer: &str,
|
|
kind: ResolutionKind,
|
|
) -> Result<ModuleSpecifier, AnyError> {
|
|
let referrer = if referrer == "." {
|
|
if kind != ResolutionKind::MainModule {
|
|
return Err(generic_error(format!(
|
|
"Expected to resolve main module, got {:?} instead.",
|
|
kind
|
|
)));
|
|
}
|
|
let current_dir = std::env::current_dir().unwrap();
|
|
deno_core::resolve_path(".", ¤t_dir)?
|
|
} else {
|
|
ModuleSpecifier::parse(referrer).map_err(|err| {
|
|
type_error(format!("Referrer uses invalid specifier: {}", err))
|
|
})?
|
|
};
|
|
|
|
let permissions = if matches!(kind, ResolutionKind::DynamicImport) {
|
|
&self.dynamic_permissions
|
|
} else {
|
|
&self.root_permissions
|
|
};
|
|
if let Some(result) = self.shared.node_resolver.resolve_if_in_npm_package(
|
|
specifier,
|
|
&referrer,
|
|
permissions,
|
|
) {
|
|
return result;
|
|
}
|
|
|
|
let maybe_mapped = self
|
|
.shared
|
|
.mapped_specifier_resolver
|
|
.resolve(specifier, &referrer)?
|
|
.into_specifier();
|
|
|
|
// npm specifier
|
|
let specifier_text = maybe_mapped
|
|
.as_ref()
|
|
.map(|r| r.as_str())
|
|
.unwrap_or(specifier);
|
|
if let Ok(reference) = NpmPackageReqReference::from_str(specifier_text) {
|
|
return self.shared.node_resolver.resolve_req_reference(
|
|
&reference,
|
|
permissions,
|
|
&referrer,
|
|
);
|
|
}
|
|
|
|
match maybe_mapped {
|
|
Some(resolved) => Ok(resolved),
|
|
None => deno_core::resolve_import(specifier, referrer.as_str())
|
|
.map_err(|err| err.into()),
|
|
}
|
|
}
|
|
|
|
fn load(
|
|
&self,
|
|
original_specifier: &ModuleSpecifier,
|
|
maybe_referrer: Option<&ModuleSpecifier>,
|
|
is_dynamic: bool,
|
|
) -> Pin<Box<deno_core::ModuleSourceFuture>> {
|
|
let is_data_uri = get_source_from_data_url(original_specifier).ok();
|
|
if let Some((source, _)) = is_data_uri {
|
|
return Box::pin(deno_core::futures::future::ready(Ok(
|
|
deno_core::ModuleSource::new(
|
|
deno_core::ModuleType::JavaScript,
|
|
source.into(),
|
|
original_specifier,
|
|
),
|
|
)));
|
|
}
|
|
|
|
let permissions = if is_dynamic {
|
|
&self.dynamic_permissions
|
|
} else {
|
|
&self.root_permissions
|
|
};
|
|
if let Some(result) =
|
|
self.shared.npm_module_loader.load_sync_if_in_npm_package(
|
|
original_specifier,
|
|
maybe_referrer,
|
|
permissions,
|
|
)
|
|
{
|
|
return match result {
|
|
Ok(code_source) => Box::pin(deno_core::futures::future::ready(Ok(
|
|
deno_core::ModuleSource::new_with_redirect(
|
|
match code_source.media_type {
|
|
MediaType::Json => ModuleType::Json,
|
|
_ => ModuleType::JavaScript,
|
|
},
|
|
code_source.code,
|
|
original_specifier,
|
|
&code_source.found_url,
|
|
),
|
|
))),
|
|
Err(err) => Box::pin(deno_core::futures::future::ready(Err(err))),
|
|
};
|
|
}
|
|
|
|
let Some(module) =
|
|
self.shared.eszip.get_module(original_specifier.as_str())
|
|
else {
|
|
return Box::pin(deno_core::futures::future::ready(Err(type_error(
|
|
format!("Module not found: {}", original_specifier),
|
|
))));
|
|
};
|
|
let original_specifier = original_specifier.clone();
|
|
let found_specifier =
|
|
ModuleSpecifier::parse(&module.specifier).expect("invalid url in eszip");
|
|
|
|
async move {
|
|
let code = module.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.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!();
|
|
}
|
|
},
|
|
code.into(),
|
|
&original_specifier,
|
|
&found_specifier,
|
|
))
|
|
}
|
|
.boxed_local()
|
|
}
|
|
}
|
|
|
|
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(arc_u8) })
|
|
}
|
|
|
|
struct StandaloneModuleLoaderFactory {
|
|
shared: Arc<SharedModuleLoaderState>,
|
|
}
|
|
|
|
impl ModuleLoaderFactory for StandaloneModuleLoaderFactory {
|
|
fn create_for_main(
|
|
&self,
|
|
root_permissions: PermissionsContainer,
|
|
dynamic_permissions: PermissionsContainer,
|
|
) -> Rc<dyn ModuleLoader> {
|
|
Rc::new(EmbeddedModuleLoader {
|
|
shared: self.shared.clone(),
|
|
root_permissions,
|
|
dynamic_permissions,
|
|
})
|
|
}
|
|
|
|
fn create_for_worker(
|
|
&self,
|
|
root_permissions: PermissionsContainer,
|
|
dynamic_permissions: PermissionsContainer,
|
|
) -> Rc<dyn ModuleLoader> {
|
|
Rc::new(EmbeddedModuleLoader {
|
|
shared: self.shared.clone(),
|
|
root_permissions,
|
|
dynamic_permissions,
|
|
})
|
|
}
|
|
|
|
fn create_source_map_getter(
|
|
&self,
|
|
) -> Option<Box<dyn deno_core::SourceMapGetter>> {
|
|
None
|
|
}
|
|
}
|
|
|
|
struct StandaloneRootCertStoreProvider {
|
|
ca_stores: Option<Vec<String>>,
|
|
ca_data: Option<CaData>,
|
|
cell: once_cell::sync::OnceCell<RootCertStore>,
|
|
}
|
|
|
|
impl RootCertStoreProvider for StandaloneRootCertStoreProvider {
|
|
fn get_or_try_init(&self) -> Result<&RootCertStore, AnyError> {
|
|
self.cell.get_or_try_init(|| {
|
|
get_root_cert_store(None, self.ca_stores.clone(), self.ca_data.clone())
|
|
.map_err(|err| err.into())
|
|
})
|
|
}
|
|
}
|
|
|
|
pub async fn run(
|
|
mut eszip: eszip::EszipV2,
|
|
metadata: Metadata,
|
|
) -> Result<(), AnyError> {
|
|
let main_module = &metadata.entrypoint;
|
|
let current_exe_path = std::env::current_exe().unwrap();
|
|
let current_exe_name =
|
|
current_exe_path.file_name().unwrap().to_string_lossy();
|
|
let deno_dir_provider = Arc::new(DenoDirProvider::new(None));
|
|
let root_cert_store_provider = Arc::new(StandaloneRootCertStoreProvider {
|
|
ca_stores: metadata.ca_stores,
|
|
ca_data: metadata.ca_data.map(CaData::Bytes),
|
|
cell: Default::default(),
|
|
});
|
|
let progress_bar = ProgressBar::new(ProgressBarStyle::TextOnly);
|
|
let http_client = Arc::new(HttpClient::new(
|
|
Some(root_cert_store_provider.clone()),
|
|
metadata.unsafely_ignore_certificate_errors.clone(),
|
|
));
|
|
// use a dummy npm registry url
|
|
let npm_registry_url = ModuleSpecifier::parse("https://localhost/").unwrap();
|
|
let root_path = std::env::temp_dir()
|
|
.join(format!("deno-compile-{}", current_exe_name))
|
|
.join("node_modules");
|
|
let npm_cache_dir = NpmCacheDir::new(root_path.clone());
|
|
let npm_global_cache_dir = npm_cache_dir.get_cache_location();
|
|
let cache_setting = CacheSetting::Only;
|
|
let (package_json_deps_provider, fs, npm_resolver, maybe_vfs_root) =
|
|
match metadata.node_modules {
|
|
Some(binary::NodeModules::Managed {
|
|
node_modules_dir,
|
|
package_json_deps,
|
|
}) => {
|
|
// this will always have a snapshot
|
|
let snapshot = eszip.take_npm_snapshot().unwrap();
|
|
let vfs_root_dir_path = if node_modules_dir {
|
|
root_path
|
|
} else {
|
|
npm_cache_dir.registry_folder(&npm_registry_url)
|
|
};
|
|
let vfs = load_npm_vfs(vfs_root_dir_path.clone())
|
|
.context("Failed to load npm vfs.")?;
|
|
let maybe_node_modules_path = if node_modules_dir {
|
|
Some(vfs.root().to_path_buf())
|
|
} else {
|
|
None
|
|
};
|
|
let package_json_deps_provider =
|
|
Arc::new(PackageJsonDepsProvider::new(
|
|
package_json_deps.map(|serialized| serialized.into_deps()),
|
|
));
|
|
let fs = Arc::new(DenoCompileFileSystem::new(vfs))
|
|
as Arc<dyn deno_fs::FileSystem>;
|
|
let npm_resolver = create_cli_npm_resolver(
|
|
CliNpmResolverCreateOptions::Managed(CliNpmResolverManagedCreateOptions {
|
|
snapshot: CliNpmResolverManagedSnapshotOption::Specified(Some(snapshot)),
|
|
maybe_lockfile: None,
|
|
fs: fs.clone(),
|
|
http_client: http_client.clone(),
|
|
npm_global_cache_dir,
|
|
cache_setting,
|
|
text_only_progress_bar: progress_bar,
|
|
maybe_node_modules_path,
|
|
package_json_installer:
|
|
CliNpmResolverManagedPackageJsonInstallerOption::ConditionalInstall(
|
|
package_json_deps_provider.clone(),
|
|
),
|
|
npm_registry_url,
|
|
npm_system_info: Default::default(),
|
|
}),
|
|
)
|
|
.await?;
|
|
(
|
|
package_json_deps_provider,
|
|
fs,
|
|
npm_resolver,
|
|
Some(vfs_root_dir_path),
|
|
)
|
|
}
|
|
Some(binary::NodeModules::Byonm { package_json_deps }) => {
|
|
let vfs_root_dir_path = root_path;
|
|
let vfs = load_npm_vfs(vfs_root_dir_path.clone())
|
|
.context("Failed to load npm vfs.")?;
|
|
let node_modules_path = vfs.root().join("node_modules");
|
|
let package_json_deps_provider =
|
|
Arc::new(PackageJsonDepsProvider::new(
|
|
package_json_deps.map(|serialized| serialized.into_deps()),
|
|
));
|
|
let fs = Arc::new(DenoCompileFileSystem::new(vfs))
|
|
as Arc<dyn deno_fs::FileSystem>;
|
|
let npm_resolver =
|
|
create_cli_npm_resolver(CliNpmResolverCreateOptions::Byonm(
|
|
CliNpmResolverByonmCreateOptions {
|
|
fs: fs.clone(),
|
|
root_node_modules_dir: node_modules_path,
|
|
},
|
|
))
|
|
.await?;
|
|
(
|
|
package_json_deps_provider,
|
|
fs,
|
|
npm_resolver,
|
|
Some(vfs_root_dir_path),
|
|
)
|
|
}
|
|
None => {
|
|
let package_json_deps_provider =
|
|
Arc::new(PackageJsonDepsProvider::new(None));
|
|
let fs = Arc::new(deno_fs::RealFs) as Arc<dyn deno_fs::FileSystem>;
|
|
let npm_resolver = create_cli_npm_resolver(
|
|
CliNpmResolverCreateOptions::Managed(CliNpmResolverManagedCreateOptions {
|
|
snapshot: CliNpmResolverManagedSnapshotOption::Specified(None),
|
|
maybe_lockfile: None,
|
|
fs: fs.clone(),
|
|
http_client: http_client.clone(),
|
|
npm_global_cache_dir,
|
|
cache_setting,
|
|
text_only_progress_bar: progress_bar,
|
|
maybe_node_modules_path: None,
|
|
package_json_installer:
|
|
CliNpmResolverManagedPackageJsonInstallerOption::ConditionalInstall(
|
|
package_json_deps_provider.clone(),
|
|
),
|
|
npm_registry_url,
|
|
npm_system_info: Default::default(),
|
|
}),
|
|
)
|
|
.await?;
|
|
(package_json_deps_provider, fs, npm_resolver, None)
|
|
}
|
|
};
|
|
|
|
let has_node_modules_dir = npm_resolver.root_node_modules_path().is_some();
|
|
let node_resolver = Arc::new(NodeResolver::new(
|
|
fs.clone(),
|
|
npm_resolver.clone().into_npm_resolver(),
|
|
));
|
|
let cjs_resolutions = Arc::new(CjsResolutionStore::default());
|
|
let cache_db = Caches::new(deno_dir_provider.clone());
|
|
let node_analysis_cache = NodeAnalysisCache::new(cache_db.node_analysis_db());
|
|
let cjs_esm_code_analyzer =
|
|
CliCjsCodeAnalyzer::new(node_analysis_cache, fs.clone());
|
|
let node_code_translator = Arc::new(NodeCodeTranslator::new(
|
|
cjs_esm_code_analyzer,
|
|
fs.clone(),
|
|
node_resolver.clone(),
|
|
npm_resolver.clone().into_npm_resolver(),
|
|
));
|
|
let maybe_import_map = metadata.maybe_import_map.map(|(base, source)| {
|
|
Arc::new(parse_from_json(&base, &source).unwrap().import_map)
|
|
});
|
|
let cli_node_resolver = Arc::new(CliNodeResolver::new(
|
|
cjs_resolutions.clone(),
|
|
node_resolver.clone(),
|
|
npm_resolver.clone(),
|
|
));
|
|
let module_loader_factory = StandaloneModuleLoaderFactory {
|
|
shared: Arc::new(SharedModuleLoaderState {
|
|
eszip,
|
|
mapped_specifier_resolver: MappedSpecifierResolver::new(
|
|
maybe_import_map.clone(),
|
|
package_json_deps_provider.clone(),
|
|
),
|
|
node_resolver: cli_node_resolver.clone(),
|
|
npm_module_loader: Arc::new(NpmModuleLoader::new(
|
|
cjs_resolutions,
|
|
node_code_translator,
|
|
fs.clone(),
|
|
cli_node_resolver,
|
|
)),
|
|
}),
|
|
};
|
|
|
|
let permissions = {
|
|
let mut permissions = metadata.permissions;
|
|
// if running with an npm vfs, grant read access to it
|
|
if let Some(vfs_root) = maybe_vfs_root {
|
|
match &mut permissions.allow_read {
|
|
Some(vec) if vec.is_empty() => {
|
|
// do nothing, already granted
|
|
}
|
|
Some(vec) => {
|
|
vec.push(vfs_root);
|
|
}
|
|
None => {
|
|
permissions.allow_read = Some(vec![vfs_root]);
|
|
}
|
|
}
|
|
}
|
|
|
|
PermissionsContainer::new(Permissions::from_options(&permissions)?)
|
|
};
|
|
let feature_checker = Arc::new({
|
|
let mut checker = FeatureChecker::default();
|
|
checker.set_exit_cb(Box::new(crate::unstable_exit_cb));
|
|
// TODO(bartlomieju): enable, once we deprecate `--unstable` in favor
|
|
// of granular --unstable-* flags.
|
|
// feature_checker.set_warn_cb(Box::new(crate::unstable_warn_cb));
|
|
if metadata.unstable {
|
|
checker.enable_legacy_unstable();
|
|
}
|
|
checker
|
|
});
|
|
let worker_factory = CliMainWorkerFactory::new(
|
|
StorageKeyResolver::empty(),
|
|
crate::args::DenoSubcommand::Run(Default::default()),
|
|
npm_resolver,
|
|
node_resolver,
|
|
Default::default(),
|
|
Box::new(module_loader_factory),
|
|
root_cert_store_provider,
|
|
fs,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
feature_checker,
|
|
CliMainWorkerOptions {
|
|
argv: metadata.argv,
|
|
log_level: WorkerLogLevel::Info,
|
|
coverage_dir: None,
|
|
enable_op_summary_metrics: false,
|
|
enable_testing_features: false,
|
|
has_node_modules_dir,
|
|
hmr: false,
|
|
inspect_brk: false,
|
|
inspect_wait: false,
|
|
strace_ops: None,
|
|
is_inspecting: false,
|
|
is_npm_main: main_module.scheme() == "npm",
|
|
skip_op_registration: true,
|
|
location: metadata.location,
|
|
maybe_binary_npm_command_name: NpmPackageReqReference::from_specifier(
|
|
main_module,
|
|
)
|
|
.ok()
|
|
.map(|req_ref| npm_pkg_req_ref_to_binary_command(&req_ref)),
|
|
origin_data_folder_path: None,
|
|
seed: metadata.seed,
|
|
unsafely_ignore_certificate_errors: metadata
|
|
.unsafely_ignore_certificate_errors,
|
|
unstable: metadata.unstable,
|
|
maybe_root_package_json_deps: package_json_deps_provider.deps().cloned(),
|
|
},
|
|
None,
|
|
);
|
|
|
|
v8_set_flags(construct_v8_flags(&[], &metadata.v8_flags, vec![]));
|
|
|
|
let mut worker = worker_factory
|
|
.create_main_worker(main_module.clone(), permissions)
|
|
.await?;
|
|
|
|
let exit_code = worker.run().await?;
|
|
std::process::exit(exit_code)
|
|
}
|