diff options
| author | David Sherret <dsherret@users.noreply.github.com> | 2023-05-01 14:35:23 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-01 14:35:23 -0400 |
| commit | 9efed4c7a3d32de62e9c9b5e0c6712ce97637abb (patch) | |
| tree | aa370f95df93c71f6c57d6a01a50b4df1955ee57 /cli/factory.rs | |
| parent | 30628288ce2b411ca3def46129a4606073e16bac (diff) | |
refactor(cli): remove ProcState - add CliFactory (#18900)
This removes `ProcState` and replaces it with a new `CliFactory` which
initializes our "service structs" on demand. This isn't a performance
improvement at the moment for `deno run`, but might unlock performance
improvements in the future.
Diffstat (limited to 'cli/factory.rs')
| -rw-r--r-- | cli/factory.rs | 669 |
1 files changed, 669 insertions, 0 deletions
diff --git a/cli/factory.rs b/cli/factory.rs new file mode 100644 index 000000000..69560cf54 --- /dev/null +++ b/cli/factory.rs @@ -0,0 +1,669 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use crate::args::CliOptions; +use crate::args::DenoSubcommand; +use crate::args::Flags; +use crate::args::Lockfile; +use crate::args::StorageKeyResolver; +use crate::args::TsConfigType; +use crate::cache::Caches; +use crate::cache::DenoDir; +use crate::cache::EmitCache; +use crate::cache::HttpCache; +use crate::cache::NodeAnalysisCache; +use crate::cache::ParsedSourceCache; +use crate::emit::Emitter; +use crate::file_fetcher::FileFetcher; +use crate::graph_util::ModuleGraphBuilder; +use crate::graph_util::ModuleGraphContainer; +use crate::http_util::HttpClient; +use crate::module_loader::CjsResolutionStore; +use crate::module_loader::CliModuleLoaderFactory; +use crate::module_loader::ModuleLoadPreparer; +use crate::module_loader::NpmModuleLoader; +use crate::node::CliCjsEsmCodeAnalyzer; +use crate::node::CliNodeCodeTranslator; +use crate::npm::create_npm_fs_resolver; +use crate::npm::CliNpmRegistryApi; +use crate::npm::CliNpmResolver; +use crate::npm::NpmCache; +use crate::npm::NpmResolution; +use crate::npm::PackageJsonDepsInstaller; +use crate::resolver::CliGraphResolver; +use crate::tools::check::TypeChecker; +use crate::util::progress_bar::ProgressBar; +use crate::util::progress_bar::ProgressBarStyle; +use crate::watcher::FileWatcher; +use crate::watcher::FileWatcherReporter; +use crate::worker::CliMainWorkerFactory; +use crate::worker::CliMainWorkerOptions; +use crate::worker::HasNodeSpecifierChecker; + +use deno_core::error::AnyError; +use deno_core::parking_lot::Mutex; + +use deno_runtime::deno_node; +use deno_runtime::deno_node::analyze::NodeCodeTranslator; +use deno_runtime::deno_node::NodeResolver; +use deno_runtime::deno_tls::rustls::RootCertStore; +use deno_runtime::deno_web::BlobStore; +use deno_runtime::inspector_server::InspectorServer; +use deno_semver::npm::NpmPackageReqReference; +use import_map::ImportMap; +use log::warn; +use std::cell::RefCell; +use std::future::Future; +use std::path::PathBuf; +use std::sync::Arc; + +pub struct CliFactoryBuilder { + maybe_sender: Option<tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>>, +} + +impl CliFactoryBuilder { + pub fn new() -> Self { + Self { maybe_sender: None } + } + + pub fn with_watcher( + mut self, + sender: tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>, + ) -> Self { + self.maybe_sender = Some(sender); + self + } + + pub async fn build_from_flags( + self, + flags: Flags, + ) -> Result<CliFactory, AnyError> { + Ok(self.build_from_cli_options(Arc::new(CliOptions::from_flags(flags)?))) + } + + pub fn build_from_cli_options(self, options: Arc<CliOptions>) -> CliFactory { + CliFactory { + maybe_sender: RefCell::new(self.maybe_sender), + options, + services: Default::default(), + } + } +} + +struct Deferred<T>(once_cell::unsync::OnceCell<T>); + +impl<T> Default for Deferred<T> { + fn default() -> Self { + Self(once_cell::unsync::OnceCell::default()) + } +} + +impl<T> Deferred<T> { + pub fn get_or_try_init( + &self, + create: impl FnOnce() -> Result<T, AnyError>, + ) -> Result<&T, AnyError> { + self.0.get_or_try_init(create) + } + + pub fn get_or_init(&self, create: impl FnOnce() -> T) -> &T { + self.0.get_or_init(create) + } + + pub async fn get_or_try_init_async( + &self, + create: impl Future<Output = Result<T, AnyError>>, + ) -> Result<&T, AnyError> { + if self.0.get().is_none() { + // todo(dsherret): it would be more ideal if this enforced a + // single executor and then we could make some initialization + // concurrent + let val = create.await?; + _ = self.0.set(val); + } + Ok(self.0.get().unwrap()) + } +} + +#[derive(Default)] +struct CliFactoryServices { + dir: Deferred<DenoDir>, + caches: Deferred<Arc<Caches>>, + file_fetcher: Deferred<Arc<FileFetcher>>, + http_client: Deferred<HttpClient>, + emit_cache: Deferred<EmitCache>, + emitter: Deferred<Arc<Emitter>>, + graph_container: Deferred<Arc<ModuleGraphContainer>>, + lockfile: Deferred<Option<Arc<Mutex<Lockfile>>>>, + maybe_import_map: Deferred<Option<Arc<ImportMap>>>, + maybe_inspector_server: Deferred<Option<Arc<InspectorServer>>>, + root_cert_store: Deferred<RootCertStore>, + blob_store: Deferred<BlobStore>, + parsed_source_cache: Deferred<Arc<ParsedSourceCache>>, + resolver: Deferred<Arc<CliGraphResolver>>, + file_watcher: Deferred<Arc<FileWatcher>>, + maybe_file_watcher_reporter: Deferred<Option<FileWatcherReporter>>, + module_graph_builder: Deferred<Arc<ModuleGraphBuilder>>, + module_load_preparer: Deferred<Arc<ModuleLoadPreparer>>, + node_code_translator: Deferred<Arc<CliNodeCodeTranslator>>, + node_fs: Deferred<Arc<dyn deno_node::NodeFs>>, + node_resolver: Deferred<Arc<NodeResolver>>, + npm_api: Deferred<Arc<CliNpmRegistryApi>>, + npm_cache: Deferred<Arc<NpmCache>>, + npm_resolver: Deferred<Arc<CliNpmResolver>>, + npm_resolution: Deferred<Arc<NpmResolution>>, + package_json_deps_installer: Deferred<Arc<PackageJsonDepsInstaller>>, + text_only_progress_bar: Deferred<ProgressBar>, + type_checker: Deferred<Arc<TypeChecker>>, + cjs_resolutions: Deferred<Arc<CjsResolutionStore>>, +} + +pub struct CliFactory { + maybe_sender: + RefCell<Option<tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>>>, + options: Arc<CliOptions>, + services: CliFactoryServices, +} + +impl CliFactory { + pub async fn from_flags(flags: Flags) -> Result<Self, AnyError> { + CliFactoryBuilder::new().build_from_flags(flags).await + } + + pub fn from_cli_options(options: Arc<CliOptions>) -> Self { + CliFactoryBuilder::new().build_from_cli_options(options) + } + + pub fn cli_options(&self) -> &Arc<CliOptions> { + &self.options + } + + pub fn deno_dir(&self) -> Result<&DenoDir, AnyError> { + self + .services + .dir + .get_or_try_init(|| self.options.resolve_deno_dir()) + } + + pub fn caches(&self) -> Result<&Arc<Caches>, AnyError> { + self.services.caches.get_or_try_init(|| { + let caches = Arc::new(Caches::new(self.deno_dir()?.clone())); + // Warm up the caches we know we'll likely need based on the CLI mode + match self.options.sub_command() { + DenoSubcommand::Run(_) => { + _ = caches.dep_analysis_db(); + _ = caches.node_analysis_db(); + } + DenoSubcommand::Check(_) => { + _ = caches.dep_analysis_db(); + _ = caches.node_analysis_db(); + _ = caches.type_checking_cache_db(); + } + _ => {} + } + Ok(caches) + }) + } + + pub fn blob_store(&self) -> &BlobStore { + self.services.blob_store.get_or_init(BlobStore::default) + } + + pub fn root_cert_store(&self) -> Result<&RootCertStore, AnyError> { + self + .services + .root_cert_store + .get_or_try_init(|| self.options.resolve_root_cert_store()) + } + + pub fn text_only_progress_bar(&self) -> &ProgressBar { + self + .services + .text_only_progress_bar + .get_or_init(|| ProgressBar::new(ProgressBarStyle::TextOnly)) + } + + pub fn http_client(&self) -> Result<&HttpClient, AnyError> { + self.services.http_client.get_or_try_init(|| { + HttpClient::new( + Some(self.root_cert_store()?.clone()), + self.options.unsafely_ignore_certificate_errors().clone(), + ) + }) + } + + pub fn file_fetcher(&self) -> Result<&Arc<FileFetcher>, AnyError> { + self.services.file_fetcher.get_or_try_init(|| { + Ok(Arc::new(FileFetcher::new( + HttpCache::new(&self.deno_dir()?.deps_folder_path()), + self.options.cache_setting(), + !self.options.no_remote(), + self.http_client()?.clone(), + self.blob_store().clone(), + Some(self.text_only_progress_bar().clone()), + ))) + }) + } + + pub fn maybe_lockfile(&self) -> &Option<Arc<Mutex<Lockfile>>> { + self + .services + .lockfile + .get_or_init(|| self.options.maybe_lockfile()) + } + + pub fn npm_cache(&self) -> Result<&Arc<NpmCache>, AnyError> { + self.services.npm_cache.get_or_try_init(|| { + Ok(Arc::new(NpmCache::new( + self.deno_dir()?.npm_folder_path(), + self.options.cache_setting(), + self.http_client()?.clone(), + self.text_only_progress_bar().clone(), + ))) + }) + } + + pub fn npm_api(&self) -> Result<&Arc<CliNpmRegistryApi>, AnyError> { + self.services.npm_api.get_or_try_init(|| { + Ok(Arc::new(CliNpmRegistryApi::new( + CliNpmRegistryApi::default_url().to_owned(), + self.npm_cache()?.clone(), + self.http_client()?.clone(), + self.text_only_progress_bar().clone(), + ))) + }) + } + + pub async fn npm_resolution(&self) -> Result<&Arc<NpmResolution>, AnyError> { + self + .services + .npm_resolution + .get_or_try_init_async(async { + let npm_api = self.npm_api()?; + Ok(Arc::new(NpmResolution::from_serialized( + npm_api.clone(), + self + .options + .resolve_npm_resolution_snapshot(npm_api) + .await?, + self.maybe_lockfile().as_ref().cloned(), + ))) + }) + .await + } + + pub fn node_fs(&self) -> &Arc<dyn deno_node::NodeFs> { + self + .services + .node_fs + .get_or_init(|| Arc::new(deno_node::RealFs)) + } + + pub async fn npm_resolver(&self) -> Result<&Arc<CliNpmResolver>, AnyError> { + self + .services + .npm_resolver + .get_or_try_init_async(async { + let npm_resolution = self.npm_resolution().await?; + let npm_fs_resolver = create_npm_fs_resolver( + self.node_fs().clone(), + self.npm_cache()?.clone(), + self.text_only_progress_bar(), + CliNpmRegistryApi::default_url().to_owned(), + npm_resolution.clone(), + self.options.node_modules_dir_path(), + ); + Ok(Arc::new(CliNpmResolver::new( + npm_resolution.clone(), + npm_fs_resolver, + self.maybe_lockfile().as_ref().cloned(), + ))) + }) + .await + } + + pub async fn package_json_deps_installer( + &self, + ) -> Result<&Arc<PackageJsonDepsInstaller>, AnyError> { + self + .services + .package_json_deps_installer + .get_or_try_init_async(async { + let npm_api = self.npm_api()?; + let npm_resolution = self.npm_resolution().await?; + Ok(Arc::new(PackageJsonDepsInstaller::new( + npm_api.clone(), + npm_resolution.clone(), + self.options.maybe_package_json_deps(), + ))) + }) + .await + } + + pub async fn maybe_import_map( + &self, + ) -> Result<&Option<Arc<ImportMap>>, AnyError> { + self + .services + .maybe_import_map + .get_or_try_init_async(async { + Ok( + self + .options + .resolve_import_map(self.file_fetcher()?) + .await? + .map(Arc::new), + ) + }) + .await + } + + pub async fn resolver(&self) -> Result<&Arc<CliGraphResolver>, AnyError> { + self + .services + .resolver + .get_or_try_init_async(async { + Ok(Arc::new(CliGraphResolver::new( + self.options.to_maybe_jsx_import_source_config(), + self.maybe_import_map().await?.clone(), + self.options.no_npm(), + self.npm_api()?.clone(), + self.npm_resolution().await?.clone(), + self.package_json_deps_installer().await?.clone(), + ))) + }) + .await + } + + pub fn file_watcher(&self) -> Result<&Arc<FileWatcher>, AnyError> { + self.services.file_watcher.get_or_try_init(|| { + let watcher = FileWatcher::new( + self.options.clone(), + self.cjs_resolutions().clone(), + self.graph_container().clone(), + self.maybe_file_watcher_reporter().clone(), + self.parsed_source_cache()?.clone(), + ); + watcher.init_watcher(); + Ok(Arc::new(watcher)) + }) + } + + pub fn maybe_file_watcher_reporter(&self) -> &Option<FileWatcherReporter> { + let maybe_sender = self.maybe_sender.borrow_mut().take(); + self + .services + .maybe_file_watcher_reporter + .get_or_init(|| maybe_sender.map(FileWatcherReporter::new)) + } + + pub fn emit_cache(&self) -> Result<&EmitCache, AnyError> { + self.services.emit_cache.get_or_try_init(|| { + Ok(EmitCache::new(self.deno_dir()?.gen_cache.clone())) + }) + } + + pub fn parsed_source_cache( + &self, + ) -> Result<&Arc<ParsedSourceCache>, AnyError> { + self.services.parsed_source_cache.get_or_try_init(|| { + Ok(Arc::new(ParsedSourceCache::new( + self.caches()?.dep_analysis_db(), + ))) + }) + } + + pub fn emitter(&self) -> Result<&Arc<Emitter>, AnyError> { + self.services.emitter.get_or_try_init(|| { + let ts_config_result = self + .options + .resolve_ts_config_for_emit(TsConfigType::Emit)?; + if let Some(ignored_options) = ts_config_result.maybe_ignored_options { + warn!("{}", ignored_options); + } + let emit_options: deno_ast::EmitOptions = + ts_config_result.ts_config.into(); + Ok(Arc::new(Emitter::new( + self.emit_cache()?.clone(), + self.parsed_source_cache()?.clone(), + emit_options, + ))) + }) + } + + pub async fn node_resolver(&self) -> Result<&Arc<NodeResolver>, AnyError> { + self + .services + .node_resolver + .get_or_try_init_async(async { + Ok(Arc::new(NodeResolver::new( + self.node_fs().clone(), + self.npm_resolver().await?.clone(), + ))) + }) + .await + } + + pub async fn node_code_translator( + &self, + ) -> Result<&Arc<CliNodeCodeTranslator>, AnyError> { + self + .services + .node_code_translator + .get_or_try_init_async(async { + let caches = self.caches()?; + let node_analysis_cache = + NodeAnalysisCache::new(caches.node_analysis_db()); + let cjs_esm_analyzer = CliCjsEsmCodeAnalyzer::new(node_analysis_cache); + + Ok(Arc::new(NodeCodeTranslator::new( + cjs_esm_analyzer, + self.node_fs().clone(), + self.node_resolver().await?.clone(), + self.npm_resolver().await?.clone(), + ))) + }) + .await + } + + pub async fn type_checker(&self) -> Result<&Arc<TypeChecker>, AnyError> { + self + .services + .type_checker + .get_or_try_init_async(async { + Ok(Arc::new(TypeChecker::new( + self.caches()?.clone(), + self.options.clone(), + self.node_resolver().await?.clone(), + self.npm_resolver().await?.clone(), + ))) + }) + .await + } + + pub async fn module_graph_builder( + &self, + ) -> Result<&Arc<ModuleGraphBuilder>, AnyError> { + self + .services + .module_graph_builder + .get_or_try_init_async(async { + Ok(Arc::new(ModuleGraphBuilder::new( + self.options.clone(), + self.resolver().await?.clone(), + self.npm_resolver().await?.clone(), + self.parsed_source_cache()?.clone(), + self.maybe_lockfile().clone(), + self.emit_cache()?.clone(), + self.file_fetcher()?.clone(), + self.type_checker().await?.clone(), + ))) + }) + .await + } + + pub fn graph_container(&self) -> &Arc<ModuleGraphContainer> { + self.services.graph_container.get_or_init(Default::default) + } + + pub fn maybe_inspector_server(&self) -> &Option<Arc<InspectorServer>> { + self + .services + .maybe_inspector_server + .get_or_init(|| self.options.resolve_inspector_server().map(Arc::new)) + } + + pub async fn module_load_preparer( + &self, + ) -> Result<&Arc<ModuleLoadPreparer>, AnyError> { + self + .services + .module_load_preparer + .get_or_try_init_async(async { + Ok(Arc::new(ModuleLoadPreparer::new( + self.options.clone(), + self.graph_container().clone(), + self.maybe_lockfile().clone(), + self.maybe_file_watcher_reporter().clone(), + self.module_graph_builder().await?.clone(), + self.parsed_source_cache()?.clone(), + self.text_only_progress_bar().clone(), + self.resolver().await?.clone(), + self.type_checker().await?.clone(), + ))) + }) + .await + } + + pub fn cjs_resolutions(&self) -> &Arc<CjsResolutionStore> { + self.services.cjs_resolutions.get_or_init(Default::default) + } + + /// Gets a function that can be used to create a CliMainWorkerFactory + /// for a file watcher. + pub async fn create_cli_main_worker_factory_func( + &self, + ) -> Result<Arc<dyn Fn() -> CliMainWorkerFactory>, AnyError> { + let emitter = self.emitter()?.clone(); + let graph_container = self.graph_container().clone(); + let module_load_preparer = self.module_load_preparer().await?.clone(); + let parsed_source_cache = self.parsed_source_cache()?.clone(); + let resolver = self.resolver().await?.clone(); + let blob_store = self.blob_store().clone(); + let cjs_resolutions = self.cjs_resolutions().clone(); + let node_code_translator = self.node_code_translator().await?.clone(); + let options = self.cli_options().clone(); + let main_worker_options = self.create_cli_main_worker_options()?; + let node_fs = self.node_fs().clone(); + let root_cert_store = self.root_cert_store()?.clone(); + let node_resolver = self.node_resolver().await?.clone(); + let npm_resolver = self.npm_resolver().await?.clone(); + let maybe_inspector_server = self.maybe_inspector_server().clone(); + Ok(Arc::new(move || { + CliMainWorkerFactory::new( + StorageKeyResolver::from_options(&options), + npm_resolver.clone(), + node_resolver.clone(), + Box::new(CliHasNodeSpecifierChecker(graph_container.clone())), + blob_store.clone(), + Box::new(CliModuleLoaderFactory::new( + &options, + emitter.clone(), + graph_container.clone(), + module_load_preparer.clone(), + parsed_source_cache.clone(), + resolver.clone(), + NpmModuleLoader::new( + cjs_resolutions.clone(), + node_code_translator.clone(), + node_resolver.clone(), + ), + )), + root_cert_store.clone(), + node_fs.clone(), + maybe_inspector_server.clone(), + main_worker_options.clone(), + ) + })) + } + + pub async fn create_cli_main_worker_factory( + &self, + ) -> Result<CliMainWorkerFactory, AnyError> { + let node_resolver = self.node_resolver().await?; + Ok(CliMainWorkerFactory::new( + StorageKeyResolver::from_options(&self.options), + self.npm_resolver().await?.clone(), + node_resolver.clone(), + Box::new(CliHasNodeSpecifierChecker(self.graph_container().clone())), + self.blob_store().clone(), + Box::new(CliModuleLoaderFactory::new( + &self.options, + self.emitter()?.clone(), + self.graph_container().clone(), + self.module_load_preparer().await?.clone(), + self.parsed_source_cache()?.clone(), + self.resolver().await?.clone(), + NpmModuleLoader::new( + self.cjs_resolutions().clone(), + self.node_code_translator().await?.clone(), + node_resolver.clone(), + ), + )), + self.root_cert_store()?.clone(), + self.node_fs().clone(), + self.maybe_inspector_server().clone(), + self.create_cli_main_worker_options()?, + )) + } + + fn create_cli_main_worker_options( + &self, + ) -> Result<CliMainWorkerOptions, AnyError> { + Ok(CliMainWorkerOptions { + argv: self.options.argv().clone(), + debug: self + .options + .log_level() + .map(|l| l == log::Level::Debug) + .unwrap_or(false), + coverage_dir: self.options.coverage_dir(), + enable_testing_features: self.options.enable_testing_features(), + has_node_modules_dir: self.options.has_node_modules_dir(), + inspect_brk: self.options.inspect_brk().is_some(), + inspect_wait: self.options.inspect_wait().is_some(), + is_inspecting: self.options.is_inspecting(), + is_npm_main: self.options.is_npm_main(), + location: self.options.location_flag().clone(), + maybe_binary_npm_command_name: { + let mut maybe_binary_command_name = None; + if let DenoSubcommand::Run(flags) = self.options.sub_command() { + if let Ok(pkg_ref) = NpmPackageReqReference::from_str(&flags.script) { + // if the user ran a binary command, we'll need to set process.argv[0] + // to be the name of the binary command instead of deno + let binary_name = pkg_ref + .sub_path + .as_deref() + .unwrap_or(pkg_ref.req.name.as_str()); + maybe_binary_command_name = Some(binary_name.to_string()); + } + } + maybe_binary_command_name + }, + origin_data_folder_path: Some(self.deno_dir()?.origin_data_folder_path()), + seed: self.options.seed(), + unsafely_ignore_certificate_errors: self + .options + .unsafely_ignore_certificate_errors() + .clone(), + unstable: self.options.unstable(), + }) + } +} + +struct CliHasNodeSpecifierChecker(Arc<ModuleGraphContainer>); + +impl HasNodeSpecifierChecker for CliHasNodeSpecifierChecker { + fn has_node_specifier(&self) -> bool { + self.0.graph().has_node_specifier + } +} |
