diff options
Diffstat (limited to 'crates/shirabe/src/factory.rs')
| -rw-r--r-- | crates/shirabe/src/factory.rs | 434 |
1 files changed, 237 insertions, 197 deletions
diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index 80f1548..73197ce 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -15,8 +15,10 @@ use shirabe_php_shim::{ use crate::autoload::AutoloadGenerator; use crate::cache::Cache; -use crate::composer::{ComposerWeakHandle, PartialOrFullComposer}; -use crate::composer::{PartialComposerHandle, PartialComposerWeakHandle}; +use crate::composer::{ + AnyComposerHandle, AnyComposerWeakHandle, Composer, FullComposerHandle, FullComposerWeakHandle, + InnerFullComposer, InnerPartialComposer, PartialComposer, +}; use crate::config::Config; use crate::config::JsonConfigSource; use crate::downloader::DownloadManager; @@ -90,6 +92,16 @@ impl DisablePlugins { } } +/// Shared managers produced while building the common (partial) part of a Composer, reused by the +/// full-load branch to wire the full-only managers (download/autoload/archive/locker). +struct ComposerBuildArtifacts { + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, + process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, + r#loop: std::rc::Rc<std::cell::RefCell<Loop>>, + dispatcher: std::rc::Rc<std::cell::RefCell<EventDispatcher>>, + im: std::rc::Rc<std::cell::RefCell<InstallationManager>>, +} + /// Creates a configured instance of composer. pub struct Factory; @@ -436,7 +448,7 @@ impl Factory { cwd: Option<&str>, full_load: bool, disable_scripts: bool, - ) -> anyhow::Result<PartialComposerHandle> { + ) -> anyhow::Result<AnyComposerHandle> { // if a custom composer.json path is given, we change the default cwd to be that file's directory let mut local_config = local_config; let mut cwd = cwd.map(|s| s.to_string()); @@ -577,161 +589,50 @@ impl Factory { // PartialComposerWeak (Weak<RefCell<InnerComposer>>). The closure cannot return a // Result, so construction errors are surfaced through `build_error`. let mut build_error: Option<anyhow::Error> = None; - let composer = std::rc::Rc::new_cyclic( - |composer_weak: &std::rc::Weak<std::cell::RefCell<PartialOrFullComposer>>| { - let mut build = || -> anyhow::Result<PartialOrFullComposer> { - let mut composer: PartialOrFullComposer = if full_load { - PartialOrFullComposer::new_full() - } else { - PartialOrFullComposer::new_partial() - }; - composer.set_config(config.clone()); - if is_global { - composer.set_global(); - } - - if full_load { - // load auth configs into the IO instance - // TODO(phase-b): load_configuration requires &mut IOInterface; create_composer takes &dyn IOInterface - // io.load_configuration(&mut *config.borrow_mut())?; - - // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it - // we only load if the InstalledVersions class wasn't defined yet so that this is only loaded once - let installed_versions_path = format!( - "{}/composer/installed.php", - config.borrow_mut().get_str("vendor-dir")? - ); - if !disable_plugins.is_disabled_at_all() - && !disable_scripts - && !class_exists("Composer\\InstalledVersions") - && file_exists(&installed_versions_path) - { - // force loading the class at this point so it is loaded from the composer phar and not from the vendor dir - // as we cannot guarantee integrity of that file - if class_exists("Composer\\InstalledVersions") { - FilesystemRepository::safely_load_installed_versions( - &installed_versions_path, - ); - } - } - } - - let http_downloader = std::rc::Rc::new(std::cell::RefCell::new( - Self::create_http_downloader(io.clone(), &config, IndexMap::new())?, + let composer: AnyComposerHandle = if full_load { + let inner = std::rc::Rc::new_cyclic( + |composer_weak: &std::rc::Weak<std::cell::RefCell<InnerFullComposer>>| { + let weak = AnyComposerWeakHandle::Full(FullComposerWeakHandle::from_weak( + composer_weak.clone(), )); - let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new( - Some(io.clone()), - ))); - let r#loop = std::rc::Rc::new(std::cell::RefCell::new(Loop::new( - http_downloader.clone(), - Some(process.clone()), - ))); - composer.set_loop(r#loop.clone()); - - // initialize event dispatcher with the Composer back-reference - let dispatcher = { - let mut d = EventDispatcher::new( - PartialComposerWeakHandle::from_weak(composer_weak.clone()), + let mut full = InnerFullComposer::default(); + let mut build = || -> anyhow::Result<()> { + let artifacts = self.build_composer_base( + &mut full.partial, + weak, io.clone(), - Some(process.clone()), - ); - d.set_run_scripts(!disable_scripts); - std::rc::Rc::new(std::cell::RefCell::new(d)) - }; - composer.set_event_dispatcher(dispatcher.clone()); - - // initialize repository manager - let rm = std::rc::Rc::new(std::cell::RefCell::new(RepositoryFactory::manager( - io.clone(), - &config, - Some(http_downloader.clone()), - Some(dispatcher.clone()), - Some(process.clone()), - )?)); - - // force-set the version of the global package if not defined as - // guessing it adds no value and only takes time - if !full_load && !local_config_data.contains_key("version") { - local_config_data - .insert("version".to_string(), PhpMixed::String("1.0.0".to_string())); - } - - // load package - let parser = VersionParser::new(); - let guesser = VersionGuesser::new( - config.clone(), - process.clone(), - parser.clone(), - Some(io.clone()), - ); - let mut loader = self.load_root_package( - rm.clone(), - config.clone(), - parser, - guesser, - io.clone(), - ); - let package = loader.load( - local_config_data - .iter() - .map(|(k, v)| (k.clone(), Box::new(v.clone()))) - .collect(), - "Composer\\Package\\RootPackage", - Some(&cwd), - )?; - // TODO(phase-b): set_package expects RootPackageInterface; loader returns BasePackage - // composer.set_package(package); - let _ = package; - - // load local repository - self.add_local_repository( - io.clone(), - &mut rm.borrow_mut(), - &vendor_dir, - composer.get_package().clone(), - Some(&process), - ); - composer.set_repository_manager(rm.clone()); - - // initialize installation manager - let im = std::rc::Rc::new(std::cell::RefCell::new( - self.create_installation_manager( - r#loop.clone(), - io.clone(), - Some(dispatcher.clone()), - ), - )); - composer.set_installation_manager(im.clone()); + &config, + full_load, + is_global, + disable_plugins, + disable_scripts, + &mut local_config_data, + &cwd, + &vendor_dir, + )?; - if let PartialOrFullComposer::Full(ref mut composer_full) = composer { // initialize download manager let dm = self.create_download_manager( io.clone(), &config, - &http_downloader, - &process, - Some(&dispatcher), + &artifacts.http_downloader, + &artifacts.process, + Some(&artifacts.dispatcher), )?; - composer_full.set_download_manager(dm.clone()); + full.download_manager = Some(dm.clone()); // initialize autoload generator let generator = - AutoloadGenerator::new(dispatcher.clone(), Some(io.clone())); - composer_full.set_autoload_generator(std::rc::Rc::new( - std::cell::RefCell::new(generator), - )); + AutoloadGenerator::new(artifacts.dispatcher.clone(), Some(io.clone())); + full.autoload_generator = + Some(std::rc::Rc::new(std::cell::RefCell::new(generator))); // initialize archive manager - let am = self.create_archive_manager(&*config.borrow(), &dm, &r#loop)?; - composer_full - .set_archive_manager(std::rc::Rc::new(std::cell::RefCell::new(am))); - } - - // add installers to the manager (must happen after download manager is created since they read it out of $composer) - self.create_default_installers(&im, &composer, io.clone(), Some(&process)); + let am = + self.create_archive_manager(&*config.borrow(), &dm, &artifacts.r#loop)?; + full.archive_manager = Some(std::rc::Rc::new(std::cell::RefCell::new(am))); - // init locker if possible - if let PartialOrFullComposer::Full(ref mut composer_full) = composer { + // init locker if possible if let Some(ref composer_file_path) = composer_file { let lock_file = Self::get_lock_file(composer_file_path); let lock_enabled = config @@ -741,13 +642,13 @@ impl Factory { .unwrap_or(true); if !lock_enabled && file_exists(&lock_file) { io.write_error3( - &format!( - "<warning>{} is present but ignored as the \"lock\" config option is disabled.</warning>", - lock_file - ), - true, - crate::io::NORMAL, - ); + &format!( + "<warning>{} is present but ignored as the \"lock\" config option is disabled.</warning>", + lock_file + ), + true, + crate::io::NORMAL, + ); } let locker = Locker::new( @@ -761,12 +662,11 @@ impl Factory { None, Some(io.clone()), )?, - im.clone(), + artifacts.im.clone(), &file_get_contents(composer_file_path).unwrap_or_default(), - process.clone(), + artifacts.process.clone(), ); - composer_full - .set_locker(std::rc::Rc::new(std::cell::RefCell::new(locker))); + full.locker = Some(std::rc::Rc::new(std::cell::RefCell::new(locker))); } else { let lock_contents = JsonFile::encode( &PhpMixed::Array( @@ -780,26 +680,46 @@ impl Factory { let locker = Locker::new( io.clone(), JsonFile::new(Platform::get_dev_null(), None, Some(io.clone()))?, - im.clone(), + artifacts.im.clone(), &lock_contents, - process.clone(), + artifacts.process.clone(), ); - composer_full - .set_locker(std::rc::Rc::new(std::cell::RefCell::new(locker))); + full.locker = Some(std::rc::Rc::new(std::cell::RefCell::new(locker))); } + Ok(()) + }; + if let Err(e) = build() { + build_error = Some(e); } - - Ok(composer) - }; - match build() { - Ok(composer) => std::cell::RefCell::new(composer), - Err(e) => { + std::cell::RefCell::new(full) + }, + ); + AnyComposerHandle::Full(FullComposerHandle::from_rc(inner)) + } else { + let inner = std::rc::Rc::new_cyclic( + |composer_weak: &std::rc::Weak<std::cell::RefCell<InnerPartialComposer>>| { + let weak = AnyComposerWeakHandle::Partial(composer_weak.clone()); + let mut partial = InnerPartialComposer::default(); + if let Err(e) = self.build_composer_base( + &mut partial, + weak, + io.clone(), + &config, + full_load, + is_global, + disable_plugins, + disable_scripts, + &mut local_config_data, + &cwd, + &vendor_dir, + ) { build_error = Some(e); - std::cell::RefCell::new(PartialOrFullComposer::new_partial()) } - } - }, - ); + std::cell::RefCell::new(partial) + }, + ); + AnyComposerHandle::Partial(inner) + }; if let Some(e) = build_error { return Err(e); } @@ -809,11 +729,8 @@ impl Factory { // PluginManager::new upgrades the Composer back-reference to read its config and locker, so // it must be built after Rc::new_cyclic returns; inside the closure the Rc is not yet // constructed and the weak handle cannot upgrade. - let (is_full, is_global) = { - let c = composer.borrow(); - (c.is_full(), c.is_global()) - }; - if is_full { + let is_global = composer.is_global(); + if let Some(full) = composer.as_full() { let global_composer = if !is_global { self.create_global_composer( io.clone(), @@ -828,16 +745,12 @@ impl Factory { let pm = self.create_plugin_manager( io.clone(), - ComposerWeakHandle::from_weak(std::rc::Rc::downgrade(&composer)), + full.downgrade(), global_composer, disable_plugins, ); let pm = std::rc::Rc::new(std::cell::RefCell::new(pm)); - composer - .borrow_mut() - .as_full_mut() - .unwrap() - .set_plugin_manager(pm.clone()); + full.set_plugin_manager(pm.clone()); if is_global { pm.borrow_mut().set_running_in_global_dir(true); @@ -850,7 +763,7 @@ impl Factory { // the Composer through the dispatcher) is safe only after the Rc has been constructed. let init_event = Event::from_name(PluginEvents::INIT.to_string()); let init_event_name = init_event.get_name().to_string(); - let dispatcher = composer.borrow().get_event_dispatcher(); + let dispatcher = composer.get_event_dispatcher(); dispatcher .borrow_mut() .dispatch(Some(&init_event_name), Some(init_event))?; @@ -861,14 +774,14 @@ impl Factory { // self.purge_packages(rm.get_local_repository(), &mut im)?; } - Ok(PartialComposerHandle::from_rc(composer)) + Ok(composer) } pub fn create_global( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, disable_plugins: DisablePlugins, disable_scripts: bool, - ) -> Option<PartialComposerHandle> { + ) -> Option<AnyComposerHandle> { let factory = Self; let config = Self::create_config(Some(io.clone()), None).ok()?; @@ -909,7 +822,7 @@ impl Factory { disable_plugins: DisablePlugins, disable_scripts: bool, full_load: bool, - ) -> Option<PartialComposerHandle> { + ) -> Option<AnyComposerHandle> { // make sure if disable plugins was 'local' it is now turned off let disable_plugins = if matches!( disable_plugins, @@ -1176,8 +1089,8 @@ impl Factory { fn create_plugin_manager( &self, io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - composer: ComposerWeakHandle, - global_composer: Option<PartialComposerHandle>, + composer: FullComposerWeakHandle, + global_composer: Option<AnyComposerHandle>, disable_plugins: DisablePlugins, ) -> PluginManager { PluginManager::new(io, composer, global_composer, disable_plugins) @@ -1192,10 +1105,143 @@ impl Factory { InstallationManager::new(r#loop, io, event_dispatcher) } + /// Builds the common (partial) part of a Composer into `composer`, returning the shared managers + /// the full-load branch needs to wire its full-only managers. PHP's PartialComposer constructor + /// flow lives here so both the partial and full `Rc::new_cyclic` branches can share it. + fn build_composer_base( + &self, + composer: &mut InnerPartialComposer, + composer_weak: AnyComposerWeakHandle, + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: &std::rc::Rc<std::cell::RefCell<Config>>, + full_load: bool, + is_global: bool, + disable_plugins: DisablePlugins, + disable_scripts: bool, + local_config_data: &mut IndexMap<String, PhpMixed>, + cwd: &str, + vendor_dir: &str, + ) -> anyhow::Result<ComposerBuildArtifacts> { + composer.config = Some(config.clone()); + if is_global { + composer.global = true; + } + + if full_load { + // load auth configs into the IO instance + // TODO(phase-b): load_configuration requires &mut IOInterface; create_composer takes &dyn IOInterface + // io.load_configuration(&mut *config.borrow_mut())?; + + // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it + // we only load if the InstalledVersions class wasn't defined yet so that this is only loaded once + let installed_versions_path = format!( + "{}/composer/installed.php", + config.borrow_mut().get_str("vendor-dir")? + ); + if !disable_plugins.is_disabled_at_all() + && !disable_scripts + && !class_exists("Composer\\InstalledVersions") + && file_exists(&installed_versions_path) + { + // force loading the class at this point so it is loaded from the composer phar and not from the vendor dir + // as we cannot guarantee integrity of that file + if class_exists("Composer\\InstalledVersions") { + FilesystemRepository::safely_load_installed_versions(&installed_versions_path); + } + } + } + + let http_downloader = std::rc::Rc::new(std::cell::RefCell::new( + Self::create_http_downloader(io.clone(), config, IndexMap::new())?, + )); + let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(Some( + io.clone(), + )))); + let r#loop = std::rc::Rc::new(std::cell::RefCell::new(Loop::new( + http_downloader.clone(), + Some(process.clone()), + ))); + composer.r#loop = Some(r#loop.clone()); + + // initialize event dispatcher with the Composer back-reference + let dispatcher = { + let mut d = EventDispatcher::new(composer_weak, io.clone(), Some(process.clone())); + d.set_run_scripts(!disable_scripts); + std::rc::Rc::new(std::cell::RefCell::new(d)) + }; + composer.event_dispatcher = Some(dispatcher.clone()); + + // initialize repository manager + let rm = std::rc::Rc::new(std::cell::RefCell::new(RepositoryFactory::manager( + io.clone(), + config, + Some(http_downloader.clone()), + Some(dispatcher.clone()), + Some(process.clone()), + )?)); + + // force-set the version of the global package if not defined as + // guessing it adds no value and only takes time + if !full_load && !local_config_data.contains_key("version") { + local_config_data.insert("version".to_string(), PhpMixed::String("1.0.0".to_string())); + } + + // load package + let parser = VersionParser::new(); + let guesser = VersionGuesser::new( + config.clone(), + process.clone(), + parser.clone(), + Some(io.clone()), + ); + let mut loader = + self.load_root_package(rm.clone(), config.clone(), parser, guesser, io.clone()); + let package = loader.load( + local_config_data + .iter() + .map(|(k, v)| (k.clone(), Box::new(v.clone()))) + .collect(), + "Composer\\Package\\RootPackage", + Some(cwd), + )?; + // TODO(phase-b): set_package expects RootPackageInterface; loader returns BasePackage + // composer.package = Some(package); + let _ = package; + + // load local repository + self.add_local_repository( + io.clone(), + &mut rm.borrow_mut(), + vendor_dir, + composer.package.as_ref().unwrap().clone(), + Some(&process), + ); + composer.repository_manager = Some(rm.clone()); + + // initialize installation manager + let im = std::rc::Rc::new(std::cell::RefCell::new(self.create_installation_manager( + r#loop.clone(), + io.clone(), + Some(dispatcher.clone()), + ))); + composer.installation_manager = Some(im.clone()); + + // add installers to the manager (must happen after download manager is created since they read it out of $composer) + self.create_default_installers(&im, config, io.clone(), Some(&process)); + + Ok(ComposerBuildArtifacts { + http_downloader, + process, + r#loop, + dispatcher, + im, + }) + } + fn create_default_installers( &self, im: &std::rc::Rc<std::cell::RefCell<InstallationManager>>, - composer: &PartialOrFullComposer, + config: &std::rc::Rc<std::cell::RefCell<Config>>, io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, process: Option<&std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>, ) { @@ -1203,21 +1249,15 @@ impl Factory { process.map(std::rc::Rc::clone), ))); let bin_dir = trim( - &composer - .get_config() - .borrow_mut() - .get_str("bin-dir") - .unwrap_or_default(), + &config.borrow_mut().get_str("bin-dir").unwrap_or_default(), Some("/"), ); - let bin_compat = composer - .get_config() + let bin_compat = config .borrow_mut() .get_str("bin-compat") .unwrap_or_default(); let vendor_dir = trim( - &composer - .get_config() + &config .borrow_mut() .get_str("vendor-dir") .unwrap_or_default(), @@ -1266,7 +1306,7 @@ impl Factory { config: Option<LocalConfigInput>, disable_plugins: DisablePlugins, disable_scripts: bool, - ) -> anyhow::Result<PartialComposerHandle> { + ) -> anyhow::Result<AnyComposerHandle> { let factory = Self; // for BC reasons, if a config is passed in either as array or a path that is not the default composer.json path |
