diff options
Diffstat (limited to 'crates/shirabe')
54 files changed, 1502 insertions, 993 deletions
diff --git a/crates/shirabe/src/command/about_command.rs b/crates/shirabe/src/command/about_command.rs index bd7643d..ddb9f12 100644 --- a/crates/shirabe/src/command/about_command.rs +++ b/crates/shirabe/src/command/about_command.rs @@ -3,7 +3,8 @@ use crate::command::BaseCommand; use crate::command::BaseCommandData; use crate::command::HasBaseCommandData; -use crate::composer::Composer; +use crate::composer; +use crate::composer::ComposerHandle; use crate::io::IOInterface; use shirabe_external_packages::symfony::component::console::input::InputInterface; use shirabe_external_packages::symfony::component::console::output::OutputInterface; @@ -21,7 +22,7 @@ impl AboutCommand { } pub fn execute(&mut self, input: &dyn InputInterface, output: &dyn OutputInterface) -> i64 { - let composer_version = Composer::get_version(); + let composer_version = composer::get_version(); let _ = (input, output); self.get_io().write(&format!( diff --git a/crates/shirabe/src/command/archive_command.rs b/crates/shirabe/src/command/archive_command.rs index 9c43fc6..c823860 100644 --- a/crates/shirabe/src/command/archive_command.rs +++ b/crates/shirabe/src/command/archive_command.rs @@ -10,7 +10,7 @@ use shirabe_external_packages::symfony::component::console::output::OutputInterf use shirabe_php_shim::{LogicException, get_debug_type}; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::config::Config; use crate::console::input::InputArgument; use crate::console::input::InputOption; @@ -68,13 +68,12 @@ impl ArchiveCommand { output: &dyn OutputInterface, ) -> Result<i64> { let composer = self.try_composer(None, None); - let mut config: Option<std::rc::Rc<std::cell::RefCell<Config>>> = None; - if let Some(ref composer) = composer { - config = Some(std::rc::Rc::clone(composer.get_config())); + let config = if let Some(ref composer) = composer { + let config = composer.borrow_partial().get_config(); // TODO(plugin): dispatch CommandEvent let command_event = CommandEvent::new(PluginEvents::COMMAND, "archive", input, output); - let event_dispatcher = composer.get_event_dispatcher(); + let event_dispatcher = composer.borrow_partial().get_event_dispatcher(); event_dispatcher .borrow_mut() .dispatch(Some(command_event.get_name()), None); @@ -84,11 +83,9 @@ impl ArchiveCommand { vec![], indexmap::IndexMap::new(), ); - } - - let config = match config { - Some(c) => c, - None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_config(None, None)?)), + config + } else { + std::rc::Rc::new(std::cell::RefCell::new(Factory::create_config(None, None)?)) }; let format = input @@ -143,18 +140,19 @@ impl ArchiveCommand { composer.as_ref(), )?; - if return_code == 0 { - if let Some(ref composer) = composer { - composer - .get_event_dispatcher() - .borrow_mut() - .dispatch_script( - ScriptEvents::POST_ARCHIVE_CMD, - true, - vec![], - indexmap::IndexMap::new(), - ); - } + if return_code == 0 + && let Some(ref composer) = composer + { + composer + .borrow_partial() + .get_event_dispatcher() + .borrow_mut() + .dispatch_script( + ScriptEvents::POST_ARCHIVE_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); } Ok(return_code) @@ -170,11 +168,16 @@ impl ArchiveCommand { dest: &str, file_name: Option<String>, ignore_filters: bool, - composer: Option<&Composer>, + composer: Option<&PartialComposerHandle>, ) -> Result<i64> { + let composer_guard = composer.map(crate::command::composer_full); let owned_archive_manager; - let archive_manager: &ArchiveManager = if let Some(composer) = composer { - composer.get_archive_manager() + let composer_archive_manager; + let composer_archive_manager_ref; + let archive_manager: &ArchiveManager = if let Some(composer) = &composer_guard { + composer_archive_manager = composer.get_archive_manager().clone(); + composer_archive_manager_ref = composer_archive_manager.borrow(); + &composer_archive_manager_ref } else { let factory = Factory; let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(()))); @@ -198,7 +201,10 @@ impl ArchiveCommand { None => return Ok(1), } } else { - self.require_composer(None, None)?.get_package().clone_box() + let _rc = self.require_composer(None, None)?; + crate::command::composer_full(&_rc) + .get_package() + .clone_box() }; io.write_error(&format!( @@ -244,12 +250,14 @@ impl ArchiveCommand { let repo; if let Some(composer) = self.try_composer(None, None) { - let local_repo = composer.get_repository_manager().get_local_repository(); + let composer = crate::command::composer_full(&composer); + let repository_manager = composer.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let local_repo = repository_manager.get_local_repository(); let mut repos: Vec<Box<dyn crate::repository::RepositoryInterface>> = vec![local_repo.clone_box()]; repos.extend( - composer - .get_repository_manager() + repository_manager .get_repositories() .iter() .map(|r| r.clone_box()), diff --git a/crates/shirabe/src/command/audit_command.rs b/crates/shirabe/src/command/audit_command.rs index c2e6c93..e814821 100644 --- a/crates/shirabe/src/command/audit_command.rs +++ b/crates/shirabe/src/command/audit_command.rs @@ -3,7 +3,7 @@ use crate::advisory::AuditConfig; use crate::advisory::Auditor; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::console::input::InputOption; use crate::io::IOInterface; use crate::package::PackageInterface; @@ -51,14 +51,15 @@ impl AuditCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let mut composer = self.require_composer(None, None)?; - let packages = self.get_packages(&mut composer, input)?; + let composer = self.require_composer(None, None)?; + let packages = self.get_packages(&composer, input)?; if packages.is_empty() { self.get_io().write_error("No packages - skipping audit."); return Ok(0); } + let composer = crate::command::composer_full(&composer); let auditor = Auditor; let mut repo_set = RepositorySet::new( "stable", @@ -68,7 +69,11 @@ impl AuditCommand { indexmap::IndexMap::new(), indexmap::IndexMap::new(), ); - for repo in composer.get_repository_manager().get_repositories() { + for repo in composer + .get_repository_manager() + .borrow() + .get_repositories() + { // TODO(phase-b): repositories are shared (PHP class semantics); needs Rc wrapper repo_set.add_repository(repo.clone_box())?; } @@ -139,11 +144,13 @@ impl AuditCommand { fn get_packages( &self, - composer: &mut Composer, + composer: &PartialComposerHandle, input: &dyn InputInterface, ) -> Result<Vec<Box<dyn PackageInterface>>> { + let mut composer = crate::command::composer_full_mut(composer); if input.get_option("locked").as_bool().unwrap_or(false) { - let locker = composer.get_locker_mut(); + let locker = composer.get_locker().clone(); + let mut locker = locker.borrow_mut(); if !locker.is_locked() { return Err(UnexpectedValueException { message: "Valid composer.json and composer.lock files are required to run this command with --locked".to_string(), diff --git a/crates/shirabe/src/command/base_command.rs b/crates/shirabe/src/command/base_command.rs index 2aebccb..9511c4d 100644 --- a/crates/shirabe/src/command/base_command.rs +++ b/crates/shirabe/src/command/base_command.rs @@ -17,7 +17,7 @@ use shirabe_php_shim::{ use crate::advisory::AuditConfig; use crate::advisory::Auditor; use crate::command::SelfUpdateCommand; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::config::Config; use crate::console::Application; use crate::console::input::InputArgument; @@ -160,23 +160,23 @@ pub trait BaseCommand { required: bool, disable_plugins: Option<bool>, disable_scripts: Option<bool>, - ) -> Result<Option<Composer>>; + ) -> Result<Option<PartialComposerHandle>>; /// Retrieves the default Composer\Composer instance or throws fn require_composer( &mut self, disable_plugins: Option<bool>, disable_scripts: Option<bool>, - ) -> Result<Composer>; + ) -> Result<PartialComposerHandle>; /// Retrieves the default Composer\Composer instance or null fn try_composer( &mut self, disable_plugins: Option<bool>, disable_scripts: Option<bool>, - ) -> Option<Composer>; + ) -> Option<PartialComposerHandle>; - fn set_composer(&mut self, composer: Composer); + fn set_composer(&mut self, composer: PartialComposerHandle); /// Removes the cached composer instance fn reset_composer(&mut self) -> Result<()>; @@ -205,7 +205,7 @@ pub trait BaseCommand { config: Option<IndexMap<String, PhpMixed>>, disable_plugins: bool, disable_scripts: Option<bool>, - ) -> Result<Composer>; + ) -> Result<PartialComposerHandle>; /// Returns preferSource and preferDist values based on the configuration. fn get_preferred_install_options( @@ -253,7 +253,7 @@ pub trait BaseCommand { #[derive(Debug)] pub struct BaseCommandData { - pub(crate) composer: Option<Composer>, + pub(crate) composer: Option<PartialComposerHandle>, pub(crate) io: Option<Box<dyn IOInterface>>, } @@ -261,11 +261,11 @@ pub trait HasBaseCommandData { fn base_command_data(&self) -> &BaseCommandData; fn base_command_data_mut(&mut self) -> &mut BaseCommandData; - fn composer(&self) -> Option<&Composer> { - self.base_command_data().composer.as_ref() + fn composer(&self) -> Option<PartialComposerHandle> { + self.base_command_data().composer.clone() } - fn composer_mut(&mut self) -> &mut Option<Composer> { + fn composer_mut(&mut self) -> &mut Option<PartialComposerHandle> { &mut self.base_command_data_mut().composer } @@ -289,7 +289,7 @@ impl<C: HasBaseCommandData> BaseCommand for C { required: bool, disable_plugins: Option<bool>, disable_scripts: Option<bool>, - ) -> Result<Option<Composer>> { + ) -> Result<Option<PartialComposerHandle>> { if required { return Ok(Some( self.require_composer(disable_plugins, disable_scripts)?, @@ -303,27 +303,25 @@ impl<C: HasBaseCommandData> BaseCommand for C { &mut self, _disable_plugins: Option<bool>, _disable_scripts: Option<bool>, - ) -> Result<Composer> { - // TODO(phase-b): Composer is a PHP class (shared by reference). Returning owned - // `Composer` and the Application::get_composer -> &Composer mismatch require a - // shared-ownership wrapper (Rc<RefCell<Composer>>) before this can be wired up. + ) -> Result<PartialComposerHandle> { + // TODO(phase-b): depends on Application::get_composer, which is still stubbed. let _ = RuntimeException { message: String::new(), code: 0, }; - todo!("require_composer pending Composer shared-ownership refactor") + todo!("require_composer pending Application::get_composer") } fn try_composer( &mut self, _disable_plugins: Option<bool>, _disable_scripts: Option<bool>, - ) -> Option<Composer> { - // TODO(phase-b): same shared-ownership refactor as require_composer. - todo!("try_composer pending Composer shared-ownership refactor") + ) -> Option<PartialComposerHandle> { + // TODO(phase-b): depends on Application::get_composer, which is still stubbed. + todo!("try_composer pending Application::get_composer") } - fn set_composer(&mut self, composer: Composer) { + fn set_composer(&mut self, composer: PartialComposerHandle) { *self.composer_mut() = Some(composer); } @@ -389,7 +387,7 @@ impl<C: HasBaseCommandData> BaseCommand for C { ); // TODO(phase-b): event_dispatcher.dispatch expects Option<Event>; need wrapper from // PreCommandRunEvent. - let _ = composer.get_event_dispatcher(); + let _ = composer.borrow_partial().get_event_dispatcher(); let _ = pre_command_run_event.get_name(); } @@ -482,7 +480,7 @@ impl<C: HasBaseCommandData> BaseCommand for C { config: Option<IndexMap<String, PhpMixed>>, disable_plugins: bool, disable_scripts: Option<bool>, - ) -> Result<Composer> { + ) -> Result<PartialComposerHandle> { let disable_plugins = disable_plugins || input.has_parameter_option(&["--no-plugins"], false); let disable_scripts = disable_scripts.unwrap_or(false) diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs index 7ef3b15..498c589 100644 --- a/crates/shirabe/src/command/base_dependency_command.rs +++ b/crates/shirabe/src/command/base_dependency_command.rs @@ -47,7 +47,8 @@ pub trait BaseDependencyCommand: BaseCommand { output: &dyn OutputInterface, inverted: bool, ) -> anyhow::Result<i64> { - let mut composer = self.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer); // TODO(plugin): dispatch CommandEvent(PluginEvents::COMMAND, self.get_name(), input, output) via composer.get_event_dispatcher() let mut repos: Vec<Box<dyn RepositoryInterface>> = vec![]; @@ -56,7 +57,8 @@ pub trait BaseDependencyCommand: BaseCommand { ))); if input.get_option("locked").as_bool().unwrap_or(false) { - let locker = composer.get_locker_mut(); + let locker = composer.get_locker().clone(); + let mut locker = locker.borrow_mut(); if !locker.is_locked() { return Err(anyhow::anyhow!(UnexpectedValueException { @@ -78,7 +80,9 @@ pub trait BaseDependencyCommand: BaseCommand { platform_overrides, )?)); } else { - let local_repo = composer.get_repository_manager().get_local_repository(); + let repository_manager = composer.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let local_repo = repository_manager.get_local_repository(); let root_pkg = composer.get_package(); if local_repo.get_packages().len() == 0 @@ -142,7 +146,7 @@ pub trait BaseDependencyCommand: BaseCommand { let default_repos = CompositeRepository::new( RepositoryFactory::default_repos( Some(self.get_io()), - Some(std::rc::Rc::clone(composer.get_config())), + Some(composer.get_config()), // TODO(phase-b): get_repository_manager returns &; default_repos needs &mut Some(todo!("share repository_manager as &mut")), )? diff --git a/crates/shirabe/src/command/bump_command.rs b/crates/shirabe/src/command/bump_command.rs index 4141235..0b9727f 100644 --- a/crates/shirabe/src/command/bump_command.rs +++ b/crates/shirabe/src/command/bump_command.rs @@ -9,7 +9,6 @@ use shirabe_external_packages::symfony::component::console::output::OutputInterf use shirabe_php_shim::{PhpMixed, file_get_contents, file_put_contents, is_writable, strtolower}; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::factory::Factory; @@ -134,7 +133,8 @@ impl BumpCommand { return Ok(Self::ERROR_GENERIC); } - let mut composer = self.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer); let has_lock_file_disabled = !composer.get_config().borrow().has("lock") || composer .get_config() @@ -143,9 +143,14 @@ impl BumpCommand { .as_bool() .unwrap_or(true); let repo: Box<dyn crate::repository::RepositoryInterface> = if !has_lock_file_disabled { - Box::new(composer.get_locker_mut().get_locked_repository(true)?) - } else if composer.get_locker_mut().is_locked() { - if !composer.get_locker_mut().is_fresh()? { + Box::new( + composer + .get_locker() + .borrow_mut() + .get_locked_repository(true)?, + ) + } else if composer.get_locker().borrow_mut().is_locked() { + if !composer.get_locker().borrow_mut().is_fresh()? { io.write_error3( "<error>The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.</error>", true, @@ -153,12 +158,18 @@ impl BumpCommand { ); return Ok(Self::ERROR_LOCK_OUTDATED); } - Box::new(composer.get_locker_mut().get_locked_repository(true)?) + Box::new( + composer + .get_locker() + .borrow_mut() + .get_locked_repository(true)?, + ) } else { // TODO(phase-b): get_local_repository returns &dyn InstalledRepositoryInterface; // cloning into an owned Box requires clone_box on that trait. composer .get_repository_manager() + .borrow() .get_local_repository() .clone_box() }; @@ -253,7 +264,7 @@ impl BumpCommand { } updates - .entry(key) + .entry(*key) .or_default() .insert(pkg_name.clone(), bumped); } @@ -306,7 +317,7 @@ impl BumpCommand { } if !dry_run - && composer.get_locker_mut().is_locked() + && composer.get_locker().borrow_mut().is_locked() && composer .get_config() .borrow_mut() @@ -316,7 +327,8 @@ impl BumpCommand { && change_count > 0 { composer - .get_locker_mut() + .get_locker() + .borrow_mut() .update_hash(&composer_json, None::<fn(_) -> _>)?; } diff --git a/crates/shirabe/src/command/check_platform_reqs_command.rs b/crates/shirabe/src/command/check_platform_reqs_command.rs index 40bd898..83770be 100644 --- a/crates/shirabe/src/command/check_platform_reqs_command.rs +++ b/crates/shirabe/src/command/check_platform_reqs_command.rs @@ -9,7 +9,6 @@ use shirabe_semver::constraint::Constraint; use shirabe_semver::constraint::ConstraintInterface; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputOption; use crate::io::IOInterface; use crate::json::JsonFile; @@ -54,7 +53,8 @@ impl CheckPlatformReqsCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let mut composer = self.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer); let io = self.get_io(); let no_dev = input.get_option("no-dev").as_bool().unwrap_or(false); @@ -71,16 +71,27 @@ impl CheckPlatformReqsCommand { "<info>Checking {}platform requirements using the lock file</info>", if no_dev { "non-dev " } else { "" } )); - Box::new(composer.get_locker_mut().get_locked_repository(!no_dev)?) + Box::new( + composer + .get_locker() + .borrow_mut() + .get_locked_repository(!no_dev)?, + ) } else { - let local_repo = composer.get_repository_manager().get_local_repository(); + let repository_manager = composer.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let local_repo = repository_manager.get_local_repository(); if local_repo.get_packages().is_empty() { io.write_error(&format!( "<warning>No vendor dir present, checking {}platform requirements from the lock file</warning>", if no_dev { "non-dev " } else { "" } )); - Box::new(composer.get_locker_mut().get_locked_repository(!no_dev)?) - as Box<dyn crate::repository::RepositoryInterface> + Box::new( + composer + .get_locker() + .borrow_mut() + .get_locked_repository(!no_dev)?, + ) as Box<dyn crate::repository::RepositoryInterface> } else { if no_dev { remove_packages = local_repo.get_dev_package_names().clone(); diff --git a/crates/shirabe/src/command/clear_cache_command.rs b/crates/shirabe/src/command/clear_cache_command.rs index 515b0e4..4859a46 100644 --- a/crates/shirabe/src/command/clear_cache_command.rs +++ b/crates/shirabe/src/command/clear_cache_command.rs @@ -1,7 +1,8 @@ //! ref: composer/src/Composer/Command/ClearCacheCommand.php use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer; +use crate::composer::ComposerHandle; use crate::factory::Factory; use indexmap::IndexMap; use shirabe_external_packages::symfony::component::console::input::InputInterface; @@ -32,7 +33,7 @@ impl ClearCacheCommand { _output: &dyn OutputInterface, ) -> anyhow::Result<i64> { // TODO(phase-b): port full execute logic once Config sharing model is settled - let _ = Composer::VERSION; + let _ = composer::VERSION; let _: IndexMap<String, String> = IndexMap::new(); let _ = Factory::create_config(None, None); todo!("phase-b: ClearCacheCommand::execute requires Config sharing strategy") diff --git a/crates/shirabe/src/command/config_command.rs b/crates/shirabe/src/command/config_command.rs index 3b9e632..6412160 100644 --- a/crates/shirabe/src/command/config_command.rs +++ b/crates/shirabe/src/command/config_command.rs @@ -19,7 +19,6 @@ use shirabe_php_shim::{ use crate::advisory::Auditor; use crate::command::BaseConfigCommand; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::config::Config; use crate::config::ConfigSourceInterface; use crate::config::JsonConfigSource; diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index 6a289ed..c0093e8 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -15,7 +15,7 @@ use shirabe_php_shim::{ use crate::advisory::Auditor; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::config::Config; use crate::config::ConfigSourceInterface; use crate::config::JsonConfigSource; @@ -279,21 +279,21 @@ impl CreateProjectCommand { unlink("composer.lock"); } - let mut composer = + let mut composer_handle = self.create_composer_instance(input, io, None, disable_plugins, Some(disable_scripts))?; // add the repository to the composer.json and use it for the install run later if let Some(repos) = repositories.as_ref() { if add_repository { for (index, repo) in repos.iter().enumerate() { - let repo_config = RepositoryFactory::config_from_string( - io, - composer.get_config(), - repo, - true, - )?; + let config = crate::command::composer_full(&composer_handle).get_config(); + let repo_config = + RepositoryFactory::config_from_string(io, &config, repo, true)?; let composer_json_repositories_config = - composer.get_config().borrow().get_repositories(); + crate::command::composer_full(&composer_handle) + .get_config() + .borrow() + .get_repositories(); // TODO(phase-b): generate_repository_name expects existing repos as // IndexMap<String, Box<dyn RepositoryInterface>>; pass empty placeholder. let _ = &composer_json_repositories_config; @@ -333,12 +333,14 @@ impl CreateProjectCommand { ); } - composer = + composer_handle = self.create_composer_instance(input, io, None, disable_plugins, None)?; } } } + let mut composer = crate::command::composer_full_mut(&composer_handle); + let process = composer .get_loop() .borrow() @@ -358,7 +360,7 @@ impl CreateProjectCommand { ); // use the new config including the newly installed project - let config = std::rc::Rc::clone(composer.get_config()); + let config = composer.get_config(); let (ps, pd) = self.get_preferred_install_options(&*config.borrow(), input, false)?; prefer_source = ps; prefer_dist = pd; @@ -366,10 +368,11 @@ impl CreateProjectCommand { // install dependencies of the created project if no_install == false { composer - .get_installation_manager_mut() + .get_installation_manager() + .borrow_mut() .set_output_progress(!no_progress); - let mut installer = Installer::create(io.clone_box(), &composer); + let mut installer = Installer::create(io.clone_box(), &composer_handle); // TODO(phase-b): set_suggested_packages_reporter takes by value but PHP class // means shared ownership; needs Rc<SuggestedPackagesReporter> for proper sharing. installer @@ -402,7 +405,7 @@ impl CreateProjectCommand { ) .set_audit_config(self.create_audit_config(&mut *config.borrow_mut(), input)?); - if !composer.get_locker_mut().is_locked() { + if !composer.get_locker().borrow_mut().is_locked() { installer.set_update(true); } @@ -700,13 +703,14 @@ impl CreateProjectCommand { .into()); } - let composer = self.create_composer_instance( + let composer_handle = self.create_composer_instance( input, io, Some(config.borrow_mut().all(0)?), disable_plugins, Some(disable_scripts), )?; + let composer = crate::command::composer_full(&composer_handle); let config = composer.get_config(); // set the base dir here again on the new config instance, as otherwise in case the vendor dir is defined in an env var for example it would still override the value set above by $config->all() config.borrow_mut().set_base_dir(Some(directory.clone())); @@ -910,7 +914,8 @@ impl CreateProjectCommand { .set_prefer_dist(prefer_dist); let project_installer = ProjectInstaller::new(&directory, dm.clone(), fs.clone()); - let im = composer.get_installation_manager(); + let installation_manager = composer.get_installation_manager().clone(); + let mut im = installation_manager.borrow_mut(); im.set_output_progress(!no_progress); im.add_installer(Box::new(project_installer)); let mut installed_repo = InstalledArrayRepository::new()?; @@ -959,7 +964,7 @@ impl CreateProjectCommand { config: Option<indexmap::IndexMap<String, PhpMixed>>, disable_plugins: bool, disable_scripts: Option<bool>, - ) -> Result<Composer> { + ) -> Result<PartialComposerHandle> { self.create_composer_instance(input, io, config, disable_plugins, disable_scripts) } diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs index 1fdea40..e721e52 100644 --- a/crates/shirabe/src/command/diagnose_command.rs +++ b/crates/shirabe/src/command/diagnose_command.rs @@ -20,7 +20,8 @@ use shirabe_php_shim::{ use crate::advisory::Auditor; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer; +use crate::composer::ComposerHandle; use crate::config::Config; use crate::downloader::TransportException; use crate::factory::Factory; @@ -81,6 +82,7 @@ impl DiagnoseCommand { let config: std::rc::Rc<std::cell::RefCell<Config>>; if let Some(ref mut c) = composer { + let c = crate::command::composer_full(c); config = c.get_config().clone(); let command_event = CommandEvent::new6( @@ -146,7 +148,7 @@ impl DiagnoseCommand { io.write(&format!( "Composer version: <comment>{}</comment>", - Composer::get_version() + composer::get_version() )); io.write_no_newline("Checking Composer and its dependencies for vulnerabilities: "); @@ -244,18 +246,24 @@ impl DiagnoseCommand { )); if let Some(ref mut c) = composer { + let mut c = crate::command::composer_full_mut(c); io.write(&format!( "Active plugins: {}", - implode(", ", &c.get_plugin_manager().get_registered_plugins()) + implode( + ", ", + &c.get_plugin_manager().borrow().get_registered_plugins() + ) )); io.write_no_newline("Checking composer.json: "); let r = self.check_composer_schema()?; self.output_result(r); - if c.get_locker_mut().is_locked() { + if c.get_locker().borrow_mut().is_locked() { io.write_no_newline("Checking composer.lock: "); - let r = self.check_composer_lock_schema(c.get_locker_mut())?; + let locker = c.get_locker().clone(); + let locker = locker.borrow(); + let r = self.check_composer_lock_schema(&locker)?; self.output_result(r); } } @@ -874,11 +882,11 @@ impl DiagnoseCommand { .and_then(|v| v.as_string()) .unwrap_or("") .to_string(); - if Composer::VERSION != latest_version && Composer::VERSION != "@package_version@" { + if composer::VERSION != latest_version && composer::VERSION != "@package_version@" { return Ok(PhpMixed::String(format!( "<comment>You are not running the latest {} version, run `composer self-update` to update ({} => {})</comment>", versions_util.get_channel()?, - Composer::VERSION, + composer::VERSION, latest_version ))); } @@ -912,7 +920,7 @@ impl DiagnoseCommand { } let local_repo = FilesystemRepository::new(installed_json, false, None, None)?; - let version = Composer::get_version(); + let version = composer::get_version(); let mut packages = local_repo.inner.get_canonical_packages(); if version != "@package_version@" { let version_parser = VersionParser::new(); diff --git a/crates/shirabe/src/command/dump_autoload_command.rs b/crates/shirabe/src/command/dump_autoload_command.rs index 5e883c7..588c91e 100644 --- a/crates/shirabe/src/command/dump_autoload_command.rs +++ b/crates/shirabe/src/command/dump_autoload_command.rs @@ -6,7 +6,6 @@ use shirabe_external_packages::symfony::component::console::output::OutputInterf use shirabe_php_shim::{InvalidArgumentException, PhpMixed, file_exists}; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputOption; use crate::io::IOInterface; use crate::plugin::CommandEvent; @@ -47,7 +46,8 @@ impl DumpAutoloadCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> Result<i64> { - let mut composer = self.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer); // TODO(plugin): dispatch CommandEvent let command_event = @@ -58,11 +58,13 @@ impl DumpAutoloadCommand { .dispatch(Some(command_event.get_name()), None); // Clone the Rc<RefCell<Config>> so we can take mutable borrows of composer later - let config = std::rc::Rc::clone(composer.get_config()); + let config = composer.get_config(); let mut missing_dependencies = false; { - let local_repo = composer.get_repository_manager().get_local_repository(); + let repository_manager = composer.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let local_repo = repository_manager.get_local_repository(); for local_pkg in local_repo.get_canonical_packages() { // TODO(phase-b): get_install_path takes &mut self on installation_manager which conflicts with the &local_repo borrow held by this loop; needs shared-ownership refactor let install_path: Option<String> = @@ -137,10 +139,16 @@ impl DumpAutoloadCommand { let platform_requirement_filter = self.get_platform_requirement_filter(input)?; if input.get_option("dry-run").as_bool().unwrap_or(false) { - composer.get_autoload_generator_mut().set_dry_run(true); + composer + .get_autoload_generator() + .borrow_mut() + .set_dry_run(true); } if input.get_option("no-dev").as_bool().unwrap_or(false) { - composer.get_autoload_generator_mut().set_dev_mode(false); + composer + .get_autoload_generator() + .borrow_mut() + .set_dev_mode(false); } if input.get_option("dev").as_bool().unwrap_or(false) { if input.get_option("no-dev").as_bool().unwrap_or(false) { @@ -152,17 +160,26 @@ impl DumpAutoloadCommand { } .into()); } - composer.get_autoload_generator_mut().set_dev_mode(true); + composer + .get_autoload_generator() + .borrow_mut() + .set_dev_mode(true); } composer - .get_autoload_generator_mut() + .get_autoload_generator() + .borrow_mut() .set_class_map_authoritative(authoritative); - composer.get_autoload_generator_mut().set_run_scripts(true); composer - .get_autoload_generator_mut() + .get_autoload_generator() + .borrow_mut() + .set_run_scripts(true); + composer + .get_autoload_generator() + .borrow_mut() .set_apcu(apcu, apcu_prefix); composer - .get_autoload_generator_mut() + .get_autoload_generator() + .borrow_mut() .set_platform_requirement_filter(platform_requirement_filter); // TODO(phase-b): dump requires multiple borrows of composer simultaneously (autoload generator mut, repository, package, installation manager, locker); needs shared-ownership refactor let class_map: shirabe_class_map_generator::class_map::ClassMap = diff --git a/crates/shirabe/src/command/exec_command.rs b/crates/shirabe/src/command/exec_command.rs index 198bdb1..68e2d0f 100644 --- a/crates/shirabe/src/command/exec_command.rs +++ b/crates/shirabe/src/command/exec_command.rs @@ -6,7 +6,6 @@ use shirabe_external_packages::symfony::component::console::output::OutputInterf use shirabe_php_shim::{PhpMixed, RuntimeException, basename, chdir, getcwd, glob}; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::io::IOInterface; @@ -80,15 +79,15 @@ impl ExecCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let mut composer = self.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; if input.get_option("list").as_bool().unwrap_or(false) || input.get_argument("binary").as_string_opt().is_none() { let bins = self.get_binaries(true)?; if bins.is_empty() { - let bin_dir = composer - .get_config_mut() + let bin_dir = crate::command::composer_full_mut(&composer) + .get_config() .borrow_mut() .get("bin-dir") .as_string() @@ -119,9 +118,11 @@ impl ExecCommand { .unwrap_or("") .to_string(); - let dispatcher = composer.get_event_dispatcher(); + let dispatcher = crate::command::composer_full(&composer) + .get_event_dispatcher() + .clone(); // TODO(phase-b): add_listener takes a Callable; wiring binary as callable not yet ported - let _ = (dispatcher, &binary); + let _ = &binary; let initial_working_directory = self.get_application()?.get_initial_working_directory(); if let Some(ref iwd) = initial_working_directory { @@ -152,16 +153,17 @@ impl ExecCommand { } fn get_binaries(&mut self, for_display: bool) -> Result<Vec<String>> { - let mut composer = self.require_composer(None, None)?; - let bin_dir = composer - .get_config_mut() + let composer = self.require_composer(None, None)?; + let composer_ref = crate::command::composer_full_mut(&composer); + let bin_dir = composer_ref + .get_config() .borrow_mut() .get("bin-dir") .as_string() .unwrap_or("") .to_string(); let bins = glob(&format!("{}/*", bin_dir)); - let local_bins_raw: Vec<String> = composer.get_package().get_binaries(); + let local_bins_raw: Vec<String> = composer_ref.get_package().get_binaries(); let local_bins: Vec<String> = if for_display { local_bins_raw .into_iter() diff --git a/crates/shirabe/src/command/fund_command.rs b/crates/shirabe/src/command/fund_command.rs index 058e5af..233eeaa 100644 --- a/crates/shirabe/src/command/fund_command.rs +++ b/crates/shirabe/src/command/fund_command.rs @@ -13,7 +13,6 @@ use shirabe_semver::constraint::ConstraintInterface; use shirabe_semver::constraint::MatchAllConstraint; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputOption; use crate::io::IOInterface; use crate::json::JsonFile; @@ -51,11 +50,13 @@ impl FundCommand { _output: &dyn OutputInterface, ) -> Result<i64> { let composer = self.require_composer(None, None)?; + let composer = crate::command::composer_full(&composer); - let repo = composer.get_repository_manager().get_local_repository(); + let repository_manager = composer.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let repo = repository_manager.get_local_repository(); let remote_repos = CompositeRepository::new( - composer - .get_repository_manager() + repository_manager .get_repositories() .iter() .map(|r| r.clone_box()) diff --git a/crates/shirabe/src/command/global_command.rs b/crates/shirabe/src/command/global_command.rs index 27dec6a..a8cf1d4 100644 --- a/crates/shirabe/src/command/global_command.rs +++ b/crates/shirabe/src/command/global_command.rs @@ -10,7 +10,6 @@ use shirabe_external_packages::symfony::component::console::output::OutputInterf use shirabe_php_shim::{LogicException, RuntimeException, chdir}; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::factory::Factory; use crate::io::IOInterface; diff --git a/crates/shirabe/src/command/home_command.rs b/crates/shirabe/src/command/home_command.rs index 6f3ac5c..e7fc6b5 100644 --- a/crates/shirabe/src/command/home_command.rs +++ b/crates/shirabe/src/command/home_command.rs @@ -6,7 +6,6 @@ use shirabe_external_packages::symfony::component::console::output::OutputInterf use shirabe_php_shim::{FILTER_VALIDATE_URL, PhpMixed, filter_var}; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::io::IOInterface; @@ -90,12 +89,9 @@ impl HomeCommand { let packages = if packages.is_empty() { io.write_error("No package specified, opening homepage for the root package"); - vec![ - self.require_composer(None, None)? - .get_package() - .get_name() - .to_string(), - ] + let composer_rc = self.require_composer(None, None)?; + let composer_ref = crate::command::composer_full(&composer_rc); + vec![composer_ref.get_package().get_name().to_string()] } else { packages }; @@ -209,6 +205,7 @@ impl HomeCommand { let composer = self.try_composer(None, None); if let Some(composer) = composer { + let composer = crate::command::composer_full(&composer); let mut repos: Vec<Box<dyn RepositoryInterface>> = vec![]; repos.push(Box::new(RootPackageRepository::new( composer.get_package().clone_box(), diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs index eb6983f..122642d 100644 --- a/crates/shirabe/src/command/init_command.rs +++ b/crates/shirabe/src/command/init_command.rs @@ -19,7 +19,7 @@ use shirabe_php_shim::{ use crate::command::PackageDiscoveryTrait; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::console::input::InputOption; use crate::factory::Factory; use crate::io::IOInterface; @@ -56,7 +56,7 @@ impl PackageDiscoveryTrait for InitCommand { todo!() } - fn try_composer(&self) -> Option<Composer> { + fn try_composer(&self) -> Option<PartialComposerHandle> { todo!() } @@ -64,7 +64,7 @@ impl PackageDiscoveryTrait for InitCommand { &self, disable_plugins: Option<bool>, disable_scripts: Option<bool>, - ) -> Composer { + ) -> PartialComposerHandle { todo!() } diff --git a/crates/shirabe/src/command/install_command.rs b/crates/shirabe/src/command/install_command.rs index 666833d..632ccb1 100644 --- a/crates/shirabe/src/command/install_command.rs +++ b/crates/shirabe/src/command/install_command.rs @@ -100,9 +100,10 @@ impl InstallCommand { return Ok(1); } - let mut composer = self.require_composer(None, None)?; + let composer_handle = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer_handle); - if !composer.get_locker_mut().is_locked() && !HttpDownloader::is_curl_enabled() { + if !composer.get_locker().borrow_mut().is_locked() && !HttpDownloader::is_curl_enabled() { io.write_error("<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>"); } @@ -113,9 +114,9 @@ impl InstallCommand { .borrow_mut() .dispatch(Some(command_event.get_name()), None); - let mut install = Installer::create(io.clone_box(), &composer); + let mut install = Installer::create(io.clone_box(), &composer_handle); - let config = std::rc::Rc::clone(composer.get_config()); + let config = composer.get_config(); let (prefer_source, prefer_dist) = self.get_preferred_install_options(&*config.borrow(), input, false)?; @@ -153,7 +154,8 @@ impl InstallCommand { .unwrap_or(false); composer - .get_installation_manager_mut() + .get_installation_manager() + .borrow_mut() .set_output_progress(!input.get_option("no-progress").as_bool().unwrap_or(false)); install diff --git a/crates/shirabe/src/command/licenses_command.rs b/crates/shirabe/src/command/licenses_command.rs index 0c00ed6..961f70b 100644 --- a/crates/shirabe/src/command/licenses_command.rs +++ b/crates/shirabe/src/command/licenses_command.rs @@ -12,7 +12,6 @@ use shirabe_external_packages::symfony::console::style::SymfonyStyle; use shirabe_php_shim::{PhpMixed, RuntimeException, UnexpectedValueException}; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputOption; use crate::io::IOInterface; use crate::json::JsonFile; @@ -80,7 +79,8 @@ impl LicensesCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> Result<i64> { - let mut composer = self.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer); // TODO(plugin): dispatch COMMAND event for plugin hooks let command_event = CommandEvent::new(PluginEvents::COMMAND, "licenses", input, output); @@ -95,7 +95,8 @@ impl LicensesCommand { let root_licenses_snap = composer.get_package().get_license().clone(); let packages = if input.get_option("locked").as_bool().unwrap_or(false) { - let locker = composer.get_locker_mut(); + let locker = composer.get_locker().clone(); + let mut locker = locker.borrow_mut(); if !locker.is_locked() { return Err(UnexpectedValueException { message: "Valid composer.json and composer.lock files are required to run this command with --locked".to_string(), @@ -106,7 +107,9 @@ impl LicensesCommand { let repo = locker.get_locked_repository(!no_dev)?; <crate::repository::LockArrayRepository as crate::repository::RepositoryInterface>::get_packages(&repo) } else { - let repo = composer.get_repository_manager().get_local_repository(); + let repository_manager = composer.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let repo = repository_manager.get_local_repository(); if input.get_option("no-dev").as_bool().unwrap_or(false) { RepositoryUtils::filter_required_packages( &repo.get_packages(), diff --git a/crates/shirabe/src/command/mod.rs b/crates/shirabe/src/command/mod.rs index 614fff7..4452f85 100644 --- a/crates/shirabe/src/command/mod.rs +++ b/crates/shirabe/src/command/mod.rs @@ -37,6 +37,8 @@ pub mod suggests_command; pub mod update_command; pub mod validate_command; +pub(crate) use crate::composer::{composer_full, composer_full_mut}; + pub use about_command::*; pub use archive_command::*; pub use audit_command::*; diff --git a/crates/shirabe/src/command/outdated_command.rs b/crates/shirabe/src/command/outdated_command.rs index c4d9e89..89edb44 100644 --- a/crates/shirabe/src/command/outdated_command.rs +++ b/crates/shirabe/src/command/outdated_command.rs @@ -1,7 +1,6 @@ //! ref: composer/src/Composer/Command/OutdatedCommand.php use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::io::IOInterface; diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs index f273811..43d6ee8 100644 --- a/crates/shirabe/src/command/package_discovery_trait.rs +++ b/crates/shirabe/src/command/package_discovery_trait.rs @@ -15,7 +15,7 @@ use shirabe_php_shim::{ trim, }; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::factory::Factory; use crate::filter::platform_requirement_filter::IgnoreAllPlatformRequirementFilter; use crate::filter::platform_requirement_filter::PlatformRequirementFilterFactory; @@ -41,12 +41,12 @@ pub trait PackageDiscoveryTrait { // PHP: trait dependencies (provided by BaseCommand) fn get_io(&self) -> &dyn IOInterface; - fn try_composer(&self) -> Option<Composer>; + fn try_composer(&self) -> Option<PartialComposerHandle>; fn require_composer( &self, disable_plugins: Option<bool>, disable_scripts: Option<bool>, - ) -> Composer; + ) -> PartialComposerHandle; fn get_platform_requirement_filter( &self, input: &dyn InputInterface, @@ -219,10 +219,14 @@ pub trait PackageDiscoveryTrait { // Collect existing packages let composer = self.try_composer(); - let mut installed_repo: Option<_> = None; - if let Some(c) = &composer { - installed_repo = Some(c.get_repository_manager().get_local_repository()); - } + let composer_ref = composer.as_ref().map(|c| c.borrow_partial()); + let repository_manager = composer_ref + .as_ref() + .map(|c| c.get_repository_manager().clone()); + let repository_manager_ref = repository_manager.as_ref().map(|rm| rm.borrow()); + let installed_repo = repository_manager_ref + .as_ref() + .map(|rm| rm.get_local_repository()); let mut existing_packages: Vec<String> = vec![]; if let Some(repo) = &installed_repo { for package in repo.get_packages() { @@ -231,6 +235,9 @@ pub trait PackageDiscoveryTrait { } // PHP: unset($composer, $installedRepo); drop(installed_repo); + drop(repository_manager_ref); + drop(repository_manager); + drop(composer_ref); drop(composer); let io = self.get_io(); @@ -804,9 +811,10 @@ pub trait PackageDiscoveryTrait { let mut similar_packages: IndexMap<String, i64> = IndexMap::new(); let composer_for_installed = self.require_composer(None, None); - let installed_repo = composer_for_installed - .get_repository_manager() - .get_local_repository(); + let composer_for_installed = composer_for_installed.borrow_partial(); + let repository_manager = composer_for_installed.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let installed_repo = repository_manager.get_local_repository(); for result in &results { // TODO(phase-b): installed_repo.find_package signature mismatch with FindPackageConstraint diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index 3e985a4..8bb4643 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -9,7 +9,6 @@ use shirabe_external_packages::symfony::component::console::output::OutputInterf use shirabe_php_shim::InvalidArgumentException; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputDefinitionItem; use crate::console::input::InputOption; @@ -68,9 +67,12 @@ impl ReinstallCommand { output: &dyn OutputInterface, ) -> Result<i64> { let composer = self.require_composer(None, None)?; + let composer = crate::command::composer_full(&composer); let io = self.get_io(); - let local_repo = composer.get_repository_manager().get_local_repository(); + let repository_manager = composer.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let local_repo = repository_manager.get_local_repository(); let mut packages_to_reinstall: Vec<Box<dyn crate::package::PackageInterface>> = vec![]; let mut package_names_to_reinstall: Vec<String> = vec![]; @@ -193,11 +195,11 @@ impl ReinstallCommand { .borrow_mut() .dispatch(Some(command_event.get_name()), None); - let config = std::rc::Rc::clone(composer.get_config()); + let config = composer.get_config(); let (prefer_source, prefer_dist) = self.get_preferred_install_options(&*config.borrow(), input, false)?; - let installation_manager = composer.get_installation_manager(); + let installation_manager = composer.get_installation_manager().clone(); let download_manager = composer.get_download_manager(); let package = composer.get_package(); diff --git a/crates/shirabe/src/command/remove_command.rs b/crates/shirabe/src/command/remove_command.rs index dbffcea..ab322f9 100644 --- a/crates/shirabe/src/command/remove_command.rs +++ b/crates/shirabe/src/command/remove_command.rs @@ -9,7 +9,6 @@ use shirabe_php_shim::{PhpMixed, UnexpectedValueException, array_map, strtolower use crate::advisory::Auditor; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::config::ConfigSourceInterface; use crate::config::JsonConfigSource; use crate::console::input::InputArgument; @@ -183,9 +182,11 @@ impl RemoveCommand { .unwrap_or_default(); if input.get_option("unused").as_bool().unwrap_or(false) { - let mut composer = self.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer); { - let locker = composer.get_locker_mut(); + let locker = composer.get_locker().clone(); + let mut locker = locker.borrow_mut(); if !locker.is_locked() { return Err(anyhow::anyhow!(UnexpectedValueException { message: @@ -197,7 +198,8 @@ impl RemoveCommand { } let locked_packages = composer - .get_locker_mut() + .get_locker() + .borrow_mut() .get_locked_repository(true)? .get_packages(); @@ -426,14 +428,17 @@ impl RemoveCommand { } // TODO(plugin): deactivate installed plugins - if let Some(mut composer_opt) = self.try_composer(None, None) { + if let Some(composer_opt) = self.try_composer(None, None) { + let mut composer_opt = crate::command::composer_full_mut(&composer_opt); composer_opt - .get_plugin_manager_mut() + .get_plugin_manager() + .borrow_mut() .deactivate_installed_plugins(); } self.reset_composer(); - let mut composer = self.require_composer(None, None)?; + let composer_handle = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer_handle); if dry_run { // TODO(phase-b): composer.get_package() returns &dyn RootPackageInterface; set_requires/set_dev_requires need &mut self; needs shared-ownership refactor @@ -501,12 +506,13 @@ impl RemoveCommand { } composer - .get_installation_manager_mut() + .get_installation_manager() + .borrow_mut() .set_output_progress(!input.get_option("no-progress").as_bool().unwrap_or(false)); // TODO(phase-b): Installer::create expects Box<dyn IOInterface>; io here is &mut dyn IOInterface let io_box: Box<dyn IOInterface> = todo!("share IOInterface as Box<dyn IOInterface>"); - let mut install = Installer::create(io_box, &composer); + let mut install = Installer::create(io_box, &composer_handle); let update_dev_mode = !input.get_option("update-no-dev").as_bool().unwrap_or(false); let optimize = input @@ -601,7 +607,7 @@ impl RemoveCommand { // if no lock is present, we do not do a partial update as // this is not supported by the Installer - if composer.get_locker_mut().is_locked() { + if composer.get_locker().borrow_mut().is_locked() { install.set_update_allow_list(packages.clone()); } @@ -618,6 +624,7 @@ impl RemoveCommand { for package in &packages { if !composer .get_repository_manager() + .borrow() .get_local_repository() .find_packages(package, None) .is_empty() diff --git a/crates/shirabe/src/command/repository_command.rs b/crates/shirabe/src/command/repository_command.rs index dce57b5..1f6599b 100644 --- a/crates/shirabe/src/command/repository_command.rs +++ b/crates/shirabe/src/command/repository_command.rs @@ -10,7 +10,6 @@ use shirabe_php_shim::{ use crate::command::BaseConfigCommand; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::config::Config; use crate::config::ConfigSourceInterface; use crate::config::JsonConfigSource; diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index 5f4e27a..15bff24 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -17,7 +17,7 @@ use shirabe_php_shim::{ use crate::advisory::Auditor; use crate::command::PackageDiscoveryTrait; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::dependency_resolver::Request; @@ -74,7 +74,7 @@ impl PackageDiscoveryTrait for RequireCommand { todo!() } - fn try_composer(&self) -> Option<Composer> { + fn try_composer(&self) -> Option<PartialComposerHandle> { todo!() } @@ -82,7 +82,7 @@ impl PackageDiscoveryTrait for RequireCommand { &self, disable_plugins: Option<bool>, disable_scripts: Option<bool>, - ) -> Composer { + ) -> PartialComposerHandle { todo!() } @@ -251,7 +251,10 @@ impl RequireCommand { } let composer = self.require_composer(None, None)?; - let repos = composer.get_repository_manager().get_repositories(); + let composer = crate::command::composer_full(&composer); + let repository_manager = composer.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let repos = repository_manager.get_repositories(); let platform_overrides = composer.get_config().borrow_mut().get("platform"); let platform_overrides_map: IndexMap<String, PhpMixed> = platform_overrides @@ -545,7 +548,10 @@ impl RequireCommand { return Ok(0); } - composer.get_plugin_manager().deactivate_installed_plugins(); + composer + .get_plugin_manager() + .borrow_mut() + .deactivate_installed_plugins(); // try/catch/finally // TODO(phase-b): do_update borrows io from self while also needing &mut self for state @@ -683,7 +689,8 @@ impl RequireCommand { ) -> Result<i64> { // Update packages self.reset_composer()?; - let mut composer = self.require_composer(None, None)?; + let composer_handle = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer_handle); self.dependency_resolution_completed = false; // TODO(phase-b): add_listener expects a Callable enum; PHP closure should set @@ -837,12 +844,14 @@ impl RequireCommand { .dispatch(Some(command_event.get_name()), None); composer - .get_installation_manager_mut() + .get_installation_manager() + .borrow_mut() .set_output_progress(!input.get_option("no-progress").as_bool().unwrap_or(false)); // TODO(phase-b): Installer::create takes Box<dyn IOInterface> for ownership but io is a // borrowed &dyn here; needs Rc<dyn IOInterface> for proper sharing. - let mut install = Installer::create(todo!("share io as Box<dyn IOInterface>"), &composer); + let mut install = + Installer::create(todo!("share io as Box<dyn IOInterface>"), &composer_handle); let (prefer_source, prefer_dist) = self.get_preferred_install_options(&*composer.get_config().borrow(), input, false)?; @@ -871,7 +880,7 @@ impl RequireCommand { // if no lock is present, or the file is brand new, we do not do a // partial update as this is not supported by the Installer - if !self.first_require && composer.get_locker().is_locked() { + if !self.first_require && composer.get_locker().borrow_mut().is_locked() { install.set_update_allow_list( array_keys(requirements) .into_iter() @@ -920,8 +929,9 @@ impl RequireCommand { dry_run: bool, fixed: bool, ) -> Result<i64> { - let mut composer = self.require_composer(None, None)?; - let locker_is_locked = composer.get_locker_mut().is_locked(); + let composer = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer); + let locker_is_locked = composer.get_locker().borrow_mut().is_locked(); let mut requirements: IndexMap<String, String> = IndexMap::new(); let mut version_selector = VersionSelector::new( RepositorySet::new( @@ -939,7 +949,10 @@ impl RequireCommand { // interface for find_package. let locked_repo; let repo: &dyn RepositoryInterface = if locker_is_locked { - locked_repo = composer.get_locker_mut().get_locked_repository(true)?; + locked_repo = composer + .get_locker() + .borrow_mut() + .get_locked_repository(true)?; &locked_repo } else { todo!("convert &dyn InstalledRepositoryInterface to &dyn RepositoryInterface") diff --git a/crates/shirabe/src/command/run_script_command.rs b/crates/shirabe/src/command/run_script_command.rs index 8121feb..436d906 100644 --- a/crates/shirabe/src/command/run_script_command.rs +++ b/crates/shirabe/src/command/run_script_command.rs @@ -7,7 +7,6 @@ use shirabe_external_packages::symfony::component::console::output::OutputInterf use shirabe_php_shim::{InvalidArgumentException, PhpMixed, RuntimeException}; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::io::IOInterface; @@ -184,6 +183,9 @@ impl RunScriptCommand { } let composer = self.require_composer(None, None)?; + let dispatcher = crate::command::composer_full(&composer) + .get_event_dispatcher() + .clone(); let dev_mode = input.get_option("dev").as_bool().unwrap_or(false) || !input.get_option("no-dev").as_bool().unwrap_or(false); // TODO(phase-b): ScriptEvent::new takes Composer/IOInterface by value; placeholder construction. @@ -224,8 +226,7 @@ impl RunScriptCommand { Platform::put_env("COMPOSER_DEV_MODE", if dev_mode { "1" } else { "0" }); - Ok(composer - .get_event_dispatcher() + Ok(dispatcher .borrow_mut() .dispatch_script(&script, dev_mode, args, IndexMap::new())?) } @@ -254,10 +255,11 @@ impl RunScriptCommand { } fn get_scripts(&mut self) -> Result<Vec<(String, String)>> { - let scripts = self - .require_composer(None, None)? + let composer = self.require_composer(None, None)?; + let scripts = crate::command::composer_full(&composer) .get_package() .get_scripts(); + drop(composer); if scripts.is_empty() { return Ok(vec![]); } diff --git a/crates/shirabe/src/command/script_alias_command.rs b/crates/shirabe/src/command/script_alias_command.rs index a054631..6499002 100644 --- a/crates/shirabe/src/command/script_alias_command.rs +++ b/crates/shirabe/src/command/script_alias_command.rs @@ -1,7 +1,6 @@ //! ref: composer/src/Composer/Command/ScriptAliasCommand.php use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::io::IOInterface; @@ -98,6 +97,9 @@ impl ScriptAliasCommand { _output: &dyn OutputInterface, ) -> Result<i64> { let composer = self.require_composer(None, None)?; + let dispatcher = crate::command::composer_full(&composer) + .get_event_dispatcher() + .clone(); let args = input.get_arguments(); @@ -136,8 +138,7 @@ impl ScriptAliasCommand { }) .unwrap_or_default(); - Ok(composer - .get_event_dispatcher() + Ok(dispatcher .borrow_mut() .dispatch_script(&self.script, dev_mode, args_value, flags)?) } diff --git a/crates/shirabe/src/command/search_command.rs b/crates/shirabe/src/command/search_command.rs index 16df221..fb6b047 100644 --- a/crates/shirabe/src/command/search_command.rs +++ b/crates/shirabe/src/command/search_command.rs @@ -1,7 +1,6 @@ //! ref: composer/src/Composer/Command/SearchCommand.php use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::io::IOInterface; @@ -77,21 +76,27 @@ impl SearchCommand { let io_box = self.get_io().clone_box(); self.create_composer_instance(input, io_box.as_ref(), None, false, None)? }; + let composer_ref = crate::command::composer_full(&composer); // TODO(phase-b): get_local_repository returns &dyn InstalledRepositoryInterface but we need Box<dyn RepositoryInterface> let local_repo: Box<dyn RepositoryInterface> = todo!("share local_repo as RepositoryInterface"); let installed_repo = CompositeRepository::new(vec![local_repo, Box::new(platform_repo)]); let mut all_repos: Vec<Box<dyn RepositoryInterface>> = vec![Box::new(installed_repo)]; // TODO(phase-b): get_repositories returns &Vec<Box<...>>; needs ownership reshape - for r in composer.get_repository_manager().get_repositories() { + for r in composer_ref + .get_repository_manager() + .borrow() + .get_repositories() + { all_repos.push(r.clone_box()); } let repos = CompositeRepository::new(all_repos); // TODO(plugin): dispatch CommandEvent for search command let command_event = CommandEvent::new(PluginEvents::COMMAND, "search", input, output); - composer - .get_event_dispatcher() + let dispatcher = composer_ref.get_event_dispatcher().clone(); + drop(composer_ref); + dispatcher .borrow_mut() .dispatch(Some(command_event.get_name()), None); diff --git a/crates/shirabe/src/command/self_update_command.rs b/crates/shirabe/src/command/self_update_command.rs index ccfbe07..8645ce1 100644 --- a/crates/shirabe/src/command/self_update_command.rs +++ b/crates/shirabe/src/command/self_update_command.rs @@ -20,7 +20,8 @@ use shirabe_php_shim::{ }; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer; +use crate::composer::ComposerHandle; use crate::config::Config; use crate::console::input::InputArgument; use crate::console::input::InputOption; @@ -280,7 +281,7 @@ impl SelfUpdateCommand { .as_string() .map(|s| s.to_string()) .unwrap_or_else(|| latest_version.clone()); - let current_major_version = Preg::replace(r"{^(\d+).*}", "$1", &Composer::get_version())?; + let current_major_version = Preg::replace(r"{^(\d+).*}", "$1", &composer::get_version())?; let update_major_version = Preg::replace(r"{^(\d+).*}", "$1", &update_version)?; let preview_major_version = Preg::replace( r"{^(\d+).*}", @@ -382,7 +383,7 @@ impl SelfUpdateCommand { channel_string.push_str(".x"); } - if Composer::VERSION == update_version.as_str() { + if composer::VERSION == update_version.as_str() { io.write_error3( &sprintf( "<info>You are already using the latest available Composer version %s (%s channel).</info>", @@ -414,11 +415,11 @@ impl SelfUpdateCommand { "%s/%s-%s%s", &[ PhpMixed::String(rollback_dir.clone()), - PhpMixed::String(strtr(Composer::RELEASE_DATE, " :", "_-")), + PhpMixed::String(strtr(composer::RELEASE_DATE, " :", "_-")), PhpMixed::String(Preg::replace( r"{^([0-9a-f]{7})[0-9a-f]{33}$}", "$1", - &Composer::VERSION, + &composer::VERSION, )?), PhpMixed::String(Self::OLD_INSTALL_EXT.to_string()), ], @@ -637,7 +638,7 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ io.write_error3( &sprintf( "Use <info>composer self-update --rollback</info> to return to version <comment>%s</comment>", - &[PhpMixed::String(Composer::VERSION.to_string())], + &[PhpMixed::String(composer::VERSION.to_string())], ), true, io_interface::NORMAL, diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs index ae386ac..3eac2df 100644 --- a/crates/shirabe/src/command/show_command.rs +++ b/crates/shirabe/src/command/show_command.rs @@ -16,7 +16,7 @@ use shirabe_php_shim::{ use shirabe_semver::constraint::ConstraintInterface; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::console::input::InputOption; use crate::dependency_resolver::DefaultPolicy; use crate::dependency_resolver::PolicyInterface; @@ -179,6 +179,7 @@ impl ShowCommand { // init repos let mut platform_overrides: IndexMap<String, PhpMixed> = IndexMap::new(); if let Some(ref composer) = composer { + let composer = crate::command::composer_full(composer); if let Some(p) = composer .get_config() .borrow() @@ -208,7 +209,10 @@ impl ShowCommand { && input.get_option("installed").as_bool() != Some(true) && input.get_option("locked").as_bool() != Some(true) { - let package = self.require_composer(None, None)?.get_package().clone_box(); + let _rc = self.require_composer(None, None)?; + let package = crate::command::composer_full(&_rc) + .get_package() + .clone_box(); if input.get_option("name-only").as_bool() == Some(true) { self.get_io().write(package.get_name()); @@ -239,9 +243,11 @@ impl ShowCommand { } else if input.get_option("available").as_bool() == Some(true) { let mut ir = InstalledRepository::new(vec![Box::new(make_platform_repo()?)]); if let Some(ref composer) = composer { + let composer = crate::command::composer_full(composer); repos = Box::new(CompositeRepository::new( composer .get_repository_manager() + .borrow() .get_repositories() .iter() .map(|r| r.clone_box()) @@ -250,6 +256,7 @@ impl ShowCommand { ir.add_repository( composer .get_repository_manager() + .borrow() .get_local_repository() .clone_box(), ); @@ -268,12 +275,14 @@ impl ShowCommand { installed_repo = Box::new(ir); } } else if input.get_option("all").as_bool() == Some(true) && composer.is_some() { - let composer_ref = composer.as_mut().unwrap(); + let mut composer_ref = crate::command::composer_full_mut(composer.as_ref().unwrap()); let local_repo_cloned = composer_ref .get_repository_manager() + .borrow() .get_local_repository() .clone_box(); - let locker = composer_ref.get_locker_mut(); + let locker_rc = composer_ref.get_locker().clone(); + let mut locker = locker_rc.borrow_mut(); if locker.is_locked() { let lr = locker.get_locked_repository(true)?; installed_repo = Box::new(InstalledRepository::new(vec![ @@ -297,7 +306,11 @@ impl ShowCommand { m })?, )]; - for r in composer_ref.get_repository_manager().get_repositories() { + for r in composer_ref + .get_repository_manager() + .borrow() + .get_repositories() + { composite_input.push(r.clone_box()); } repos = Box::new(CompositeRepository::new(composite_input)); @@ -319,15 +332,21 @@ impl ShowCommand { } repos = Box::new(CompositeRepository::new(composite_input)); } else if input.get_option("locked").as_bool() == Some(true) { - if composer.is_none() || !composer.as_mut().unwrap().get_locker_mut().is_locked() { + if composer.is_none() + || !crate::command::composer_full_mut(composer.as_ref().unwrap()) + .get_locker() + .borrow_mut() + .is_locked() + { return Err(UnexpectedValueException { message: "A valid composer.json and composer.lock files is required to run this command with --locked".to_string(), code: 0, } .into()); } - let composer_ref = composer.as_mut().unwrap(); - let locker = composer_ref.get_locker_mut(); + let mut composer_ref = crate::command::composer_full_mut(composer.as_ref().unwrap()); + let locker_rc = composer_ref.get_locker().clone(); + let mut locker = locker_rc.borrow_mut(); let mut lr = locker.get_locked_repository(input.get_option("no-dev").as_bool() != Some(true))?; if input.get_option("self").as_bool() == Some(true) { @@ -346,11 +365,17 @@ impl ShowCommand { // can't clone Composer, so we re-fetch via require_composer when missing // but otherwise borrow the existing Option. let composer_local_owned; - let composer_local: &Composer = match composer.as_ref() { - Some(c) => c, + // Borrow guards that keep the Ref alive for the duration of the block. + let _guard_from_existing; + let composer_local = match composer.as_ref() { + Some(c) => { + _guard_from_existing = crate::command::composer_full(c); + &*_guard_from_existing + } None => { composer_local_owned = self.require_composer(None, None)?; - &composer_local_owned + _guard_from_existing = crate::command::composer_full(&composer_local_owned); + &*_guard_from_existing } }; let root_pkg = composer_local.get_package(); @@ -364,6 +389,7 @@ impl ShowCommand { if input.get_option("no-dev").as_bool() == Some(true) { let local_packages = composer_local .get_repository_manager() + .borrow() .get_local_repository() .get_packages(); let packages = RepositoryUtils::filter_required_packages( @@ -385,9 +411,9 @@ impl ShowCommand { Box::new(InstalledArrayRepository::new_with_packages(Vec::new())?), ])); } else { - let lr = composer_local - .get_repository_manager() - .get_local_repository(); + let repository_manager = composer_local.get_repository_manager().clone(); + let repository_manager = repository_manager.borrow(); + let lr = repository_manager.get_local_repository(); installed_repo = Box::new(InstalledRepository::new(vec![ root_repo.clone_box(), lr.clone_box(), @@ -412,6 +438,7 @@ impl ShowCommand { } if let Some(ref composer) = composer { + let composer = crate::command::composer_full(composer); let command_event = CommandEvent::new6( PluginEvents::COMMAND, "show", @@ -1418,11 +1445,12 @@ impl ShowCommand { } pub(crate) fn get_root_requires(&mut self) -> Vec<String> { - let composer = self.try_composer(None, None); - let composer = match composer { + let composer_rc = self.try_composer(None, None); + let composer_rc = match composer_rc { None => return vec![], Some(c) => c, }; + let composer = crate::command::composer_full(&composer_rc); let root_package = composer.get_package(); @@ -2522,7 +2550,7 @@ impl ShowCommand { fn find_latest_package( &mut self, package: &dyn PackageInterface, - composer: &Composer, + composer: &PartialComposerHandle, platform_repo: &PlatformRepository, major_only: bool, minor_only: bool, @@ -2534,17 +2562,21 @@ impl ShowCommand { // TODO(phase-b): VersionSelector::new wants RepositorySet by value, but get_repository_set // returns &mut RepositorySet. Constructing a placeholder set keeps compile clean. let _ = self.get_repository_set(composer)?; + let composer_ref = crate::command::composer_full(composer); let placeholder_rs = RepositorySet::new( - composer.get_package().get_minimum_stability(), - composer.get_package().get_stability_flags().clone(), + composer_ref.get_package().get_minimum_stability(), + composer_ref.get_package().get_stability_flags().clone(), Vec::new(), IndexMap::new(), IndexMap::new(), IndexMap::new(), ); let mut version_selector = VersionSelector::new(placeholder_rs, Some(platform_repo))?; - let mut stability = composer.get_package().get_minimum_stability().to_string(); - let flags = composer.get_package().get_stability_flags(); + let mut stability = composer_ref + .get_package() + .get_minimum_stability() + .to_string(); + let flags = composer_ref.get_package().get_stability_flags(); if let Some(flag_value) = flags.get(name) { let key_map: IndexMap<String, String> = base_package::STABILITIES .iter() @@ -2557,7 +2589,7 @@ impl ShowCommand { } let mut best_stability = stability.clone(); - if composer.get_package().get_prefer_stable() { + if composer_ref.get_package().get_prefer_stable() { best_stability = package.get_stability().to_string(); } @@ -2654,7 +2686,11 @@ impl ShowCommand { Ok(candidate) } - fn get_repository_set(&mut self, composer: &Composer) -> anyhow::Result<&mut RepositorySet> { + fn get_repository_set( + &mut self, + composer: &PartialComposerHandle, + ) -> anyhow::Result<&mut RepositorySet> { + let composer = crate::command::composer_full(composer); if self.repository_set.is_none() { // TODO(phase-b): RepositorySet::with_stability_and_flags — using new() placeholder. let mut rs = RepositorySet::new( @@ -2668,6 +2704,7 @@ impl ShowCommand { rs.add_repository(Box::new(CompositeRepository::new( composer .get_repository_manager() + .borrow() .get_repositories() .iter() .map(|r| r.clone_box()) diff --git a/crates/shirabe/src/command/status_command.rs b/crates/shirabe/src/command/status_command.rs index 801d2c6..83a21b8 100644 --- a/crates/shirabe/src/command/status_command.rs +++ b/crates/shirabe/src/command/status_command.rs @@ -6,7 +6,6 @@ use shirabe_external_packages::symfony::component::console::input::InputInterfac use shirabe_external_packages::symfony::component::console::output::OutputInterface; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputOption; use crate::io::IOInterface; use crate::package::dumper::ArrayDumper; @@ -44,42 +43,49 @@ impl StatusCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.require_composer(None, None)?; + let composer_rc = self.require_composer(None, None)?; + { + let composer = crate::command::composer_full(&composer_rc); - // TODO(plugin): dispatch CommandEvent - let command_event = CommandEvent::new(PluginEvents::COMMAND, "status", input, output); - composer - .get_event_dispatcher() - .borrow_mut() - .dispatch(Some(command_event.get_name()), None); + // TODO(plugin): dispatch CommandEvent + let command_event = CommandEvent::new(PluginEvents::COMMAND, "status", input, output); + composer + .get_event_dispatcher() + .borrow_mut() + .dispatch(Some(command_event.get_name()), None); - composer - .get_event_dispatcher() - .borrow_mut() - .dispatch_script( - ScriptEvents::PRE_STATUS_CMD, - true, - vec![], - indexmap::IndexMap::new(), - ); + composer + .get_event_dispatcher() + .borrow_mut() + .dispatch_script( + ScriptEvents::PRE_STATUS_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); + } let exit_code = self.do_execute(input)?; - composer - .get_event_dispatcher() - .borrow_mut() - .dispatch_script( - ScriptEvents::POST_STATUS_CMD, - true, - vec![], - indexmap::IndexMap::new(), - ); + { + let composer = crate::command::composer_full(&composer_rc); + composer + .get_event_dispatcher() + .borrow_mut() + .dispatch_script( + ScriptEvents::POST_STATUS_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); + } Ok(exit_code) } fn do_execute(&mut self, input: &dyn InputInterface) -> Result<i64> { - let mut composer = self.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer); // TODO(phase-b): release the &mut self borrow held by get_io via clone_box. let io_box = self.get_io().clone_box(); let io: &dyn IOInterface = io_box.as_ref(); @@ -97,7 +103,7 @@ impl StatusCommand { .map(std::rc::Rc::clone) .unwrap_or_else(|| std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(io)))); let mut guesser = VersionGuesser::new( - std::rc::Rc::clone(composer.get_config()), + composer.get_config(), std::rc::Rc::clone(&process_executor), parser.clone(), Some(io_box.clone_box()), @@ -107,11 +113,13 @@ impl StatusCommand { let dm = composer.get_download_manager().clone(); let packages: Vec<_> = composer .get_repository_manager() + .borrow() .get_local_repository() .get_canonical_packages(); for package in packages { let target_dir = composer - .get_installation_manager_mut() + .get_installation_manager() + .borrow_mut() .get_install_path(package.as_ref()); let target_dir = match target_dir { Some(d) => d, diff --git a/crates/shirabe/src/command/suggests_command.rs b/crates/shirabe/src/command/suggests_command.rs index 9d5897c..6b617b1 100644 --- a/crates/shirabe/src/command/suggests_command.rs +++ b/crates/shirabe/src/command/suggests_command.rs @@ -1,7 +1,6 @@ //! ref: composer/src/Composer/Command/SuggestsCommand.php use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::installer::SuggestedPackagesReporter; @@ -45,15 +44,19 @@ impl SuggestsCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let mut composer = self.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; + let mut composer = crate::command::composer_full_mut(&composer); let mut installed_repos: Vec<Box<dyn RepositoryInterface>> = vec![Box::new( RootPackageRepository::new(composer.get_package().clone_box()), )]; - if composer.get_locker_mut().is_locked() { + if composer.get_locker().borrow_mut().is_locked() { // TODO(phase-b): get_platform_overrides returns IndexMap<String, String>; PlatformRepository::new expects IndexMap<String, PhpMixed> - let _platform_overrides = composer.get_locker_mut().get_platform_overrides()?; + let _platform_overrides = composer + .get_locker() + .borrow_mut() + .get_platform_overrides()?; let platform_overrides: IndexMap<String, PhpMixed> = todo!("convert IndexMap<String, String> to IndexMap<String, PhpMixed>"); installed_repos.push(Box::new(PlatformRepository::new( @@ -61,7 +64,8 @@ impl SuggestsCommand { platform_overrides, )?)); let locked_repo = composer - .get_locker_mut() + .get_locker() + .borrow_mut() .get_locked_repository(!input.get_option("no-dev").as_bool().unwrap_or(false))?; installed_repos.push(Box::new(locked_repo)); } else { @@ -76,6 +80,7 @@ impl SuggestsCommand { installed_repos.push( composer .get_repository_manager() + .borrow() .get_local_repository() .clone_box(), ); diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs index 3595066..b96793d 100644 --- a/crates/shirabe/src/command/update_command.rs +++ b/crates/shirabe/src/command/update_command.rs @@ -18,7 +18,7 @@ use shirabe_semver::intervals::Intervals; use crate::advisory::Auditor; use crate::command::BumpCommand; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::dependency_resolver::request::{self, Request, UpdateAllowTransitiveDeps}; @@ -95,7 +95,8 @@ impl UpdateCommand { ); } - let composer = self.require_composer(None, None)?; + let composer_handle = self.require_composer(None, None)?; + let composer = crate::command::composer_full(&composer_handle); if !HttpDownloader::is_curl_enabled() { io.write_error3( @@ -205,7 +206,7 @@ impl UpdateCommand { } if input.get_option("patch-only").as_bool().unwrap_or(false) { - if !composer.get_locker().is_locked() { + if !composer.get_locker().borrow_mut().is_locked() { return Err(InvalidArgumentException { message: "patch-only can only be used with a lock file present".to_string(), code: 0, @@ -214,6 +215,7 @@ impl UpdateCommand { } for package in composer .get_locker() + .borrow_mut() .get_locked_repository(true)? .get_canonical_packages() { @@ -249,7 +251,8 @@ impl UpdateCommand { } if input.get_option("interactive").as_bool().unwrap_or(false) { - packages = self.get_packages_interactively(io, input, output, &composer, packages)?; + packages = + self.get_packages_interactively(io, input, output, &composer_handle, packages)?; } if input.get_option("root-reqs").as_bool().unwrap_or(false) { @@ -309,11 +312,12 @@ impl UpdateCommand { composer .get_installation_manager() + .borrow_mut() .set_output_progress(!input.get_option("no-progress").as_bool().unwrap_or(false)); - let mut install = Installer::create(io.clone_box(), &composer); + let mut install = Installer::create(io.clone_box(), &composer_handle); - let config = std::rc::Rc::clone(composer.get_config()); + let config = composer.get_config(); let (prefer_source, prefer_dist) = self.get_preferred_install_options(&*config.borrow(), input, false)?; @@ -428,7 +432,7 @@ impl UpdateCommand { ); let mut bump_command = BumpCommand::new(None); // TODO(phase-b): Composer is a PHP class shared by reference; calling - // set_composer here requires Rc<RefCell<Composer>> shared-ownership. + // set_composer here requires a shared PartialComposer handle. // bump_command.set_composer(composer); result = bump_command.do_bump( io, @@ -459,7 +463,7 @@ impl UpdateCommand { io: &dyn IOInterface, input: &dyn InputInterface, output: &dyn OutputInterface, - composer: &Composer, + composer: &PartialComposerHandle, packages: Vec<String>, ) -> Result<Vec<String>> { if !input.is_interactive() { @@ -470,8 +474,9 @@ impl UpdateCommand { .into()); } + let composer_ref = crate::command::composer_full(composer); let platform_req_filter = self.get_platform_requirement_filter(input); - let stability_flags = composer.get_package().get_stability_flags(); + let stability_flags = composer_ref.get_package().get_stability_flags(); let requires = array_merge( // TODO(phase-b): array_merge for IndexMap<String, Link> todo!("composer.get_package().get_requires() as PhpMixed"), @@ -495,13 +500,17 @@ impl UpdateCommand { // Vec<Box<dyn PackageInterface>> while RepositoryInterface::get_packages // returns Vec<Box<dyn BasePackage>>. Use only the locker branch for now. let installed_packages: Vec<Box<dyn crate::package::PackageInterface>> = - if composer.get_locker().is_locked() { + if composer_ref.get_locker().borrow_mut().is_locked() { CanonicalPackagesTrait::get_packages( - &composer.get_locker().get_locked_repository(true)?, + &composer_ref + .get_locker() + .borrow_mut() + .get_locked_repository(true)?, ) } else { - let _ = composer + let _ = composer_ref .get_repository_manager() + .borrow() .get_local_repository() .get_packages(); Vec::new() @@ -613,7 +622,8 @@ impl UpdateCommand { .into()) } - fn create_version_selector(&self, composer: &Composer) -> Result<VersionSelector> { + fn create_version_selector(&self, composer: &PartialComposerHandle) -> Result<VersionSelector> { + let composer = crate::command::composer_full(composer); let mut repository_set = RepositorySet::new( composer.get_package().get_minimum_stability(), composer.get_package().get_stability_flags().clone(), @@ -625,7 +635,10 @@ impl UpdateCommand { ); // TODO(phase-b): array_filter requires Clone on Box<dyn RepositoryInterface> // which PHP classes must not implement. Skipping the repo filter for now. - let _ = &composer.get_repository_manager().get_repositories(); + let _ = &composer + .get_repository_manager() + .borrow() + .get_repositories(); let _ = |repository: &Box<dyn RepositoryInterface>| -> bool { repository .as_any() diff --git a/crates/shirabe/src/command/validate_command.rs b/crates/shirabe/src/command/validate_command.rs index d6ee3aa..f57c21c 100644 --- a/crates/shirabe/src/command/validate_command.rs +++ b/crates/shirabe/src/command/validate_command.rs @@ -5,7 +5,6 @@ use shirabe_external_packages::symfony::component::console::input::InputInterfac use shirabe_external_packages::symfony::component::console::output::OutputInterface; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; -use crate::composer::Composer; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::factory::Factory; @@ -157,7 +156,8 @@ impl ValidateCommand { validator.validate(&file, check_all, check_version); let mut lock_errors: Vec<String> = vec![]; - let mut composer = self.create_composer_instance(input, io, None, false, None)?; + let composer = self.create_composer_instance(input, io, None, false, None)?; + let mut composer = crate::command::composer_full_mut(&composer); let check_lock = (check_lock && composer .get_config() @@ -169,7 +169,8 @@ impl ValidateCommand { // TODO(phase-b): get_missing_requirement_info needs &package from composer while // locker holds &mut composer; cloning lock state isn't trivial. Use todo!() for the // package-arg subexpression below. - let locker = composer.get_locker_mut(); + let locker = composer.get_locker().clone(); + let mut locker = locker.borrow_mut(); if locker.is_locked() && !locker.is_fresh()? { lock_errors.push("- The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update` or `composer update <package name>`.".to_string()); } @@ -208,11 +209,13 @@ impl ValidateCommand { { let packages = composer .get_repository_manager() + .borrow() .get_local_repository() .get_packages(); for package in packages { let path = composer - .get_installation_manager_mut() + .get_installation_manager() + .borrow_mut() .get_install_path(package.as_ref()); let path = match path { Some(p) => p, diff --git a/crates/shirabe/src/composer.rs b/crates/shirabe/src/composer.rs index 1dfe5c8..9f420ef 100644 --- a/crates/shirabe/src/composer.rs +++ b/crates/shirabe/src/composer.rs @@ -1,36 +1,131 @@ //! ref: composer/src/Composer/Composer.php +//! ref: composer/src/Composer/PartialComposer.php use shirabe_external_packages::composer::pcre::Preg; use crate::autoload::AutoloadGenerator; +use crate::config::Config; use crate::downloader::DownloadManager; -use crate::package::Locker; +use crate::event_dispatcher::EventDispatcher; +use crate::installer::InstallationManager; use crate::package::archiver::ArchiveManager; -use crate::partial_composer::PartialComposer; +use crate::package::{Locker, RootPackageInterface}; use crate::plugin::PluginManager; +use crate::repository::RepositoryManager; +use crate::util::r#loop::Loop; +// TODO: change this information to Shirabe version. +pub const VERSION: &'static str = "2.9.7"; +pub const BRANCH_ALIAS_VERSION: &'static str = ""; +pub const RELEASE_DATE: &'static str = "2026-04-14 13:31:52"; +pub const SOURCE_VERSION: &'static str = ""; +pub const RUNTIME_API_VERSION: &'static str = "2.2.2"; + +pub fn get_version() -> String { + if VERSION == "@package_version@" { + return SOURCE_VERSION.to_string(); + } + if BRANCH_ALIAS_VERSION != "" && Preg::is_match("{^[a-f0-9]{40}$}", VERSION).unwrap_or(false) { + return format!("{}+{}", BRANCH_ALIAS_VERSION, VERSION); + } + VERSION.to_string() +} + +/// Internal data type corresponding to \Composer\PartialComposer. +#[derive(Debug, Default)] +pub struct PartialComposer { + global: bool, + package: Option<Box<dyn RootPackageInterface>>, + r#loop: Option<std::rc::Rc<std::cell::RefCell<Loop>>>, + repository_manager: Option<std::rc::Rc<std::cell::RefCell<RepositoryManager>>>, + installation_manager: Option<std::rc::Rc<std::cell::RefCell<InstallationManager>>>, + config: Option<std::rc::Rc<std::cell::RefCell<Config>>>, + event_dispatcher: Option<std::rc::Rc<std::cell::RefCell<EventDispatcher>>>, +} + +impl PartialComposer { + pub fn set_package(&mut self, package: Box<dyn RootPackageInterface>) { + self.package = Some(package); + } + + pub fn get_package(&self) -> &dyn RootPackageInterface { + self.package.as_deref().unwrap() + } + + pub fn set_config(&mut self, config: std::rc::Rc<std::cell::RefCell<Config>>) { + self.config = Some(config); + } + + pub fn get_config(&self) -> std::rc::Rc<std::cell::RefCell<Config>> { + self.config.as_ref().unwrap().clone() + } + + pub fn set_loop(&mut self, r#loop: std::rc::Rc<std::cell::RefCell<Loop>>) { + self.r#loop = Some(r#loop); + } + + pub fn get_loop(&self) -> std::rc::Rc<std::cell::RefCell<Loop>> { + self.r#loop.as_ref().unwrap().clone() + } + + pub fn set_repository_manager( + &mut self, + manager: std::rc::Rc<std::cell::RefCell<RepositoryManager>>, + ) { + self.repository_manager = Some(manager); + } + + pub fn get_repository_manager(&self) -> std::rc::Rc<std::cell::RefCell<RepositoryManager>> { + self.repository_manager.as_ref().unwrap().clone() + } + + pub fn set_installation_manager( + &mut self, + manager: std::rc::Rc<std::cell::RefCell<InstallationManager>>, + ) { + self.installation_manager = Some(manager); + } + + pub fn get_installation_manager(&self) -> std::rc::Rc<std::cell::RefCell<InstallationManager>> { + self.installation_manager.as_ref().unwrap().clone() + } + + pub fn set_event_dispatcher( + &mut self, + event_dispatcher: std::rc::Rc<std::cell::RefCell<EventDispatcher>>, + ) { + self.event_dispatcher = Some(event_dispatcher); + } + + pub fn get_event_dispatcher(&self) -> std::rc::Rc<std::cell::RefCell<EventDispatcher>> { + self.event_dispatcher.as_ref().unwrap().clone() + } + + pub fn is_global(&self) -> bool { + self.global + } + + pub fn set_global(&mut self) { + self.global = true; + } +} + +/// Internal data type corresponding to \Composer\Composer. #[derive(Debug)] pub struct Composer { - inner: PartialComposer, - locker: Option<Locker>, + partial: PartialComposer, + locker: Option<std::rc::Rc<std::cell::RefCell<Locker>>>, download_manager: Option<std::rc::Rc<std::cell::RefCell<DownloadManager>>>, // TODO(plugin): plugin_manager is part of the plugin API - plugin_manager: Option<Box<PluginManager>>, - autoload_generator: Option<AutoloadGenerator>, - archive_manager: Option<ArchiveManager>, + plugin_manager: Option<std::rc::Rc<std::cell::RefCell<PluginManager>>>, + autoload_generator: Option<std::rc::Rc<std::cell::RefCell<AutoloadGenerator>>>, + archive_manager: Option<std::rc::Rc<std::cell::RefCell<ArchiveManager>>>, } impl Composer { - // TODO: change this information to Shirabe version. - pub const VERSION: &'static str = "2.9.7"; - pub const BRANCH_ALIAS_VERSION: &'static str = ""; - pub const RELEASE_DATE: &'static str = "2026-04-14 13:31:52"; - pub const SOURCE_VERSION: &'static str = ""; - pub const RUNTIME_API_VERSION: &'static str = "2.2.2"; - pub fn new() -> Self { Self { - inner: PartialComposer::default(), + partial: PartialComposer::default(), locker: None, download_manager: None, plugin_manager: None, @@ -39,28 +134,12 @@ impl Composer { } } - pub fn get_version() -> String { - if Self::VERSION == "@package_version@" { - return Self::SOURCE_VERSION.to_string(); - } - if Self::BRANCH_ALIAS_VERSION != "" - && Preg::is_match("{^[a-f0-9]{40}$}", Self::VERSION).unwrap_or(false) - { - return format!("{}+{}", Self::BRANCH_ALIAS_VERSION, Self::VERSION); - } - Self::VERSION.to_string() - } - - pub fn set_locker(&mut self, locker: Locker) { + pub fn set_locker(&mut self, locker: std::rc::Rc<std::cell::RefCell<Locker>>) { self.locker = Some(locker); } - pub fn get_locker(&self) -> &Locker { - self.locker.as_ref().unwrap() - } - - pub fn get_locker_mut(&mut self) -> &mut Locker { - self.locker.as_mut().unwrap() + pub fn get_locker(&self) -> std::rc::Rc<std::cell::RefCell<Locker>> { + self.locker.as_ref().unwrap().clone() } pub fn set_download_manager( @@ -70,117 +149,415 @@ impl Composer { self.download_manager = Some(manager); } - pub fn get_download_manager(&self) -> &std::rc::Rc<std::cell::RefCell<DownloadManager>> { - self.download_manager.as_ref().unwrap() + pub fn get_download_manager(&self) -> std::rc::Rc<std::cell::RefCell<DownloadManager>> { + self.download_manager.as_ref().unwrap().clone() } - pub fn set_archive_manager(&mut self, manager: ArchiveManager) { + pub fn set_archive_manager( + &mut self, + manager: std::rc::Rc<std::cell::RefCell<ArchiveManager>>, + ) { self.archive_manager = Some(manager); } - pub fn get_archive_manager(&self) -> &ArchiveManager { - self.archive_manager.as_ref().unwrap() + pub fn get_archive_manager(&self) -> std::rc::Rc<std::cell::RefCell<ArchiveManager>> { + self.archive_manager.as_ref().unwrap().clone() } // TODO(plugin): set_plugin_manager is part of the plugin API - pub fn set_plugin_manager(&mut self, manager: PluginManager) { - self.plugin_manager = Some(Box::new(manager)); + pub fn set_plugin_manager(&mut self, manager: std::rc::Rc<std::cell::RefCell<PluginManager>>) { + self.plugin_manager = Some(manager); } // TODO(plugin): get_plugin_manager is part of the plugin API - pub fn get_plugin_manager(&self) -> &PluginManager { - self.plugin_manager.as_ref().unwrap() + pub fn get_plugin_manager(&self) -> std::rc::Rc<std::cell::RefCell<PluginManager>> { + self.plugin_manager.as_ref().unwrap().clone() } - // TODO(plugin): get_plugin_manager_mut is part of the plugin API - pub fn get_plugin_manager_mut(&mut self) -> &mut PluginManager { - self.plugin_manager.as_mut().unwrap() + pub fn set_autoload_generator( + &mut self, + autoload_generator: std::rc::Rc<std::cell::RefCell<AutoloadGenerator>>, + ) { + self.autoload_generator = Some(autoload_generator); } - pub fn set_autoload_generator(&mut self, autoload_generator: AutoloadGenerator) { - self.autoload_generator = Some(autoload_generator); + pub fn get_autoload_generator(&self) -> std::rc::Rc<std::cell::RefCell<AutoloadGenerator>> { + self.autoload_generator.as_ref().unwrap().clone() + } + + pub fn as_partial(&self) -> &PartialComposer { + &self.partial } - pub fn get_autoload_generator(&self) -> &AutoloadGenerator { - self.autoload_generator.as_ref().unwrap() + pub fn as_partial_mut(&mut self) -> &mut PartialComposer { + &mut self.partial } - pub fn get_autoload_generator_mut(&mut self) -> &mut AutoloadGenerator { - self.autoload_generator.as_mut().unwrap() + pub fn set_package(&mut self, package: Box<dyn crate::package::RootPackageInterface>) { + self.partial.set_package(package); } pub fn get_package(&self) -> &dyn crate::package::RootPackageInterface { - self.inner.get_package() + self.partial.get_package() } - pub fn get_config(&self) -> &std::rc::Rc<std::cell::RefCell<crate::config::Config>> { - self.inner.get_config() + pub fn set_config(&mut self, config: std::rc::Rc<std::cell::RefCell<crate::config::Config>>) { + self.partial.set_config(config); } - pub fn get_config_mut( + pub fn get_config(&self) -> std::rc::Rc<std::cell::RefCell<crate::config::Config>> { + self.partial.get_config() + } + + pub fn set_loop(&mut self, r#loop: std::rc::Rc<std::cell::RefCell<crate::util::r#loop::Loop>>) { + self.partial.set_loop(r#loop); + } + + pub fn get_loop(&self) -> std::rc::Rc<std::cell::RefCell<crate::util::r#loop::Loop>> { + self.partial.get_loop() + } + + pub fn set_repository_manager( &mut self, - ) -> &mut std::rc::Rc<std::cell::RefCell<crate::config::Config>> { - self.inner.get_config_mut() + manager: std::rc::Rc<std::cell::RefCell<crate::repository::RepositoryManager>>, + ) { + self.partial.set_repository_manager(manager); + } + + pub fn get_repository_manager( + &self, + ) -> std::rc::Rc<std::cell::RefCell<crate::repository::RepositoryManager>> { + self.partial.get_repository_manager() } - pub fn get_repository_manager(&self) -> &crate::repository::RepositoryManager { - self.inner.get_repository_manager() + pub fn set_installation_manager( + &mut self, + manager: std::rc::Rc<std::cell::RefCell<crate::installer::InstallationManager>>, + ) { + self.partial.set_installation_manager(manager); + } + + pub fn get_installation_manager( + &self, + ) -> std::rc::Rc<std::cell::RefCell<crate::installer::InstallationManager>> { + self.partial.get_installation_manager() } pub fn set_event_dispatcher( &mut self, dispatcher: std::rc::Rc<std::cell::RefCell<crate::event_dispatcher::EventDispatcher>>, ) { - self.inner.set_event_dispatcher(dispatcher); + self.partial.set_event_dispatcher(dispatcher); } pub fn get_event_dispatcher( &self, - ) -> &std::rc::Rc<std::cell::RefCell<crate::event_dispatcher::EventDispatcher>> { - self.inner.get_event_dispatcher() + ) -> std::rc::Rc<std::cell::RefCell<crate::event_dispatcher::EventDispatcher>> { + self.partial.get_event_dispatcher() + } + + pub fn is_global(&self) -> bool { + self.partial.is_global() } - pub fn get_installation_manager(&self) -> &crate::installer::InstallationManager { - self.inner.get_installation_manager() + pub fn set_global(&mut self) { + self.partial.set_global(); } +} + +#[derive(Debug)] +pub enum PartialOrFullComposer { + Full(Composer), + Partial(PartialComposer), +} - pub fn get_installation_manager_mut(&mut self) -> &mut crate::installer::InstallationManager { - self.inner.get_installation_manager_mut() +impl PartialOrFullComposer { + pub fn new_full() -> Self { + Self::Full(Composer::new()) } - pub fn get_loop(&self) -> &std::rc::Rc<std::cell::RefCell<crate::util::r#loop::Loop>> { - self.inner.get_loop() + pub fn new_partial() -> Self { + Self::Partial(PartialComposer::default()) } - pub fn set_loop(&mut self, r#loop: std::rc::Rc<std::cell::RefCell<crate::util::r#loop::Loop>>) { - self.inner.set_loop(r#loop); + pub fn is_full(&self) -> bool { + matches!(self, Self::Full(_)) + } + + pub fn is_partial(&self) -> bool { + matches!(self, Self::Partial(_)) + } + + pub fn as_full(&self) -> Option<&Composer> { + match self { + Self::Full(full) => Some(full), + Self::Partial(_) => None, + } + } + + pub fn as_full_mut(&mut self) -> Option<&mut Composer> { + match self { + Self::Full(full) => Some(full), + Self::Partial(_) => None, + } + } + + pub fn as_partial(&self) -> &PartialComposer { + match self { + Self::Full(full) => full.as_partial(), + Self::Partial(partial) => partial, + } + } + + pub fn as_partial_mut(&mut self) -> &mut PartialComposer { + match self { + Self::Full(full) => full.as_partial_mut(), + Self::Partial(partial) => partial, + } + } + + pub fn set_package(&mut self, package: Box<dyn crate::package::RootPackageInterface>) { + match self { + Self::Full(full) => full.set_package(package), + Self::Partial(partial) => partial.set_package(package), + } + } + + pub fn get_package(&self) -> &dyn crate::package::RootPackageInterface { + match self { + Self::Full(full) => full.get_package(), + Self::Partial(partial) => partial.get_package(), + } } pub fn set_config(&mut self, config: std::rc::Rc<std::cell::RefCell<crate::config::Config>>) { - self.inner.set_config(config); + match self { + Self::Full(full) => full.set_config(config), + Self::Partial(partial) => partial.set_config(config), + } } - pub fn set_global(&mut self) { - self.inner.set_global(); + pub fn get_config(&self) -> std::rc::Rc<std::cell::RefCell<crate::config::Config>> { + match self { + Self::Full(full) => full.get_config(), + Self::Partial(partial) => partial.get_config(), + } } - pub fn set_repository_manager(&mut self, manager: crate::repository::RepositoryManager) { - self.inner.set_repository_manager(manager); + pub fn set_loop(&mut self, r#loop: std::rc::Rc<std::cell::RefCell<crate::util::r#loop::Loop>>) { + match self { + Self::Full(full) => full.set_loop(r#loop), + Self::Partial(partial) => partial.set_loop(r#loop), + } + } + + pub fn get_loop(&self) -> std::rc::Rc<std::cell::RefCell<crate::util::r#loop::Loop>> { + match self { + Self::Full(full) => full.get_loop(), + Self::Partial(partial) => partial.get_loop(), + } + } + + pub fn set_repository_manager( + &mut self, + manager: std::rc::Rc<std::cell::RefCell<crate::repository::RepositoryManager>>, + ) { + match self { + Self::Full(full) => full.set_repository_manager(manager), + Self::Partial(partial) => partial.set_repository_manager(manager), + } } - pub fn set_installation_manager(&mut self, manager: crate::installer::InstallationManager) { - self.inner.set_installation_manager(manager); + pub fn get_repository_manager( + &self, + ) -> std::rc::Rc<std::cell::RefCell<crate::repository::RepositoryManager>> { + match self { + Self::Full(full) => full.get_repository_manager(), + Self::Partial(partial) => partial.get_repository_manager(), + } + } + + pub fn set_installation_manager( + &mut self, + manager: std::rc::Rc<std::cell::RefCell<crate::installer::InstallationManager>>, + ) { + match self { + Self::Full(full) => full.set_installation_manager(manager), + Self::Partial(partial) => partial.set_installation_manager(manager), + } + } + + pub fn get_installation_manager( + &self, + ) -> std::rc::Rc<std::cell::RefCell<crate::installer::InstallationManager>> { + match self { + Self::Full(full) => full.get_installation_manager(), + Self::Partial(partial) => partial.get_installation_manager(), + } + } + + pub fn set_event_dispatcher( + &mut self, + dispatcher: std::rc::Rc<std::cell::RefCell<crate::event_dispatcher::EventDispatcher>>, + ) { + match self { + Self::Full(full) => full.set_event_dispatcher(dispatcher), + Self::Partial(partial) => partial.set_event_dispatcher(dispatcher), + } + } + + pub fn get_event_dispatcher( + &self, + ) -> std::rc::Rc<std::cell::RefCell<crate::event_dispatcher::EventDispatcher>> { + match self { + Self::Full(full) => full.get_event_dispatcher(), + Self::Partial(partial) => partial.get_event_dispatcher(), + } } pub fn is_global(&self) -> bool { - self.inner.is_global() + match self { + Self::Full(full) => full.is_global(), + Self::Partial(partial) => partial.is_global(), + } + } + + pub fn set_global(&mut self) { + match self { + Self::Full(full) => full.set_global(), + Self::Partial(partial) => partial.set_global(), + } } +} + +/// Shared reference to \Composer\PartialComposer or \Composer\Composer. +/// Use this for parameters or fields typed as \Composer\PartialComposer in PHP. +#[derive(Debug, Clone)] +pub struct PartialComposerHandle(std::rc::Rc<std::cell::RefCell<PartialOrFullComposer>>); - pub fn as_partial(&self) -> &crate::partial_composer::PartialComposer { - &self.inner +impl PartialComposerHandle { + pub fn borrow_partial(&self) -> std::cell::Ref<'_, PartialComposer> { + std::cell::Ref::map(self.0.borrow(), |c| c.as_partial()) } - pub fn set_package(&mut self, package: Box<dyn crate::package::RootPackageInterface>) { - self.inner.set_package(package); + pub fn borrow_partial_mut(&self) -> std::cell::RefMut<'_, PartialComposer> { + std::cell::RefMut::map(self.0.borrow_mut(), |c| c.as_partial_mut()) + } + + pub fn is_full(&self) -> bool { + self.0.borrow().is_full() } + + /// Downcast to a full Composer handle. PHP `$composer instanceof Composer`. + pub fn as_full(&self) -> Option<ComposerHandle> { + if self.0.borrow().is_full() { + Some(ComposerHandle::from_rc_unchecked(std::rc::Rc::clone( + &self.0, + ))) + } else { + None + } + } + + pub fn downgrade(&self) -> PartialComposerWeakHandle { + PartialComposerWeakHandle(std::rc::Rc::downgrade(&self.0)) + } + + pub fn from_rc(rc: std::rc::Rc<std::cell::RefCell<PartialOrFullComposer>>) -> Self { + Self(rc) + } + + pub fn as_rc(&self) -> &std::rc::Rc<std::cell::RefCell<PartialOrFullComposer>> { + &self.0 + } +} + +/// Shared weak reference to \Composer\PartialComposer or \Composer\Composer. +#[derive(Debug, Clone)] +pub struct PartialComposerWeakHandle(std::rc::Weak<std::cell::RefCell<PartialOrFullComposer>>); + +impl PartialComposerWeakHandle { + pub fn upgrade(&self) -> Option<PartialComposerHandle> { + self.0.upgrade().map(PartialComposerHandle) + } + + pub fn from_weak(weak: std::rc::Weak<std::cell::RefCell<PartialOrFullComposer>>) -> Self { + Self(weak) + } +} + +/// Shared reference to \Composer\Composer. +/// Use this for parameters or fields typed as \Composer\Composer in PHP. +#[derive(Debug, Clone)] +pub struct ComposerHandle(std::rc::Rc<std::cell::RefCell<PartialOrFullComposer>>); + +impl ComposerHandle { + pub fn borrow(&self) -> std::cell::Ref<'_, Composer> { + std::cell::Ref::map(self.0.borrow(), |c| { + c.as_full() + .expect("Composer handle invariant: inner is Full") + }) + } + + pub fn borrow_mut(&self) -> std::cell::RefMut<'_, Composer> { + std::cell::RefMut::map(self.0.borrow_mut(), |c| { + c.as_full_mut() + .expect("Composer handle invariant: inner is Full") + }) + } + + pub fn upcast(&self) -> PartialComposerHandle { + PartialComposerHandle::from_rc(std::rc::Rc::clone(&self.0)) + } + + pub fn downgrade(&self) -> ComposerWeakHandle { + ComposerWeakHandle(std::rc::Rc::downgrade(&self.0)) + } + + pub fn from_rc_unchecked(rc: std::rc::Rc<std::cell::RefCell<PartialOrFullComposer>>) -> Self { + Self(rc) + } + + pub fn as_rc(&self) -> &std::rc::Rc<std::cell::RefCell<PartialOrFullComposer>> { + &self.0 + } +} + +impl From<ComposerHandle> for PartialComposerHandle { + fn from(c: ComposerHandle) -> Self { + c.upcast() + } +} + +/// Shared weak reference to \Composer\Composer. +#[derive(Debug, Clone)] +pub struct ComposerWeakHandle(std::rc::Weak<std::cell::RefCell<PartialOrFullComposer>>); + +impl ComposerWeakHandle { + pub fn upgrade(&self) -> Option<ComposerHandle> { + self.0.upgrade().map(ComposerHandle) + } + + pub fn from_weak(weak: std::rc::Weak<std::cell::RefCell<PartialOrFullComposer>>) -> Self { + Self(weak) + } +} + +/// Borrows a polymorphic `PartialComposer` as a fully-loaded `Composer`. +/// +/// Commands obtain their Composer through `require_composer` / `create_composer_instance`, +/// which always yield a fully-loaded instance, so the downcast is infallible here. +pub fn composer_full(composer: &PartialComposerHandle) -> std::cell::Ref<'_, Composer> { + std::cell::Ref::map(composer.as_rc().borrow(), |c| { + c.as_full() + .expect("a fully loaded Composer is required here") + }) +} + +/// Mutably borrows a polymorphic `PartialComposer` as a fully-loaded `Composer`. See [`composer_full`]. +pub fn composer_full_mut(composer: &PartialComposerHandle) -> std::cell::RefMut<'_, Composer> { + std::cell::RefMut::map(composer.as_rc().borrow_mut(), |c| { + c.as_full_mut() + .expect("a fully loaded Composer is required here") + }) } diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs index 9239bc6..4bf9197 100644 --- a/crates/shirabe/src/console/application.rs +++ b/crates/shirabe/src/console/application.rs @@ -66,7 +66,9 @@ use crate::command::StatusCommand; use crate::command::SuggestsCommand; use crate::command::UpdateCommand; use crate::command::ValidateCommand; -use crate::composer::Composer; +use crate::composer; +use crate::composer::ComposerHandle; +use crate::composer::PartialComposerHandle; use crate::console::GithubActionError; use crate::downloader::TransportException; use crate::event_dispatcher::ScriptExecutionException; @@ -86,7 +88,7 @@ use crate::util::Silencer; #[derive(Debug)] pub struct Application { inner: BaseApplication, - pub(crate) composer: Option<Composer>, + pub(crate) composer: Option<PartialComposerHandle>, pub(crate) io: Box<dyn IOInterface>, has_plugin_commands: bool, disable_plugins_by_default: bool, @@ -108,7 +110,7 @@ impl Application { // PHP: static $shutdownRegistered = false; — register only once globally static SHUTDOWN_REGISTERED: std::sync::OnceLock<()> = std::sync::OnceLock::new(); if version == "" { - version = Composer::get_version(); + version = composer::get_version(); } if function_exists("ini_set") && extension_loaded("xdebug") { ini_set("xdebug.show_exception_trace", "0"); @@ -495,8 +497,8 @@ impl Application { &sprintf( "Running %s (%s) with %s on %s", &[ - Composer::get_version().into(), - Composer::RELEASE_DATE.into(), + composer::get_version().into(), + composer::RELEASE_DATE.into(), (if defined("HHVM_VERSION") { format!("HHVM {}", shirabe_php_shim::HHVM_VERSION.unwrap_or("")) } else { @@ -629,8 +631,10 @@ impl Application { .unwrap_or_default(); if let Some(composer) = self.get_composer(false, None, None)? { + let composer = crate::command::composer_full(&composer); let root_package = composer.get_package(); - let generator = composer.get_autoload_generator(); + let generator = composer.get_autoload_generator().clone(); + let generator = generator.borrow(); // TODO(phase-b): build_package_map needs &mut InstallationManager // but get_composer returns &Composer; skip until shared ownership is settled. @@ -848,6 +852,7 @@ impl Application { let composer = self.get_composer(false, Some(true), None)?; if composer.is_some() && function_exists("disk_free_space") { let composer = composer.unwrap(); + let composer = composer.borrow_partial(); let config = composer.get_config(); let min_space_free: f64 = 100.0 * 1024.0 * 1024.0; @@ -979,7 +984,7 @@ impl Application { required: bool, disable_plugins: Option<bool>, disable_scripts: Option<bool>, - ) -> anyhow::Result<Option<&Composer>> { + ) -> anyhow::Result<Option<PartialComposerHandle>> { let disable_plugins = disable_plugins.unwrap_or(self.disable_plugins_by_default); let disable_scripts = disable_scripts.unwrap_or(self.disable_scripts_by_default); @@ -1020,7 +1025,7 @@ impl Application { } } - Ok(self.composer.as_ref()) + Ok(self.composer.clone()) } /// Removes the cached composer instance @@ -1065,12 +1070,12 @@ impl Application { pub fn get_long_version(&self) -> String { let mut branch_alias_string = String::new(); - if !Composer::BRANCH_ALIAS_VERSION.is_empty() - && Composer::BRANCH_ALIAS_VERSION != "@package_branch_alias_version@" + if !composer::BRANCH_ALIAS_VERSION.is_empty() + && composer::BRANCH_ALIAS_VERSION != "@package_branch_alias_version@" { branch_alias_string = sprintf( " (%s)", - &[Composer::BRANCH_ALIAS_VERSION.to_string().into()], + &[composer::BRANCH_ALIAS_VERSION.to_string().into()], ); } @@ -1080,7 +1085,7 @@ impl Application { self.inner.get_name().into(), self.inner.get_version().into(), branch_alias_string.into(), - Composer::RELEASE_DATE.into(), + composer::RELEASE_DATE.into(), ], ) } diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs index afda00c..f4575ce 100644 --- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs +++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs @@ -19,7 +19,8 @@ use shirabe_php_shim::{ }; use crate::autoload::ClassLoader; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; +use crate::composer::PartialComposerWeakHandle; use crate::dependency_resolver::Transaction; use crate::dependency_resolver::operation::OperationInterface; use crate::event_dispatcher::Event; @@ -31,7 +32,6 @@ use crate::installer::PackageEvent; use crate::io::ConsoleIO; use crate::io::IOInterface; use crate::package::PackageInterface; -use crate::partial_composer::PartialComposer; use crate::plugin::CommandEvent; use crate::plugin::PreCommandRunEvent; use crate::repository::RepositoryInterface; @@ -62,7 +62,7 @@ pub enum Callable { /// `$dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD);` #[derive(Debug)] pub struct EventDispatcher { - pub(crate) composer: Box<PartialComposer>, + pub(crate) composer: PartialComposerWeakHandle, pub(crate) io: Box<dyn IOInterface>, pub(crate) loader: Option<ClassLoader>, pub(crate) process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, @@ -76,7 +76,7 @@ pub struct EventDispatcher { impl EventDispatcher { pub fn new( - composer: PartialComposer, + composer: PartialComposerWeakHandle, io: Box<dyn IOInterface>, process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>, ) -> Self { @@ -92,7 +92,7 @@ impl EventDispatcher { .filter(|val| val != "") .collect(); Self { - composer: Box::new(composer), + composer, io, loader: None, process, @@ -144,10 +144,15 @@ impl EventDispatcher { additional_args: Vec<String>, flags: IndexMap<String, PhpMixed>, ) -> anyhow::Result<i64> { - let composer = self.composer_as_full_or_panic(); + let composer = self.composer(); + assert!( + composer.is_full(), + "This should only be reached with a fully loaded Composer" + ); + let event = ScriptEvent::new( event_name.to_string(), - composer, + composer.as_full().expect("checked above").downgrade(), self.io_clone(), dev_mode, additional_args, @@ -165,10 +170,15 @@ impl EventDispatcher { operations: Vec<Box<dyn OperationInterface>>, operation: Box<dyn OperationInterface>, ) -> anyhow::Result<i64> { - let composer = self.composer_as_full_or_panic(); + let composer = self.composer(); + assert!( + composer.is_full(), + "This should only be reached with a fully loaded Composer" + ); + let event = PackageEvent::new( event_name.to_string(), - composer, + composer.as_full().expect("checked above").downgrade(), self.io_clone(), dev_mode, local_repo, @@ -186,10 +196,15 @@ impl EventDispatcher { execute_operations: bool, transaction: Transaction, ) -> anyhow::Result<i64> { - let composer = self.composer_as_full_or_panic(); + let composer = self.composer(); + assert!( + composer.is_full(), + "This should only be reached with a fully loaded Composer" + ); + let event = InstallerEvent::new( event_name.to_string(), - composer, + composer.as_full().expect("checked above").downgrade(), self.io_clone(), dev_mode, execute_operations, @@ -438,10 +453,14 @@ impl EventDispatcher { ), true, crate::io::QUIET); } - let composer_full = self.composer_as_full_or_panic(); + // TODO(plugin): reached only with a fully loaded Composer (script dispatch asserts full upstream). + let composer = self.composer(); let mut script_event = ScriptEvent::new( script_name.clone(), - composer_full, + composer + .as_full() + .expect("script dispatch requires a fully loaded Composer") + .downgrade(), self.io_clone(), // event.isDevMode() is only on InstallerEvent/ScriptEvent/PackageEvent // TODO(plugin): proper dev_mode propagation when polymorphic event is supported @@ -678,7 +697,13 @@ impl EventDispatcher { ); } - let possible_local_binaries = self.composer.get_package().get_binaries(); + let possible_local_binaries = self + .composer + .upgrade() + .expect("Composer was dropped before EventDispatcher use") + .borrow_partial() + .get_package() + .get_binaries(); if !possible_local_binaries.is_empty() { for local_exec in &possible_local_binaries { if Preg::is_match( @@ -1052,7 +1077,9 @@ impl EventDispatcher { /// Finds all listeners defined as scripts in the package fn get_script_listeners(&self, event: &Event) -> Vec<Callable> { - let package = self.composer.get_package(); + let composer = self.composer(); + let composer = composer.borrow_partial(); + let package = composer.get_package(); let scripts = package.get_scripts(); // TODO(phase-b): RootPackage::get_scripts() returns Vec<String> per event; @@ -1132,7 +1159,8 @@ impl EventDispatcher { // add the bin dir to the PATH to make local binaries of deps usable in scripts let bin_dir = self - .composer + .composer() + .borrow_partial() .get_config() .borrow_mut() .get("bin-dir") @@ -1198,15 +1226,12 @@ impl EventDispatcher { } fn make_autoloader(&mut self, event: &Event, callable: &Callable) { + let composer = self.composer(); // TODO(plugin): full autoloader rebuild on plugin-supplied callables — currently a stub. - // TODO(phase-b): composer_as_full() returns Option<&Composer> borrowed from &self, - // which conflicts with &mut self updates further down (previous_listeners, - // previous_hash, loader). Resolve when Composer ownership is shared. For now, - // short-circuit before any mutable use and fabricate the rest via todo!(). - if self.composer_as_full().is_none() { + let Some(composer) = composer.as_full() else { return; - } - let composer: &Composer = todo!("composer_as_full borrows &self; needs shared ownership"); + }; + let composer = composer.borrow_mut(); let callable_key = match callable { Callable::ArrayCallable(first, method) => { @@ -1228,9 +1253,11 @@ impl EventDispatcher { let package = composer.get_package(); let packages = composer .get_repository_manager() + .borrow() .get_local_repository() .get_canonical_packages(); - let mut generator = composer.get_autoload_generator(); + let generator = composer.get_autoload_generator().clone(); + let generator = generator.borrow(); let mut hash_input = packages .iter() .map(|p: &Box<dyn PackageInterface>| format!("{}/{}", p.get_name(), p.get_version())) @@ -1249,7 +1276,7 @@ impl EventDispatcher { // TODO(phase-b): build_package_map needs &mut InstallationManager and returns Result; // Composer is &Composer here so we cannot take a mut borrow. Defer until shared ownership. - let _ = generator; + let _ = &generator; let _ = packages; let package_map: Vec<(Box<dyn PackageInterface>, Option<String>)> = todo!("build_package_map requires &mut InstallationManager"); @@ -1280,22 +1307,10 @@ impl EventDispatcher { todo!("clone Box<dyn IOInterface>") } - fn composer_as_full(&self) -> Option<&Composer> { - // TODO(phase-b): PartialComposer ↔ Composer downcasting requires Phase B design. - None - } - - fn composer_as_full_or_panic(&self) -> Composer { - // assert($this->composer instanceof Composer, ...) - assert!( - self.composer_as_full().is_some(), - "This should only be reached with a fully loaded Composer" - ); - let _ = LogicException { - message: "This should only be reached with a fully loaded Composer".to_string(), - code: 0, - }; - todo!("clone Composer out of PartialComposer in Phase B") + fn composer(&self) -> PartialComposerHandle { + self.composer + .upgrade() + .expect("EventDispatcher must lives longer than Composer") } fn is_empty_value(value: &PhpMixed) -> bool { diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index 194a94f..3e26416 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -15,7 +15,8 @@ use shirabe_php_shim::{ use crate::autoload::AutoloadGenerator; use crate::cache::Cache; -use crate::composer::Composer; +use crate::composer::{ComposerWeakHandle, PartialOrFullComposer}; +use crate::composer::{PartialComposerHandle, PartialComposerWeakHandle}; use crate::config::Config; use crate::config::JsonConfigSource; use crate::downloader::DownloadManager; @@ -52,7 +53,6 @@ use crate::package::archiver::ZipArchiver; use crate::package::loader::RootPackageLoader; use crate::package::version::VersionGuesser; use crate::package::version::VersionParser; -use crate::partial_composer::PartialComposer; use crate::plugin::PluginEvents; use crate::plugin::PluginManager; use crate::repository::FilesystemRepository; @@ -438,7 +438,7 @@ impl Factory { cwd: Option<&str>, full_load: bool, disable_scripts: bool, - ) -> anyhow::Result<PartialComposerOrComposer> { + ) -> anyhow::Result<PartialComposerHandle> { // 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()); @@ -573,162 +573,178 @@ impl Factory { let config = std::rc::Rc::new(std::cell::RefCell::new(config)); // initialize composer - let mut composer: PartialComposerOrComposer = if full_load { - PartialComposerOrComposer::Full(Composer::new()) - } else { - PartialComposerOrComposer::Partial(PartialComposer::default()) - }; - composer.set_config(std::rc::Rc::clone(&config)); - if is_global { - composer.set_global(); - } + // + // Phase C: build the whole Composer graph at once with Rc::new_cyclic so that + // back-references (the EventDispatcher's composer, etc.) can hold a + // 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(std::rc::Rc::clone(&config)); + 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())?; + 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); - } - } - } + // 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, &config, IndexMap::new())?, - )); - let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(Some( - io.clone_box(), - )))); - let r#loop = std::rc::Rc::new(std::cell::RefCell::new(Loop::new( - std::rc::Rc::clone(&http_downloader), - Some(std::rc::Rc::clone(&process)), - ))); - composer.set_loop(r#loop.clone()); + let http_downloader = std::rc::Rc::new(std::cell::RefCell::new( + Self::create_http_downloader(io, &config, IndexMap::new())?, + )); + let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new( + Some(io.clone_box()), + ))); + let r#loop = std::rc::Rc::new(std::cell::RefCell::new(Loop::new( + std::rc::Rc::clone(&http_downloader), + Some(std::rc::Rc::clone(&process)), + ))); + composer.set_loop(r#loop.clone()); - // initialize event dispatcher - let dispatcher = { - let mut d = EventDispatcher::new( - composer.as_partial(), - io.clone_box(), - Some(std::rc::Rc::clone(&process)), - ); - d.set_run_scripts(!disable_scripts); - std::rc::Rc::new(std::cell::RefCell::new(d)) - }; - composer.set_event_dispatcher(std::rc::Rc::clone(&dispatcher)); + // initialize event dispatcher with the Composer back-reference + let dispatcher = { + let mut d = EventDispatcher::new( + PartialComposerWeakHandle::from_weak(composer_weak.clone()), + io.clone_box(), + Some(std::rc::Rc::clone(&process)), + ); + d.set_run_scripts(!disable_scripts); + std::rc::Rc::new(std::cell::RefCell::new(d)) + }; + composer.set_event_dispatcher(std::rc::Rc::clone(&dispatcher)); - // initialize repository manager - let mut rm = RepositoryFactory::manager( - io, - &config, - Some(std::rc::Rc::clone(&http_downloader)), - Some(std::rc::Rc::clone(&dispatcher)), - Some(std::rc::Rc::clone(&process)), - )?; + // initialize repository manager + let rm = std::rc::Rc::new(std::cell::RefCell::new(RepositoryFactory::manager( + io, + &config, + Some(std::rc::Rc::clone(&http_downloader)), + Some(std::rc::Rc::clone(&dispatcher)), + Some(std::rc::Rc::clone(&process)), + )?)); - // 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())); - } + // 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( - std::rc::Rc::clone(&config), - std::rc::Rc::clone(&process), - parser.clone(), - Some(io.clone_box()), - ); - // TODO(phase-b): RepositoryManager is a PHP class — both composer.set_repository_manager() - // and self.load_root_package() want ownership. Use a placeholder rm for the loader. - let mut loader = self.load_root_package( - todo!("share RepositoryManager via Rc<RefCell<>>"), - std::rc::Rc::clone(&config), - parser, - guesser, - io.clone_box(), - ); - 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 package + let parser = VersionParser::new(); + let guesser = VersionGuesser::new( + std::rc::Rc::clone(&config), + std::rc::Rc::clone(&process), + parser.clone(), + Some(io.clone_box()), + ); + let mut loader = self.load_root_package( + std::rc::Rc::clone(&rm), + std::rc::Rc::clone(&config), + parser, + guesser, + io.clone_box(), + ); + 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, - &mut rm, - &vendor_dir, - composer.get_package(), - Some(&process), - ); - composer.set_repository_manager(rm); + // load local repository + self.add_local_repository( + io, + &mut rm.borrow_mut(), + &vendor_dir, + composer.get_package(), + Some(&process), + ); + composer.set_repository_manager(std::rc::Rc::clone(&rm)); - // initialize installation manager - let im = self.create_installation_manager( - r#loop.clone(), - io.clone_box(), - Some(std::rc::Rc::clone(&dispatcher)), - ); - // TODO(phase-b): set_installation_manager takes ownership; im needs sharing for create_default_installers - composer.set_installation_manager(im); + // initialize installation manager + let im = std::rc::Rc::new(std::cell::RefCell::new( + self.create_installation_manager( + r#loop.clone(), + io.clone_box(), + Some(std::rc::Rc::clone(&dispatcher)), + ), + )); + composer.set_installation_manager(std::rc::Rc::clone(&im)); - if let PartialComposerOrComposer::Full(ref mut composer_full) = composer { - // initialize download manager - let dm = self.create_download_manager( - io, - &config, - &http_downloader, - &process, - Some(&dispatcher), - )?; - composer_full.set_download_manager(dm.clone()); + if let PartialOrFullComposer::Full(ref mut composer_full) = composer { + // initialize download manager + let dm = self.create_download_manager( + io, + &config, + &http_downloader, + &process, + Some(&dispatcher), + )?; + composer_full.set_download_manager(dm.clone()); - // initialize autoload generator - let generator = - AutoloadGenerator::new(std::rc::Rc::clone(&dispatcher), Some(io.clone_box())); - composer_full.set_autoload_generator(generator); + // initialize autoload generator + let generator = AutoloadGenerator::new( + std::rc::Rc::clone(&dispatcher), + Some(io.clone_box()), + ); + composer_full.set_autoload_generator(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(am); - } + // 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, Some(&process)); + // 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, Some(&process)); - // init locker if possible - if let PartialComposerOrComposer::Full(ref mut composer_full) = composer { - if let Some(ref composer_file_path) = composer_file { - let lock_file = Self::get_lock_file(composer_file_path); - let lock_enabled = config - .borrow_mut() - .get("lock") - .and_then(|v| v.as_bool()) - .unwrap_or(true); - if !lock_enabled && file_exists(&lock_file) { - io.write_error3( + // init locker if possible + if let PartialOrFullComposer::Full(ref mut composer_full) = composer { + if let Some(ref composer_file_path) = composer_file { + let lock_file = Self::get_lock_file(composer_file_path); + let lock_enabled = config + .borrow_mut() + .get("lock") + .and_then(|v| v.as_bool()) + .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 @@ -736,79 +752,101 @@ impl Factory { true, crate::io::NORMAL, ); - } + } - // TODO(phase-b): InstallationManager is a PHP class — needs Rc<RefCell<>> sharing - let locker = Locker::new( - io.clone_box(), - JsonFile::new( - if lock_enabled { - lock_file + let locker = Locker::new( + io.clone_box(), + JsonFile::new( + if lock_enabled { + lock_file + } else { + Platform::get_dev_null() + }, + None, + Some(io.clone_box()), + )?, + std::rc::Rc::clone(&im), + &file_get_contents(composer_file_path).unwrap_or_default(), + std::rc::Rc::clone(&process), + ); + composer_full + .set_locker(std::rc::Rc::new(std::cell::RefCell::new(locker))); } else { - Platform::get_dev_null() - }, - None, - Some(io.clone_box()), - )?, - todo!("InstallationManager clone"), - &file_get_contents(composer_file_path).unwrap_or_default(), - std::rc::Rc::clone(&process), - ); - composer_full.set_locker(locker); - } else { - let lock_contents = JsonFile::encode( - &PhpMixed::Array( - local_config_data - .iter() - .map(|(k, v)| (k.clone(), Box::new(v.clone()))) - .collect(), - ), - 448, - ); - // TODO(phase-b): InstallationManager is a PHP class — needs Rc<RefCell<>> sharing - let locker = Locker::new( - io.clone_box(), - JsonFile::new(Platform::get_dev_null(), None, Some(io.clone_box()))?, - todo!("InstallationManager clone"), - &lock_contents, - std::rc::Rc::clone(&process), - ); - composer_full.set_locker(locker); - } - } + let lock_contents = JsonFile::encode( + &PhpMixed::Array( + local_config_data + .iter() + .map(|(k, v)| (k.clone(), Box::new(v.clone()))) + .collect(), + ), + 448, + ); + let locker = Locker::new( + io.clone_box(), + JsonFile::new( + Platform::get_dev_null(), + None, + Some(io.clone_box()), + )?, + std::rc::Rc::clone(&im), + &lock_contents, + std::rc::Rc::clone(&process), + ); + composer_full + .set_locker(std::rc::Rc::new(std::cell::RefCell::new(locker))); + } + } - if let PartialComposerOrComposer::Full(ref mut composer_full) = composer { - let mut global_composer: Option<PartialComposer> = None; - if !composer_full.is_global() { - global_composer = self.create_global_composer( - io, - &*config.borrow(), - disable_plugins, - disable_scripts, - false, - ); - } + if let Some(full_composer) = composer.as_full_mut() { + let global_composer = if !full_composer.is_global() { + self.create_global_composer( + io, + &*config.borrow(), + disable_plugins, + disable_scripts, + false, + ) + } else { + None + }; + let mut pm = self.create_plugin_manager( + io, + ComposerWeakHandle::from_weak(composer_weak.clone()), + global_composer, + disable_plugins, + ); + if full_composer.is_global() { + pm.set_running_in_global_dir(true); + } + pm.load_installed_plugins(); + full_composer + .set_plugin_manager(std::rc::Rc::new(std::cell::RefCell::new(pm))); + } - let mut pm = self.create_plugin_manager( - io, - composer_full, - global_composer.as_ref(), - disable_plugins, - ); - // TODO(phase-b): PluginManager is a PHP class; sharing pm before transferring requires Rc<RefCell<>> - if composer_full.is_global() { - pm.set_running_in_global_dir(true); - } - pm.load_installed_plugins(); - composer_full.set_plugin_manager(pm); + Ok(composer) + }; + match build() { + Ok(composer) => std::cell::RefCell::new(composer), + Err(e) => { + build_error = Some(e); + std::cell::RefCell::new(PartialOrFullComposer::new_partial()) + } + } + }, + ); + if let Some(e) = build_error { + return Err(e); } if full_load { + // The back-reference is now upgradeable, so dispatching the INIT event (which may read + // the Composer through the dispatcher) is safe only after the Rc has been constructed. let init_event = Event::from_name(PluginEvents::INIT.to_string()); - composer - .get_event_dispatcher() + let init_event_name = init_event.get_name().to_string(); + let dispatcher = composer.borrow().get_event_dispatcher(); + dispatcher .borrow_mut() - .dispatch(Some(init_event.get_name()), Some(init_event))?; + .dispatch(Some(&init_event_name), Some(init_event))?; // once everything is initialized we can // purge packages from local repos if they have been deleted on the filesystem @@ -816,22 +854,18 @@ impl Factory { // self.purge_packages(rm.get_local_repository(), &mut im)?; } - Ok(composer) + Ok(PartialComposerHandle::from_rc(composer)) } pub fn create_global( io: &dyn IOInterface, disable_plugins: DisablePlugins, disable_scripts: bool, - ) -> Option<Composer> { + ) -> Option<PartialComposerHandle> { let factory = Self; let config = Self::create_config(Some(io), None).ok()?; - factory - .create_global_composer(io, &config, disable_plugins, disable_scripts, true) - .and_then(|pc| match pc { - _ => None, // TODO(phase-b): downcast PartialComposer to Composer when fullLoad=true - }) + factory.create_global_composer(io, &config, disable_plugins, disable_scripts, true) } fn add_local_repository( @@ -871,7 +905,7 @@ impl Factory { disable_plugins: DisablePlugins, disable_scripts: bool, full_load: bool, - ) -> Option<PartialComposer> { + ) -> Option<PartialComposerHandle> { // make sure if disable plugins was 'local' it is now turned off let disable_plugins = if matches!( disable_plugins, @@ -882,7 +916,7 @@ impl Factory { DisablePlugins::None }; - let composer = match self.create_composer( + match self.create_composer( io, Some(LocalConfigInput::Path(format!( "{}/composer.json", @@ -893,7 +927,7 @@ impl Factory { full_load, disable_scripts, ) { - Ok(c) => Some(c.into_partial()), + Ok(c) => Some(c), Err(e) => { io.write_error3( &format!("Failed to initialize global composer: {}", e), @@ -902,9 +936,7 @@ impl Factory { ); None } - }; - - composer + } } pub fn create_download_manager( @@ -1135,8 +1167,8 @@ impl Factory { fn create_plugin_manager( &self, io: &dyn IOInterface, - composer: &Composer, - global_composer: Option<&PartialComposer>, + composer: ComposerWeakHandle, + global_composer: Option<PartialComposerHandle>, disable_plugins: DisablePlugins, ) -> PluginManager { // TODO(phase-b): PluginManager::new takes ownership of Composer/PartialComposer; PHP @@ -1156,8 +1188,8 @@ impl Factory { fn create_default_installers( &self, - im: &InstallationManager, - composer: &PartialComposerOrComposer, + im: &std::rc::Rc<std::cell::RefCell<InstallationManager>>, + composer: &PartialOrFullComposer, io: &dyn IOInterface, process: Option<&std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>, ) { @@ -1215,7 +1247,7 @@ impl Factory { fn load_root_package( &self, - rm: RepositoryManager, + rm: std::rc::Rc<std::cell::RefCell<RepositoryManager>>, config: std::rc::Rc<std::cell::RefCell<Config>>, parser: VersionParser, guesser: VersionGuesser, @@ -1229,7 +1261,7 @@ impl Factory { config: Option<LocalConfigInput>, disable_plugins: DisablePlugins, disable_scripts: bool, - ) -> anyhow::Result<Composer> { + ) -> anyhow::Result<PartialComposerHandle> { 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 @@ -1250,16 +1282,16 @@ impl Factory { disable_plugins }; - match factory.create_composer(io, config, disable_plugins, None, true, disable_scripts)? { - PartialComposerOrComposer::Full(c) => Ok(c), - PartialComposerOrComposer::Partial(_) => { - // TODO(phase-b): unreachable when fullLoad=true; downcasting needs design. - Err(anyhow::anyhow!(RuntimeException { - message: "Composer expected with fullLoad=true".to_string(), - code: 0, - })) - } + let composer = + factory.create_composer(io, config, disable_plugins, None, true, disable_scripts)?; + if !composer.is_full() { + // TODO(phase-b): unreachable when fullLoad=true; downcasting needs design. + return Err(anyhow::anyhow!(RuntimeException { + message: "Composer expected with fullLoad=true".to_string(), + code: 0, + })); } + Ok(composer) } /// If you are calling this in a plugin, you probably should instead use `$composer->getLoop()->getHttpDownloader()` @@ -1488,86 +1520,3 @@ enum ValidateJsonInput { File(JsonFile), Data(PhpMixed), } - -/// `Factory::createComposer` returns either a `Composer` (`$fullLoad=true`) or a `PartialComposer`. -pub enum PartialComposerOrComposer { - Full(Composer), - Partial(PartialComposer), -} - -impl PartialComposerOrComposer { - fn set_config(&mut self, config: std::rc::Rc<std::cell::RefCell<Config>>) { - match self { - Self::Full(c) => c.set_config(config), - Self::Partial(p) => p.set_config(config), - } - } - fn set_global(&mut self) { - match self { - Self::Full(c) => c.set_global(), - Self::Partial(p) => p.set_global(), - } - } - fn set_loop(&mut self, r#loop: std::rc::Rc<std::cell::RefCell<Loop>>) { - match self { - Self::Full(c) => c.set_loop(r#loop), - Self::Partial(p) => p.set_loop(r#loop), - } - } - fn set_event_dispatcher( - &mut self, - dispatcher: std::rc::Rc<std::cell::RefCell<EventDispatcher>>, - ) { - match self { - Self::Full(c) => c.set_event_dispatcher(dispatcher), - Self::Partial(p) => p.set_event_dispatcher(dispatcher), - } - } - fn set_repository_manager(&mut self, rm: RepositoryManager) { - match self { - Self::Full(c) => c.set_repository_manager(rm), - Self::Partial(p) => p.set_repository_manager(rm), - } - } - fn set_installation_manager(&mut self, im: InstallationManager) { - match self { - Self::Full(c) => c.set_installation_manager(im), - Self::Partial(p) => p.set_installation_manager(im), - } - } - fn set_package(&mut self, package: Box<dyn RootPackageInterface>) { - match self { - Self::Full(c) => c.set_package(package), - Self::Partial(p) => p.set_package(package), - } - } - fn get_package(&self) -> &dyn RootPackageInterface { - match self { - Self::Full(c) => c.get_package(), - Self::Partial(p) => p.get_package(), - } - } - fn get_config(&self) -> &std::rc::Rc<std::cell::RefCell<Config>> { - match self { - Self::Full(c) => c.get_config(), - Self::Partial(p) => p.get_config(), - } - } - fn get_event_dispatcher(&self) -> &std::rc::Rc<std::cell::RefCell<EventDispatcher>> { - match self { - Self::Full(c) => c.get_event_dispatcher(), - Self::Partial(p) => p.get_event_dispatcher(), - } - } - fn as_partial(&self) -> PartialComposer { - // TODO(phase-b): PHP class semantics requires sharing PartialComposer by reference; - // currently returning a fresh default since PartialComposer is not Clone. - PartialComposer::default() - } - fn into_partial(self) -> PartialComposer { - match self { - Self::Full(_) => PartialComposer::default(), - Self::Partial(p) => p, - } - } -} diff --git a/crates/shirabe/src/installer.rs b/crates/shirabe/src/installer.rs index b9236a1..8ea0249 100644 --- a/crates/shirabe/src/installer.rs +++ b/crates/shirabe/src/installer.rs @@ -45,7 +45,7 @@ use shirabe_semver; use crate::advisory::AuditConfig; use crate::advisory::Auditor; use crate::autoload::AutoloadGenerator; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; use crate::config::Config; use crate::console::GithubActionError; use crate::dependency_resolver::DefaultPolicy; @@ -108,11 +108,11 @@ pub struct Installer { // TODO can we get rid of the below and just use the package itself? pub(crate) fixed_root_package: Box<dyn RootPackageInterface>, pub(crate) download_manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, - pub(crate) repository_manager: RepositoryManager, - pub(crate) locker: Locker, - pub(crate) installation_manager: InstallationManager, + pub(crate) repository_manager: std::rc::Rc<std::cell::RefCell<RepositoryManager>>, + pub(crate) locker: std::rc::Rc<std::cell::RefCell<Locker>>, + pub(crate) installation_manager: std::rc::Rc<std::cell::RefCell<InstallationManager>>, pub(crate) event_dispatcher: std::rc::Rc<std::cell::RefCell<EventDispatcher>>, - pub(crate) autoload_generator: AutoloadGenerator, + pub(crate) autoload_generator: std::rc::Rc<std::cell::RefCell<AutoloadGenerator>>, pub(crate) prefer_source: bool, pub(crate) prefer_dist: bool, pub(crate) optimize_autoloader: bool, @@ -163,11 +163,11 @@ impl Installer { config: std::rc::Rc<std::cell::RefCell<Config>>, package: Box<dyn RootPackageInterface>, download_manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, - repository_manager: RepositoryManager, - locker: Locker, - installation_manager: InstallationManager, + repository_manager: std::rc::Rc<std::cell::RefCell<RepositoryManager>>, + locker: std::rc::Rc<std::cell::RefCell<Locker>>, + installation_manager: std::rc::Rc<std::cell::RefCell<InstallationManager>>, event_dispatcher: std::rc::Rc<std::cell::RefCell<EventDispatcher>>, - autoload_generator: AutoloadGenerator, + autoload_generator: std::rc::Rc<std::cell::RefCell<AutoloadGenerator>>, ) -> Self { let suggested_packages_reporter = SuggestedPackagesReporter::new(io.clone_box()); let platform_requirement_filter = PlatformRequirementFilterFactory::ignore_nothing(); @@ -235,10 +235,14 @@ impl Installer { }.into()); } - let is_fresh_install = self.repository_manager.get_local_repository().is_fresh(); + let is_fresh_install = self + .repository_manager + .borrow() + .get_local_repository() + .is_fresh(); // Force update if there is no lock file present - if !self.update && !self.locker.is_locked() { + if !self.update && !self.locker.borrow_mut().is_locked() { self.io.write_error("<warning>No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.</warning>"); self.update = true; } @@ -289,6 +293,7 @@ impl Installer { let local_repo_box = self .repository_manager + .borrow() .get_local_repository() .clone_installed_repository_box(); @@ -316,7 +321,9 @@ impl Installer { .as_bool() .unwrap_or(false) { - self.installation_manager.notify_installs(&*self.io); + self.installation_manager + .borrow_mut() + .notify_installs(&*self.io); } return Err(e); } @@ -332,14 +339,19 @@ impl Installer { .as_bool() .unwrap_or(false) { - self.installation_manager.notify_installs(&*self.io); + self.installation_manager + .borrow_mut() + .notify_installs(&*self.io); } if self.update { + let locked_repository_box = self + .locker + .borrow_mut() + .get_locked_repository(self.dev_mode)? + .clone_box(); let installed_repo = InstalledRepository::new(vec![ - self.locker - .get_locked_repository(self.dev_mode)? - .clone_box(), + locked_repository_box, Box::new(self.create_platform_repo(false)), Box::new(RootPackageRepository::new(self.package.clone_box())), ]); @@ -352,7 +364,7 @@ impl Installer { } // Find abandoned packages and warn user - let locked_repository = self.locker.get_locked_repository(true)?; + let locked_repository = self.locker.borrow_mut().get_locked_repository(true)?; for package in CanonicalPackagesTrait::get_packages(&locked_repository) { let complete = match package.as_complete_package() { Some(p) if p.is_abandoned() => p, @@ -382,33 +394,37 @@ impl Installer { } self.autoload_generator + .borrow_mut() .set_class_map_authoritative(self.class_map_authoritative); self.autoload_generator + .borrow_mut() .set_apcu(self.apcu_autoloader, self.apcu_autoloader_prefix.clone()); - self.autoload_generator.set_run_scripts(self.run_scripts); self.autoload_generator + .borrow_mut() + .set_run_scripts(self.run_scripts); + self.autoload_generator + .borrow_mut() .set_platform_requirement_filter(self.platform_requirement_filter.clone_box()); - self.autoload_generator.dump( + self.autoload_generator.borrow_mut().dump( &*self.config.borrow(), - self.repository_manager.get_local_repository(), + self.repository_manager.borrow().get_local_repository(), &*self.package, - &mut self.installation_manager, + &mut *self.installation_manager.borrow_mut(), "composer", self.optimize_autoloader, None, - Some(&mut self.locker), + Some(&mut *self.locker.borrow_mut()), false, )?; } if self.install && self.execute_operations { // force binaries re-generation in case they are missing - for package in self - .repository_manager - .get_local_repository() - .get_packages() - { + let repository_manager = std::rc::Rc::clone(&self.repository_manager); + let repository_manager = repository_manager.borrow(); + for package in repository_manager.get_local_repository().get_packages() { self.installation_manager + .borrow_mut() .ensure_binaries_presence(&*package); } } @@ -424,11 +440,9 @@ impl Installer { if show_funding { let mut funding_count: i64 = 0; - for package in self - .repository_manager - .get_local_repository() - .get_packages() - { + let repository_manager = std::rc::Rc::clone(&self.repository_manager); + let repository_manager = repository_manager.borrow(); + for package in repository_manager.get_local_repository().get_packages() { if let Some(cp) = package.as_complete_package_interface() { if package.as_alias_package().is_none() && !cp.get_funding().is_empty() { funding_count += 1; @@ -477,6 +491,7 @@ impl Installer { } else { ( self.repository_manager + .borrow() .get_local_repository() .get_canonical_packages(), "installed", @@ -492,7 +507,9 @@ impl Installer { IndexMap::new(), IndexMap::new(), ); - for repo in self.repository_manager.get_repositories() { + let repository_manager = std::rc::Rc::clone(&self.repository_manager); + let repository_manager = repository_manager.borrow(); + for repo in repository_manager.get_repositories() { repo_set.add_repository(repo.clone_box())?; } @@ -546,8 +563,8 @@ impl Installer { let mut try_load_locked = || -> anyhow::Result<Result<Option<LockArrayRepository>, ParsingException>> { - if self.locker.is_locked() { - match self.locker.get_locked_repository(true) { + if self.locker.borrow_mut().is_locked() { + match self.locker.borrow_mut().get_locked_repository(true) { Ok(r) => Ok(Ok(Some(r))), Err(e) => match e.downcast::<ParsingException>() { Ok(p) => Ok(Err(p)), @@ -595,7 +612,9 @@ impl Installer { // creating repository set let policy = self.create_policy(true, locked_repository.as_ref()); let mut repository_set = self.create_repository_set(true, &platform_repo, &aliases, None); - let repositories = self.repository_manager.get_repositories(); + let repository_manager = std::rc::Rc::clone(&self.repository_manager); + let repository_manager = repository_manager.borrow(); + let repositories = repository_manager.get_repositories(); for repository in repositories { repository_set.add_repository(repository.clone_box())?; } @@ -674,7 +693,7 @@ impl Installer { if self.minimal_update && self.update_allow_list.is_none() - && self.locker.is_fresh().unwrap_or(false) + && self.locker.borrow_mut().is_fresh().unwrap_or(false) { self.io.write_error("<warning>The --minimal-changes option should be used with package arguments or after modifying composer.json requirements, otherwise it will likely not yield any dependency changes.</warning>"); } @@ -852,7 +871,7 @@ impl Installer { .into_iter() .map(|(k, v)| (k, *v)) .collect(); - let updated_lock = self.locker.set_lock_data( + let updated_lock = self.locker.borrow_mut().set_lock_data( lock_transaction.get_new_lock_packages(false, self.update_mirrors), Some(lock_transaction.get_new_lock_packages(true, self.update_mirrors)), platform_reqs, @@ -961,7 +980,10 @@ impl Installer { )); } - let locked_repository = self.locker.get_locked_repository(self.dev_mode)?; + let locked_repository = self + .locker + .borrow_mut() + .get_locked_repository(self.dev_mode)?; // verify that the lock file works with the current platform repository // we can skip this part if we're doing this as the second step after an update @@ -989,7 +1011,7 @@ impl Installer { Some(&locked_repository), ); - if !self.locker.is_fresh()? { + if !self.locker.borrow_mut().is_fresh()? { self.io.write_error3( "<warning>Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update <package name>`.</warning>", true, @@ -999,6 +1021,7 @@ impl Installer { let missing_requirement_info = self .locker + .borrow_mut() .get_missing_requirement_info(&*self.package, self.dev_mode)?; if !missing_requirement_info.is_empty() { self.io.write_error(&missing_requirement_info.join("\n")); @@ -1031,7 +1054,11 @@ impl Installer { } } - for link in self.locker.get_platform_requirements(self.dev_mode)? { + for link in self + .locker + .borrow_mut() + .get_platform_requirements(self.dev_mode)? + { if !root_requires.contains_key(link.get_target()) { request .require_name(link.get_target(), Some(link.get_constraint().clone_box()))?; @@ -1148,8 +1175,8 @@ impl Installer { } if self.execute_operations { - local_repo.set_dev_package_names(self.locker.get_dev_package_names()?); - self.installation_manager.execute( + local_repo.set_dev_package_names(self.locker.borrow_mut().get_dev_package_names()?); + self.installation_manager.borrow_mut().execute( &mut *local_repo, local_repo_transaction.get_operations(), self.dev_mode, @@ -1199,6 +1226,7 @@ impl Installer { .collect() } else { self.locker + .borrow_mut() .get_platform_overrides() .unwrap_or_default() .into_iter() @@ -1243,11 +1271,13 @@ impl Installer { } else { minimum_stability = self .locker + .borrow_mut() .get_minimum_stability() .unwrap_or_else(|_| String::new()); // TODO(phase-b): locker.get_stability_flags returns IndexMap<String, String>; convert to i64 stability_flags = self .locker + .borrow_mut() .get_stability_flags() .map(|m| { m.into_iter() @@ -1356,8 +1386,8 @@ impl Installer { let mut prefer_stable: Option<bool> = None; let mut prefer_lowest: Option<bool> = None; if !for_update { - prefer_stable = self.locker.get_prefer_stable().unwrap_or(None); - prefer_lowest = self.locker.get_prefer_lowest().unwrap_or(None); + prefer_stable = self.locker.borrow_mut().get_prefer_stable().unwrap_or(None); + prefer_lowest = self.locker.borrow_mut().get_prefer_lowest().unwrap_or(None); } // old lock file without prefer stable/lowest will return null // so in this case we use the composer.json info @@ -1458,7 +1488,11 @@ impl Installer { if self.update_mirrors { let excluded_packages: IndexMap<String, i64> = if !include_dev_requires { // TODO(phase-b): locker.get_dev_package_names returns Result<Vec<String>> - let names = self.locker.get_dev_package_names().unwrap_or_default(); + let names = self + .locker + .borrow_mut() + .get_dev_package_names() + .unwrap_or_default(); names .into_iter() .enumerate() @@ -1501,7 +1535,7 @@ impl Installer { if for_update { self.package.get_aliases().to_vec() } else { - self.locker.get_aliases().unwrap_or_default() + self.locker.borrow_mut().get_aliases().unwrap_or_default() } } @@ -1600,12 +1634,19 @@ impl Installer { } /// Create Installer - pub fn create(io: Box<dyn IOInterface>, composer: &Composer) -> Self { - // TODO(phase-b): Installer::new takes owned manager/locker/etc., but Composer holds them - // by value without Clone (correct for PHP class semantics). Requires refactoring - // Installer to hold &/Rc references or moving ownership out of Composer. - let _ = (io, composer); - todo!() + pub fn create(io: Box<dyn IOInterface>, composer: &PartialComposerHandle) -> Self { + let composer = crate::composer::composer_full(composer); + Self::new( + io, + composer.get_config(), + composer.get_package().clone_box(), + composer.get_download_manager(), + composer.get_repository_manager(), + composer.get_locker(), + composer.get_installation_manager(), + composer.get_event_dispatcher(), + composer.get_autoload_generator(), + ) } /// Packages of those types are ignored, by default php-ext and php-ext-zend are ignored @@ -1928,7 +1969,7 @@ impl Installer { /// custom third-party installers. pub fn disable_plugins(&mut self) -> &mut Self { // TODO(plugin): plugin disabling is part of the plugin API - self.installation_manager.disable_plugins(); + self.installation_manager.borrow_mut().disable_plugins(); self } diff --git a/crates/shirabe/src/installer/installer_event.rs b/crates/shirabe/src/installer/installer_event.rs index 6d4bf0b..3c02b7f 100644 --- a/crates/shirabe/src/installer/installer_event.rs +++ b/crates/shirabe/src/installer/installer_event.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Installer/InstallerEvent.php -use crate::composer::Composer; +use crate::composer::ComposerWeakHandle; use crate::dependency_resolver::Transaction; use crate::event_dispatcher::Event; use crate::io::IOInterface; @@ -8,7 +8,7 @@ use crate::io::IOInterface; #[derive(Debug)] pub struct InstallerEvent { inner: Event, - composer: Composer, + composer: ComposerWeakHandle, io: Box<dyn IOInterface>, dev_mode: bool, execute_operations: bool, @@ -18,7 +18,7 @@ pub struct InstallerEvent { impl InstallerEvent { pub fn new( event_name: String, - composer: Composer, + composer: ComposerWeakHandle, io: Box<dyn IOInterface>, dev_mode: bool, execute_operations: bool, @@ -35,7 +35,7 @@ impl InstallerEvent { } } - pub fn get_composer(&self) -> &Composer { + pub fn get_composer(&self) -> &ComposerWeakHandle { &self.composer } diff --git a/crates/shirabe/src/installer/library_installer.rs b/crates/shirabe/src/installer/library_installer.rs index 0b4ba78..4c3f201 100644 --- a/crates/shirabe/src/installer/library_installer.rs +++ b/crates/shirabe/src/installer/library_installer.rs @@ -9,14 +9,13 @@ use shirabe_php_shim::{ InvalidArgumentException, LogicException, is_link, preg_quote, realpath, rmdir, rtrim, strpos, }; -use crate::composer::Composer; +use crate::composer::PartialComposerWeakHandle; use crate::downloader::DownloadManager; use crate::installer::BinaryInstaller; use crate::installer::BinaryPresenceInterface; use crate::installer::InstallerInterface; use crate::io::IOInterface; use crate::package::PackageInterface; -use crate::partial_composer::PartialComposer; use crate::repository::InstalledRepositoryInterface; use crate::util::Filesystem; use crate::util::Platform; @@ -25,7 +24,7 @@ use crate::util::Silencer; /// Package installation manager. #[derive(Debug)] pub struct LibraryInstaller { - pub(crate) composer: PartialComposer, + pub(crate) composer: PartialComposerWeakHandle, pub(crate) vendor_dir: String, pub(crate) download_manager: Option<std::rc::Rc<std::cell::RefCell<DownloadManager>>>, pub(crate) io: Box<dyn IOInterface>, @@ -38,26 +37,26 @@ impl LibraryInstaller { /// Initializes library installer. pub fn new( io: Box<dyn IOInterface>, - composer: PartialComposer, + composer: PartialComposerWeakHandle, r#type: Option<String>, filesystem: Option<std::rc::Rc<std::cell::RefCell<Filesystem>>>, binary_installer: Option<BinaryInstaller>, ) -> Self { - // PHP: $this->downloadManager = $composer instanceof Composer ? $composer->getDownloadManager() : null; - // TODO(phase-b): PartialComposer cannot downcast to Composer in this Rust port. - let download_manager: Option<std::rc::Rc<std::cell::RefCell<DownloadManager>>> = - if let Some(_full_composer) = composer.as_any().downcast_ref::<Composer>() { - // TODO(phase-b): clone or borrow the DownloadManager from the full Composer - Some(todo!("composer.get_download_manager() as DownloadManager")) - } else { - None - }; + let composer_rc = composer + .upgrade() + .expect("LibraryInstaller must lives longer than Composer"); + + let download_manager = composer_rc + .as_full() + .map(|full| full.borrow().get_download_manager()); + + let composer_ref = composer_rc.borrow_partial(); let filesystem = filesystem .unwrap_or_else(|| std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(None)))); let vendor_dir = rtrim( // TODO(phase-b): Config::get returns PhpMixed; coerce to String via get_str. - &composer + &composer_ref .get_config() .borrow_mut() .get_str("vendor-dir") @@ -69,7 +68,7 @@ impl LibraryInstaller { // TODO(phase-b): pass io by reference/clone todo!("io reference"), rtrim( - &composer + &composer_ref .get_config() .borrow_mut() .get_str("bin-dir") @@ -77,7 +76,7 @@ impl LibraryInstaller { Some("/"), ), // TODO(phase-b): Config::get returns PhpMixed; coerce to String via get_str. - composer + composer_ref .get_config() .borrow_mut() .get_str("bin-compat") diff --git a/crates/shirabe/src/installer/package_event.rs b/crates/shirabe/src/installer/package_event.rs index 0268721..d0aecc8 100644 --- a/crates/shirabe/src/installer/package_event.rs +++ b/crates/shirabe/src/installer/package_event.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Installer/PackageEvent.php -use crate::composer::Composer; +use crate::composer::ComposerWeakHandle; use crate::dependency_resolver::operation::OperationInterface; use crate::event_dispatcher::Event; use crate::io::IOInterface; @@ -10,7 +10,7 @@ use indexmap::IndexMap; #[derive(Debug)] pub struct PackageEvent { inner: Event, - composer: Composer, + composer: ComposerWeakHandle, io: Box<dyn IOInterface>, dev_mode: bool, local_repo: Box<dyn RepositoryInterface>, @@ -21,7 +21,7 @@ pub struct PackageEvent { impl PackageEvent { pub fn new( event_name: String, - composer: Composer, + composer: ComposerWeakHandle, io: Box<dyn IOInterface>, dev_mode: bool, local_repo: Box<dyn RepositoryInterface>, @@ -43,7 +43,7 @@ impl PackageEvent { self.inner.get_name() } - pub fn get_composer(&self) -> &Composer { + pub fn get_composer(&self) -> &ComposerWeakHandle { &self.composer } diff --git a/crates/shirabe/src/installer/plugin_installer.rs b/crates/shirabe/src/installer/plugin_installer.rs index dff57d8..e1d4c5e 100644 --- a/crates/shirabe/src/installer/plugin_installer.rs +++ b/crates/shirabe/src/installer/plugin_installer.rs @@ -1,11 +1,11 @@ //! ref: composer/src/Composer/Installer/PluginInstaller.php +use crate::composer::PartialComposerWeakHandle; use crate::installer::BinaryInstaller; use crate::installer::InstallerInterface; use crate::installer::LibraryInstaller; use crate::io::IOInterface; use crate::package::PackageInterface; -use crate::partial_composer::PartialComposer; use crate::plugin::PluginManager; use crate::repository::InstalledRepositoryInterface; use crate::util::Filesystem; @@ -22,7 +22,7 @@ pub struct PluginInstaller { impl PluginInstaller { pub fn new( io: Box<dyn IOInterface>, - composer: PartialComposer, + composer: PartialComposerWeakHandle, fs: Option<std::rc::Rc<std::cell::RefCell<Filesystem>>>, binary_installer: Option<BinaryInstaller>, ) -> Self { @@ -39,7 +39,7 @@ impl PluginInstaller { pub fn disable_plugins(&mut self) { // TODO(plugin): disable plugins via plugin manager - self.get_plugin_manager_mut().disable_plugins(); + self.get_plugin_manager().borrow_mut().disable_plugins(); } fn rollback_install( @@ -56,15 +56,10 @@ impl PluginInstaller { Err(e) } - fn get_plugin_manager(&self) -> &PluginManager { + fn get_plugin_manager(&self) -> std::rc::Rc<std::cell::RefCell<PluginManager>> { // TODO(plugin): PartialComposer does not expose PluginManager; revisit when wiring plugin support todo!("PartialComposer.get_plugin_manager") } - - fn get_plugin_manager_mut(&mut self) -> &mut PluginManager { - // TODO(plugin): return mutable plugin manager from composer - todo!() - } } impl InstallerInterface for PluginInstaller { @@ -87,7 +82,10 @@ impl InstallerInterface for PluginInstaller { prev_package: Option<&dyn PackageInterface>, ) -> Result<Option<Box<dyn PromiseInterface>>> { if (r#type == "install" || r#type == "update") - && !self.get_plugin_manager().are_plugins_disabled("local") + && !self + .get_plugin_manager() + .borrow() + .are_plugins_disabled("local") { let plugin_optional = package .get_extra() @@ -176,7 +174,9 @@ impl InstallerInterface for PluginInstaller { package: &dyn PackageInterface, ) -> Result<Option<Box<dyn PromiseInterface>>> { // TODO(plugin): uninstall package from plugin manager - self.get_plugin_manager_mut().uninstall_package(package); + self.get_plugin_manager() + .borrow_mut() + .uninstall_package(package); self.inner.uninstall(repo, package) } diff --git a/crates/shirabe/src/lib.rs b/crates/shirabe/src/lib.rs index a8fe3a7..9fb9f5f 100644 --- a/crates/shirabe/src/lib.rs +++ b/crates/shirabe/src/lib.rs @@ -17,7 +17,6 @@ pub mod installer; pub mod io; pub mod json; pub mod package; -pub mod partial_composer; pub mod phpstan; pub mod platform; pub mod plugin; diff --git a/crates/shirabe/src/package/loader/root_package_loader.rs b/crates/shirabe/src/package/loader/root_package_loader.rs index 231c807..5d66931 100644 --- a/crates/shirabe/src/package/loader/root_package_loader.rs +++ b/crates/shirabe/src/package/loader/root_package_loader.rs @@ -27,7 +27,7 @@ use crate::util::ProcessExecutor; #[derive(Debug)] pub struct RootPackageLoader { inner: ArrayLoader, - manager: RepositoryManager, + manager: std::rc::Rc<std::cell::RefCell<RepositoryManager>>, config: std::rc::Rc<std::cell::RefCell<Config>>, version_guesser: VersionGuesser, io: Option<Box<dyn IOInterface>>, @@ -35,7 +35,7 @@ pub struct RootPackageLoader { impl RootPackageLoader { pub fn new( - manager: RepositoryManager, + manager: std::rc::Rc<std::cell::RefCell<RepositoryManager>>, config: std::rc::Rc<std::cell::RefCell<Config>>, parser: Option<VersionParser>, version_guesser: Option<VersionGuesser>, @@ -281,10 +281,10 @@ impl RootPackageLoader { let repos = RepositoryFactory::default_repos( None, Some(std::rc::Rc::clone(&self.config)), - Some(&mut self.manager), + Some(&mut *self.manager.borrow_mut()), )?; for (_, repo) in repos { - self.manager.add_repository(repo); + self.manager.borrow_mut().add_repository(repo); } // TODO(phase-b): Config::get_repositories returns IndexMap<String, PhpMixed>, but // set_repositories expects Vec<IndexMap<String, PhpMixed>>; pass empty placeholder. diff --git a/crates/shirabe/src/package/locker.rs b/crates/shirabe/src/package/locker.rs index 5dd7ab4..ecd96e3 100644 --- a/crates/shirabe/src/package/locker.rs +++ b/crates/shirabe/src/package/locker.rs @@ -40,7 +40,7 @@ pub struct Locker { /// @var JsonFile lock_file: JsonFile, /// @var InstallationManager - installation_manager: InstallationManager, + installation_manager: std::rc::Rc<std::cell::RefCell<InstallationManager>>, /// @var string hash: String, /// @var string @@ -62,7 +62,7 @@ impl Locker { pub fn new( io: Box<dyn IOInterface>, lock_file: JsonFile, - installation_manager: InstallationManager, + installation_manager: std::rc::Rc<std::cell::RefCell<InstallationManager>>, composer_file_contents: &str, process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, ) -> Self { @@ -824,7 +824,10 @@ impl Locker { return Ok(None); } - let path = self.installation_manager.get_install_path(package); + let path = self + .installation_manager + .borrow_mut() + .get_install_path(package); if path.is_none() { return Ok(None); } diff --git a/crates/shirabe/src/partial_composer.rs b/crates/shirabe/src/partial_composer.rs deleted file mode 100644 index ec51b81..0000000 --- a/crates/shirabe/src/partial_composer.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! ref: composer/src/Composer/PartialComposer.php - -use crate::config::Config; -use crate::event_dispatcher::EventDispatcher; -use crate::installer::InstallationManager; -use crate::package::RootPackageInterface; -use crate::repository::RepositoryManager; -use crate::util::r#loop::Loop; - -#[derive(Debug, Default)] -pub struct PartialComposer { - global: bool, - package: Option<Box<dyn RootPackageInterface>>, - r#loop: Option<std::rc::Rc<std::cell::RefCell<Loop>>>, - repository_manager: Option<RepositoryManager>, - installation_manager: Option<InstallationManager>, - config: Option<std::rc::Rc<std::cell::RefCell<Config>>>, - event_dispatcher: Option<std::rc::Rc<std::cell::RefCell<EventDispatcher>>>, -} - -impl PartialComposer { - pub fn set_package(&mut self, package: Box<dyn RootPackageInterface>) { - self.package = Some(package); - } - - pub fn get_package(&self) -> &dyn RootPackageInterface { - self.package.as_deref().unwrap() - } - - pub fn set_config(&mut self, config: std::rc::Rc<std::cell::RefCell<Config>>) { - self.config = Some(config); - } - - pub fn get_config(&self) -> &std::rc::Rc<std::cell::RefCell<Config>> { - self.config.as_ref().unwrap() - } - - pub fn get_config_mut(&mut self) -> &mut std::rc::Rc<std::cell::RefCell<Config>> { - self.config.as_mut().unwrap() - } - - pub fn set_loop(&mut self, r#loop: std::rc::Rc<std::cell::RefCell<Loop>>) { - self.r#loop = Some(r#loop); - } - - pub fn get_loop(&self) -> &std::rc::Rc<std::cell::RefCell<Loop>> { - self.r#loop.as_ref().unwrap() - } - - pub fn set_repository_manager(&mut self, manager: RepositoryManager) { - self.repository_manager = Some(manager); - } - - pub fn get_repository_manager(&self) -> &RepositoryManager { - self.repository_manager.as_ref().unwrap() - } - - pub fn set_installation_manager(&mut self, manager: InstallationManager) { - self.installation_manager = Some(manager); - } - - pub fn get_installation_manager(&self) -> &InstallationManager { - self.installation_manager.as_ref().unwrap() - } - - pub fn get_installation_manager_mut(&mut self) -> &mut InstallationManager { - self.installation_manager.as_mut().unwrap() - } - - pub fn set_event_dispatcher( - &mut self, - event_dispatcher: std::rc::Rc<std::cell::RefCell<EventDispatcher>>, - ) { - self.event_dispatcher = Some(event_dispatcher); - } - - pub fn get_event_dispatcher(&self) -> &std::rc::Rc<std::cell::RefCell<EventDispatcher>> { - self.event_dispatcher.as_ref().unwrap() - } - - pub fn is_global(&self) -> bool { - self.global - } - - pub fn set_global(&mut self) { - self.global = true; - } - - /// TODO(phase-b): Emulates PHP `$composer instanceof Composer` check. - /// PartialComposer cannot be a Composer here (Composer is a separate struct - /// that wraps PartialComposer via composition), so this always returns false. - pub fn is_full_composer(&self) -> bool { - false - } - - /// TODO(phase-b): Emulates PHP downcast to `Composer`. - /// Returns self as `&dyn Any`; downcasting to Composer will always fail because - /// PartialComposer is not a Composer in this Rust port. - pub fn as_any(&self) -> &dyn std::any::Any { - self - } -} diff --git a/crates/shirabe/src/plugin/plugin_interface.rs b/crates/shirabe/src/plugin/plugin_interface.rs index 875976f..ae5e3b6 100644 --- a/crates/shirabe/src/plugin/plugin_interface.rs +++ b/crates/shirabe/src/plugin/plugin_interface.rs @@ -1,17 +1,17 @@ //! ref: composer/src/Composer/Plugin/PluginInterface.php -use crate::composer::Composer; +use crate::composer::ComposerHandle; use crate::io::IOInterface; use crate::plugin::Capable; pub const PLUGIN_API_VERSION: &'static str = "2.9.0"; pub trait PluginInterface: std::fmt::Debug { - fn activate(&mut self, composer: &Composer, io: &dyn IOInterface); + fn activate(&mut self, composer: &ComposerHandle, io: &dyn IOInterface); - fn deactivate(&mut self, composer: &Composer, io: &dyn IOInterface); + fn deactivate(&mut self, composer: &ComposerHandle, io: &dyn IOInterface); - fn uninstall(&mut self, composer: &Composer, io: &dyn IOInterface); + fn uninstall(&mut self, composer: &ComposerHandle, io: &dyn IOInterface); fn clone_box(&self) -> Box<dyn PluginInterface> { todo!() diff --git a/crates/shirabe/src/plugin/plugin_manager.rs b/crates/shirabe/src/plugin/plugin_manager.rs index a7d26c9..26e6296 100644 --- a/crates/shirabe/src/plugin/plugin_manager.rs +++ b/crates/shirabe/src/plugin/plugin_manager.rs @@ -15,7 +15,8 @@ use shirabe_php_shim::{ }; use shirabe_semver::constraint::Constraint; -use crate::composer::Composer; +use crate::composer::PartialComposerHandle; +use crate::composer::{ComposerHandle, ComposerWeakHandle}; use crate::event_dispatcher::EventSubscriberInterface; use crate::installer::InstallerInterface; use crate::io::IOInterface; @@ -26,7 +27,6 @@ use crate::package::PackageInterface; use crate::package::RootPackageInterface; use crate::package::base_package::{self, BasePackage}; use crate::package::version::VersionParser; -use crate::partial_composer::PartialComposer; use crate::plugin::Capable; use crate::plugin::PluginBlockedException; use crate::plugin::capability::Capability; @@ -48,9 +48,9 @@ pub enum DisablePlugins { #[derive(Debug)] pub struct PluginManager { - pub(crate) composer: Composer, + pub(crate) composer: ComposerWeakHandle, pub(crate) io: Box<dyn IOInterface>, - pub(crate) global_composer: Option<PartialComposer>, + pub(crate) global_composer: Option<PartialComposerHandle>, pub(crate) version_parser: VersionParser, pub(crate) disable_plugins: DisablePlugins, pub(crate) plugins: Vec<Box<dyn PluginInterface>>, @@ -71,17 +71,34 @@ static mut CLASS_COUNTER: i64 = 0; impl PluginManager { pub fn new( io: Box<dyn IOInterface>, - mut composer: Composer, - global_composer: Option<PartialComposer>, + composer: ComposerWeakHandle, + global_composer: Option<PartialComposerHandle>, disable_plugins: DisablePlugins, ) -> Self { - let allow_plugins_config = composer.get_config().borrow().get("allow-plugins").clone(); + let composer_rc = composer + .upgrade() + .expect("PluginManager must not outlive Composer"); + let allow_plugins_config = composer_rc + .borrow() + .get_config() + .borrow() + .get("allow-plugins") + .clone(); + let locker = composer_rc.borrow().get_locker().clone(); + let mut locker = locker.borrow_mut(); let allow_plugin_rules = - Self::parse_allowed_plugins(allow_plugins_config, Some(composer.get_locker_mut())); + Self::parse_allowed_plugins(allow_plugins_config, Some(&mut *locker)); + drop(locker); let allow_global_plugin_rules = Self::parse_allowed_plugins( global_composer .as_ref() - .map(|gc| gc.get_config().borrow_mut().get("allow-plugins").clone()) + .map(|gc| { + gc.borrow_partial() + .get_config() + .borrow_mut() + .get("allow-plugins") + .clone() + }) .unwrap_or(PhpMixed::Bool(false)), None, ); @@ -103,6 +120,14 @@ impl PluginManager { self.running_in_global_dir = running_in_global_dir; } + /// Upgrades the weak Composer back-reference to a full handle. PHP holds a strong + /// `Composer`; the Rust port keeps it weak to break the Composer/PluginManager cycle. + fn composer_full(&self) -> ComposerHandle { + self.composer + .upgrade() + .expect("PluginManager must not outlive Composer") + } + /// Loads all plugins from currently installed plugin packages pub fn load_installed_plugins(&mut self) -> anyhow::Result<()> { // TODO(plugin): plugin loading is part of the plugin API @@ -111,13 +136,15 @@ impl PluginManager { // box here to side-step a borrow conflict between `&self.composer` and // `&mut self`. The Rust port should eventually share via Rc<RefCell<_>>. let repo: Box<dyn RepositoryInterface> = self - .composer + .composer_full() + .borrow() .get_repository_manager() + .borrow() .get_local_repository() .clone_box(); // The root package borrow is also tied to `self.composer`; clone the package box // for the same reason as above. - let root_package = self.composer.get_package().clone_box(); + let root_package = self.composer_full().borrow().get_package().clone_box(); self.load_repository(&*repo, false, Some(&*root_package))?; } @@ -126,7 +153,9 @@ impl PluginManager { .global_composer .as_ref() .unwrap() + .borrow_partial() .get_repository_manager() + .borrow() .get_local_repository() .clone_box(); self.load_repository(&*repo, true, None)?; @@ -139,8 +168,10 @@ impl PluginManager { // TODO(plugin): deactivation is part of the plugin API if !self.are_plugins_disabled("local") { let repo: Box<dyn RepositoryInterface> = self - .composer + .composer_full() + .borrow() .get_repository_manager() + .borrow() .get_local_repository() .clone_box(); self.deactivate_repository(&*repo, false); @@ -151,7 +182,9 @@ impl PluginManager { .global_composer .as_ref() .unwrap() + .borrow_partial() .get_repository_manager() + .borrow() .get_local_repository() .clone_box(); self.deactivate_repository(&*repo, true); @@ -171,7 +204,7 @@ impl PluginManager { } /// Gets global composer or null when main composer is not fully loaded - pub fn get_global_composer(&self) -> Option<&PartialComposer> { + pub fn get_global_composer(&self) -> Option<&PartialComposerHandle> { self.global_composer.as_ref() } @@ -329,8 +362,10 @@ impl PluginManager { for plugin in plugins { match plugin { PluginOrInstaller::Installer(inst) => { - self.composer - .get_installation_manager_mut() + self.composer_full() + .borrow() + .get_installation_manager() + .borrow_mut() .remove_installer(&*inst); } PluginOrInstaller::Plugin(p) => { @@ -354,8 +389,10 @@ impl PluginManager { for plugin in plugins { match plugin { PluginOrInstaller::Installer(inst) => { - self.composer - .get_installation_manager_mut() + self.composer_full() + .borrow() + .get_installation_manager() + .borrow_mut() .remove_installer(&*inst); } PluginOrInstaller::Plugin(mut p) => { @@ -426,7 +463,7 @@ impl PluginManager { String::new() } )); - plugin.activate(&self.composer, &*self.io); + plugin.activate(&self.composer_full(), &*self.io); // TODO(plugin): if plugin is EventSubscriberInterface, hook into the event dispatcher // The PHP code calls $this->composer->getEventDispatcher()->addSubscriber($plugin); @@ -453,7 +490,7 @@ impl PluginManager { self.io .write_error(&format!("Unloading plugin {}", get_class_obj(plugin))); let mut removed = self.plugins.remove(index); - removed.deactivate(&self.composer, &*self.io); + removed.deactivate(&self.composer_full(), &*self.io); // TODO(plugin): remove_listener accepts any callable/object in PHP; here we have // a plugin instance and need to translate to a Callable, which is not portable @@ -466,7 +503,7 @@ impl PluginManager { // TODO(plugin): plugin uninstall hook self.io .write_error(&format!("Uninstalling plugin {}", get_class_obj(plugin))); - plugin.uninstall(&self.composer, &*self.io); + plugin.uninstall(&self.composer_full(), &*self.io); } fn load_repository( @@ -605,16 +642,20 @@ impl PluginManager { fn get_install_path(&mut self, package: &dyn PackageInterface, global: bool) -> Option<String> { if !global { return self - .composer - .get_installation_manager_mut() + .composer_full() + .borrow() + .get_installation_manager() + .borrow_mut() .get_install_path(package); } // PHP: assert(null !== $this->globalComposer); self.global_composer - .as_mut() + .as_ref() .unwrap() - .get_installation_manager_mut() + .borrow_partial() + .get_installation_manager() + .borrow_mut() .get_install_path(package) } @@ -821,13 +862,17 @@ impl PluginManager { } if self.io.is_interactive() && prompt { - // TODO(plugin): interactive consent flow — preserved as a stub - let composer_ref: &Composer = if is_global_plugin && self.global_composer.is_some() { - // PHP allows PartialComposer here; treat as the same dispatch surface in the stub. - &self.composer - } else { - &self.composer - }; + // TODO(plugin): interactive consent flow — preserved as a stub. PHP picks + // $this->globalComposer's config when is_global_plugin; the stub uses the + // local composer's config in both cases. Access the `composer` field directly + // (not via `composer_full()`) so the borrow stays disjoint from `rules`. + let config = self + .composer + .upgrade() + .expect("PluginManager must not outlive Composer") + .borrow() + .get_config() + .clone(); self.io.write_error(&format!("<warning>{}{} contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins</warning>", package, @@ -860,17 +905,13 @@ impl PluginManager { // persist answer in composer.json if it wasn't simply discarded if answer_str == "y" || answer_str == "n" { - let allow_plugins_value = composer_ref - .get_config() - .borrow_mut() - .get("allow-plugins") - .clone(); + let allow_plugins_value = + config.borrow_mut().get("allow-plugins").clone(); if let Some(arr) = allow_plugins_value.as_array() { let mut allow_plugins = arr.clone(); allow_plugins .insert(package.to_string(), Box::new(PhpMixed::Bool(allow))); - if composer_ref - .get_config() + if config .borrow_mut() .get("sort-packages") .as_bool() @@ -878,8 +919,7 @@ impl PluginManager { { ksort(&mut allow_plugins); } - composer_ref - .get_config() + config .borrow_mut() .get_config_source_mut() .add_config_setting( diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index 17fde5d..8494e25 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -13,7 +13,8 @@ use shirabe_php_shim::{ }; use shirabe_semver::constraint::Constraint; -use crate::composer::Composer; +use crate::composer; +use crate::composer::ComposerHandle; use crate::package::CompletePackage; use crate::package::CompletePackageInterface; use crate::package::Link; @@ -153,7 +154,7 @@ impl PlatformRepository { } } - let mut pretty_version = Composer::get_version(); + let mut pretty_version = composer::get_version(); let mut version = self .version_parser .as_ref() @@ -181,7 +182,7 @@ impl PlatformRepository { composer_plugin_api.set_description("The Composer Plugin API".to_string()); self.add_package(Box::new(composer_plugin_api))?; - pretty_version = Composer::RUNTIME_API_VERSION.to_string(); + pretty_version = composer::RUNTIME_API_VERSION.to_string(); version = self .version_parser .as_ref() diff --git a/crates/shirabe/src/script/event.rs b/crates/shirabe/src/script/event.rs index 9e6278a..1bd37dd 100644 --- a/crates/shirabe/src/script/event.rs +++ b/crates/shirabe/src/script/event.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Script/Event.php -use crate::composer::Composer; +use crate::composer::ComposerWeakHandle; use crate::event_dispatcher::Event as BaseEvent; use crate::io::IOInterface; use indexmap::IndexMap; @@ -9,7 +9,7 @@ use shirabe_php_shim::PhpMixed; #[derive(Debug)] pub struct Event { inner: BaseEvent, - composer: Composer, + composer: ComposerWeakHandle, io: Box<dyn IOInterface>, dev_mode: bool, originating_event: Option<Box<BaseEvent>>, @@ -18,7 +18,7 @@ pub struct Event { impl Event { pub fn new( name: String, - composer: Composer, + composer: ComposerWeakHandle, io: Box<dyn IOInterface>, dev_mode: bool, args: Vec<String>, @@ -33,7 +33,7 @@ impl Event { } } - pub fn get_composer(&self) -> &Composer { + pub fn get_composer(&self) -> &ComposerWeakHandle { &self.composer } diff --git a/crates/shirabe/src/util/http_downloader.rs b/crates/shirabe/src/util/http_downloader.rs index 8c8b8ef..b5a4923 100644 --- a/crates/shirabe/src/util/http_downloader.rs +++ b/crates/shirabe/src/util/http_downloader.rs @@ -14,7 +14,8 @@ use shirabe_php_shim::{ }; use shirabe_semver::constraint::Constraint; -use crate::composer::Composer; +use crate::composer; +use crate::composer::ComposerHandle; use crate::config::Config; use crate::downloader::TransportException; use crate::exception::IrrecoverableDownloadException; @@ -595,7 +596,7 @@ impl HttpDownloader { .parse_constraints(versions_value.as_string().unwrap_or(""))?; let composer_constraint = Constraint::new( "==", - &version_parser.normalize(&Composer::get_version(), None)?, + &version_parser.normalize(&composer::get_version(), None)?, ); if !constraint.matches(&composer_constraint) { continue; @@ -633,7 +634,7 @@ impl HttpDownloader { )?; let composer_constraint = Constraint::new( "==", - &version_parser.normalize(&Composer::get_version(), None)?, + &version_parser.normalize(&composer::get_version(), None)?, ); if !constraint.matches(&composer_constraint) { continue; diff --git a/crates/shirabe/src/util/stream_context_factory.rs b/crates/shirabe/src/util/stream_context_factory.rs index bb1948e..48949c8 100644 --- a/crates/shirabe/src/util/stream_context_factory.rs +++ b/crates/shirabe/src/util/stream_context_factory.rs @@ -9,7 +9,8 @@ use shirabe_php_shim::{ php_uname, stream_context_create, stripos, uasort, }; -use crate::composer::Composer; +use crate::composer; +use crate::composer::ComposerHandle; use crate::downloader::TransportException; use crate::repository::PlatformRepository; use crate::util::Filesystem; @@ -203,7 +204,7 @@ impl StreamContextFactory { let platform_php_version = PlatformRepository::get_platform_php_version(); let user_agent = format!( "User-Agent: Composer/{} ({os}; {release}; {php_version}; {http_version}{platform}{ci})", - Composer::get_version(), + composer::get_version(), os = if function_exists("php_uname") { php_uname("s") } else { |
