diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-28 22:43:11 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-28 22:43:43 +0900 |
| commit | eea4efe87e455742ec17881ee93d8095925e8516 (patch) | |
| tree | 6d242f4fdd0bf32f0494a6fbbd62bce9ed6e1dc7 /crates/shirabe/src | |
| parent | cc5d73c05a0abca2eebcc8a6afa0b1543ee49850 (diff) | |
| download | php-shirabe-eea4efe87e455742ec17881ee93d8095925e8516.tar.gz php-shirabe-eea4efe87e455742ec17881ee93d8095925e8516.tar.zst php-shirabe-eea4efe87e455742ec17881ee93d8095925e8516.zip | |
refactor(repository): introduce Rc<RefCell<_>> handles for repositories
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src')
51 files changed, 871 insertions, 481 deletions
diff --git a/crates/shirabe/src/command/archive_command.rs b/crates/shirabe/src/command/archive_command.rs index aab7b51..18b69c3 100644 --- a/crates/shirabe/src/command/archive_command.rs +++ b/crates/shirabe/src/command/archive_command.rs @@ -247,13 +247,12 @@ impl ArchiveCommand { 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()]; + let mut repos: Vec<crate::repository::RepositoryInterfaceHandle> = vec![local_repo]; repos.extend( repository_manager .get_repositories() .iter() - .map(|r| r.clone_box()), + .map(|r| r.clone()), ); repo = CompositeRepository::new(repos); min_stability = composer.get_package().get_minimum_stability().to_string(); @@ -299,7 +298,7 @@ impl ArchiveCommand { IndexMap::new(), IndexMap::new(), ); - repo_set.add_repository(Box::new(repo))?; + repo_set.add_repository(crate::repository::RepositoryInterfaceHandle::new(repo))?; let parser = VersionParser::new(); let constraint: Option<shirabe_semver::constraint::AnyConstraint> = match version.as_deref() { diff --git a/crates/shirabe/src/command/audit_command.rs b/crates/shirabe/src/command/audit_command.rs index ec8b495..270bacb 100644 --- a/crates/shirabe/src/command/audit_command.rs +++ b/crates/shirabe/src/command/audit_command.rs @@ -74,8 +74,7 @@ impl AuditCommand { .borrow() .get_repositories() { - // TODO(phase-b): repositories are shared (PHP class semantics); needs Rc wrapper - repo_set.add_repository(repo.clone_box())?; + repo_set.add_repository(repo.clone())?; } let audit_config = AuditConfig::from_config( diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs index 7a5300a..ad84953 100644 --- a/crates/shirabe/src/command/base_dependency_command.rs +++ b/crates/shirabe/src/command/base_dependency_command.rs @@ -53,10 +53,10 @@ pub trait BaseDependencyCommand: BaseCommand { 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![]; - repos.push(Box::new(RootPackageRepository::new( - composer.get_package().clone(), - ))); + let mut repos: Vec<crate::repository::RepositoryInterfaceHandle> = + vec![crate::repository::RepositoryInterfaceHandle::new( + RootPackageRepository::new(composer.get_package().clone()), + )]; if input.get_option("locked").as_bool().unwrap_or(false) { let locker = composer.get_locker().clone(); @@ -71,16 +71,17 @@ pub trait BaseDependencyCommand: BaseCommand { })); } - repos.push(Box::new(locker.get_locked_repository(true)?)); + repos.push(crate::repository::RepositoryInterfaceHandle::new( + locker.get_locked_repository(true)?, + )); let platform_overrides: IndexMap<String, PhpMixed> = locker .get_platform_overrides()? .into_iter() .map(|(k, v)| (k, PhpMixed::String(v))) .collect(); - repos.push(Box::new(PlatformRepository::new( - vec![], - platform_overrides, - )?)); + repos.push(crate::repository::RepositoryInterfaceHandle::new( + PlatformRepository::new(vec![], platform_overrides)?, + )); } else { let repository_manager = composer.get_repository_manager().clone(); let repository_manager = repository_manager.borrow(); @@ -98,10 +99,7 @@ pub trait BaseDependencyCommand: BaseCommand { return Ok(1); } - // TODO(phase-b): InstalledRepositoryInterface is shared by reference (PHP class - // semantics); Box<dyn RepositoryInterface> requires owned upcast. Skipping local - // repo push until clone_box is exposed on InstalledRepositoryInterface. - let _ = local_repo; + repos.push(local_repo); let platform_overrides = composer .get_config() @@ -112,7 +110,9 @@ pub trait BaseDependencyCommand: BaseCommand { .unwrap_or_default(); // TODO(phase-b): platform_overrides type adjustment; using empty for now let _ = platform_overrides; - repos.push(Box::new(PlatformRepository::new(vec![], IndexMap::new())?)); + repos.push(crate::repository::RepositoryInterfaceHandle::new( + PlatformRepository::new(vec![], IndexMap::new())?, + )); } let mut installed_repo = InstalledRepository::new(repos); @@ -159,9 +159,11 @@ pub trait BaseDependencyCommand: BaseCommand { &needle, FindPackageConstraint::String(text_constraint.clone()), ) { - installed_repo.add_repository(Box::new( - InstalledArrayRepository::new_with_packages(vec![r#match.into()])?, - ))?; + installed_repo.add_repository( + crate::repository::RepositoryInterfaceHandle::new( + InstalledArrayRepository::new_with_packages(vec![r#match.into()])?, + ), + )?; } else if PlatformRepository::is_platform_package(&needle) { let parser = VersionParser::new(); let platform_constraint = parser.parse_constraints(&text_constraint)?; @@ -171,11 +173,14 @@ pub trait BaseDependencyCommand: BaseCommand { .get_version() .to_string(); let temp_platform_pkg = Package::new(needle.clone(), version.clone(), version); - installed_repo.add_repository(Box::new( - InstalledArrayRepository::new_with_packages(vec![ - crate::package::PackageHandle::from_package(temp_platform_pkg).into(), - ])?, - ))?; + installed_repo.add_repository( + crate::repository::RepositoryInterfaceHandle::new( + InstalledArrayRepository::new_with_packages(vec![ + crate::package::PackageHandle::from_package(temp_platform_pkg) + .into(), + ])?, + ), + )?; } } else { self.get_io().write_error(&format!( diff --git a/crates/shirabe/src/command/bump_command.rs b/crates/shirabe/src/command/bump_command.rs index a1e5f35..0f62bb8 100644 --- a/crates/shirabe/src/command/bump_command.rs +++ b/crates/shirabe/src/command/bump_command.rs @@ -142,8 +142,8 @@ impl BumpCommand { .get("lock") .as_bool() .unwrap_or(true); - let repo: Box<dyn crate::repository::RepositoryInterface> = if !has_lock_file_disabled { - Box::new( + let repo: crate::repository::RepositoryInterfaceHandle = if !has_lock_file_disabled { + crate::repository::RepositoryInterfaceHandle::new( composer .get_locker() .borrow_mut() @@ -158,20 +158,17 @@ impl BumpCommand { ); return Ok(Self::ERROR_LOCK_OUTDATED); } - Box::new( + crate::repository::RepositoryInterfaceHandle::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() }; if composer.get_package().get_type() != "project" && !dev_only { diff --git a/crates/shirabe/src/command/check_platform_reqs_command.rs b/crates/shirabe/src/command/check_platform_reqs_command.rs index fd5edd9..15e4225 100644 --- a/crates/shirabe/src/command/check_platform_reqs_command.rs +++ b/crates/shirabe/src/command/check_platform_reqs_command.rs @@ -63,7 +63,7 @@ impl CheckPlatformReqsCommand { let mut requires: IndexMap<String, Vec<Link>> = IndexMap::new(); let mut remove_packages: Vec<String> = vec![]; - let installed_repo_base: Box<dyn crate::repository::RepositoryInterface> = if input + let installed_repo_base: crate::repository::RepositoryInterfaceHandle = if input .get_option("lock") .as_bool() .unwrap_or(false) @@ -72,7 +72,7 @@ impl CheckPlatformReqsCommand { "<info>Checking {}platform requirements using the lock file</info>", if no_dev { "non-dev " } else { "" } )); - Box::new( + crate::repository::RepositoryInterfaceHandle::new( composer .get_locker() .borrow_mut() @@ -87,21 +87,21 @@ impl CheckPlatformReqsCommand { "<warning>No vendor dir present, checking {}platform requirements from the lock file</warning>", if no_dev { "non-dev " } else { "" } )); - Box::new( + crate::repository::RepositoryInterfaceHandle::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(); + remove_packages = local_repo.get_dev_package_names(); } io.write_error(&format!( "<info>Checking {}platform requirements for packages in the vendor dir</info>", if no_dev { "non-dev " } else { "" } )); - local_repo.clone_box() + local_repo.clone() } }; @@ -115,8 +115,10 @@ impl CheckPlatformReqsCommand { } let root_pkg_repo = RootPackageRepository::new(composer.get_package().clone()); - let installed_repo = - InstalledRepository::new(vec![installed_repo_base, Box::new(root_pkg_repo)]); + let installed_repo = InstalledRepository::new(vec![ + installed_repo_base, + crate::repository::RepositoryInterfaceHandle::new(root_pkg_repo), + ]); for package in installed_repo.get_packages() { if remove_packages.contains(&package.get_name().to_string()) { @@ -134,8 +136,11 @@ impl CheckPlatformReqsCommand { requires_sorted.sort_by(|a, b| a.0.cmp(&b.0)); let installed_repo_with_platform = InstalledRepository::new(vec![ - Box::new(installed_repo), - Box::new(PlatformRepository::new(vec![], indexmap::IndexMap::new())?), + crate::repository::RepositoryInterfaceHandle::new(installed_repo), + crate::repository::RepositoryInterfaceHandle::new(PlatformRepository::new( + vec![], + indexmap::IndexMap::new(), + )?), ]); let mut results: Vec<CheckResult> = vec![]; diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index d5a8a0c..c669aa5 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -304,7 +304,7 @@ impl CreateProjectCommand { let _ = &composer_json_repositories_config; let placeholder_existing: IndexMap< String, - Box<dyn crate::repository::RepositoryInterface>, + crate::repository::RepositoryInterfaceHandle, > = IndexMap::new(); let name = RepositoryFactory::generate_repository_name( &PhpMixed::Int(index as i64), @@ -737,12 +737,14 @@ impl CreateProjectCommand { if repositories.is_none() { // TODO(phase-b): default_repos needs &mut RepositoryManager but we hold &RepositoryManager. let _ = rm; - repository_set.add_repository(Box::new(CompositeRepository::new( - RepositoryFactory::default_repos(Some(io.clone()), Some(config.clone()), None)? - .into_iter() - .map(|(_, v)| v) - .collect(), - ))); + repository_set.add_repository(crate::repository::RepositoryInterfaceHandle::new( + CompositeRepository::new( + RepositoryFactory::default_repos(Some(io.clone()), Some(config.clone()), None)? + .into_iter() + .map(|(_, v)| v) + .collect(), + ), + ))?; } else { for repo in repositories.unwrap() { let mut repo_config = diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs index c64fc39..945d1e9 100644 --- a/crates/shirabe/src/command/diagnose_command.rs +++ b/crates/shirabe/src/command/diagnose_command.rs @@ -946,7 +946,7 @@ impl DiagnoseCommand { self.http_downloader.clone().unwrap(), None, )?; - let composer_repo_as_repo: Box<dyn crate::repository::RepositoryInterface> = + let composer_repo_as_repo: crate::repository::RepositoryInterfaceHandle = todo!("ComposerRepository as RepositoryInterface"); repo_set.add_repository(composer_repo_as_repo)?; diff --git a/crates/shirabe/src/command/fund_command.rs b/crates/shirabe/src/command/fund_command.rs index 340424f..9d5720e 100644 --- a/crates/shirabe/src/command/fund_command.rs +++ b/crates/shirabe/src/command/fund_command.rs @@ -60,7 +60,7 @@ impl FundCommand { repository_manager .get_repositories() .iter() - .map(|r| r.clone_box()) + .map(|r| r.clone()) .collect(), ); let mut fundings: IndexMap<String, IndexMap<String, Vec<String>>> = IndexMap::new(); diff --git a/crates/shirabe/src/command/home_command.rs b/crates/shirabe/src/command/home_command.rs index c048bfd..0038add 100644 --- a/crates/shirabe/src/command/home_command.rs +++ b/crates/shirabe/src/command/home_command.rs @@ -203,15 +203,15 @@ impl HomeCommand { } } - fn initialize_repos(&mut self) -> Result<Vec<Box<dyn RepositoryInterface>>> { + fn initialize_repos(&mut self) -> Result<Vec<crate::repository::RepositoryInterfaceHandle>> { 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(), - ))); + let mut repos: Vec<crate::repository::RepositoryInterfaceHandle> = vec![]; + repos.push(crate::repository::RepositoryInterfaceHandle::new( + RootPackageRepository::new(composer.get_package().clone()), + )); // TODO(phase-b): get_local_repository / get_repositories return shared refs; needs Rc<dyn ...> migration return Ok(repos); } diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs index 730658c..38e620b 100644 --- a/crates/shirabe/src/command/init_command.rs +++ b/crates/shirabe/src/command/init_command.rs @@ -455,8 +455,10 @@ impl InitCommand { let mut repo_manager = RepositoryFactory::manager(io.clone(), &config, None, None, None)?; - let mut repos: Vec<Box<dyn crate::repository::RepositoryInterface>> = - vec![Box::new(PlatformRepository::new(vec![], IndexMap::new())?)]; + let mut repos: Vec<crate::repository::RepositoryInterfaceHandle> = + vec![crate::repository::RepositoryInterfaceHandle::new( + PlatformRepository::new(vec![], IndexMap::new())?, + )]; let mut create_default_packagist_repo = true; for repo in &repositories { let repo_config = diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs index d0574c6..d656d83 100644 --- a/crates/shirabe/src/command/package_discovery_trait.rs +++ b/crates/shirabe/src/command/package_discovery_trait.rs @@ -56,9 +56,11 @@ pub trait PackageDiscoveryTrait { fn get_repos(&mut self) -> &CompositeRepository { if self.get_repos_mut().is_none() { // PHP: array_merge([new PlatformRepository], RepositoryFactory::defaultReposWithDefaultManager($this->getIO())) - let mut repos: Vec<Box<dyn crate::repository::RepositoryInterface>> = vec![ + let mut repos: Vec<crate::repository::RepositoryInterfaceHandle> = vec![ // TODO(phase-b): PlatformRepository::new() signature - Box::new(todo!("PlatformRepository::new()") as PlatformRepository), + crate::repository::RepositoryInterfaceHandle::new::<PlatformRepository>(todo!( + "PlatformRepository::new()" + )), ]; let io_owned: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = self.get_io(); for (_, repo) in RepositoryFactory::default_repos_with_default_manager(io_owned) diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index ad4739b..29e8129 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -219,7 +219,7 @@ impl ReinstallCommand { uninstall_operations, install_operations, dev_mode, - local_repo, + local_repo.clone(), &installation_manager, ); // installation_manager.execute(local_repo_mut, uninstall_ops_boxed, dev_mode, true, false); diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index bf5f159..93ec469 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -261,10 +261,12 @@ impl RequireCommand { .unwrap_or_default(); // initialize self.repos as it is used by the PackageDiscoveryTrait let platform_repo = PlatformRepository::new(vec![], platform_overrides_map)?; - let mut combined: Vec<Box<dyn crate::repository::RepositoryInterface>> = vec![ - // TODO(phase-b): PlatformRepository should be shared via Rc; use placeholder until - // CompositeRepository accepts shared references - Box::new(todo!("share platform_repo with PlatformRepository") as PlatformRepository), + let mut combined: Vec<crate::repository::RepositoryInterfaceHandle> = vec![ + // TODO(phase-c): share this platform_repo as a handle instead of constructing a + // separate one; PlatformRepository is held by value here for the requirement below. + crate::repository::RepositoryInterfaceHandle::new::<PlatformRepository>(todo!( + "share platform_repo with PlatformRepository" + )), ]; for _repo in repos { // TODO(phase-b): repos are borrowed from RepositoryManager; need to take ownership diff --git a/crates/shirabe/src/command/search_command.rs b/crates/shirabe/src/command/search_command.rs index 2033cfc..5d6a31b 100644 --- a/crates/shirabe/src/command/search_command.rs +++ b/crates/shirabe/src/command/search_command.rs @@ -10,6 +10,7 @@ use crate::plugin::CommandEvent; use crate::plugin::PluginEvents; use crate::repository::CompositeRepository; use crate::repository::PlatformRepository; +use crate::repository::RepositoryInterfaceHandle; use crate::repository::repository_interface::{self, RepositoryInterface}; use anyhow::Result; use indexmap::IndexMap; @@ -78,18 +79,22 @@ impl SearchCommand { self.create_composer_instance(input, io_box, 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 + let local_repo = composer_ref + .get_repository_manager() + .borrow() + .get_local_repository(); + let installed_repo = CompositeRepository::new(vec![ + local_repo, + RepositoryInterfaceHandle::new(platform_repo), + ]); + let mut all_repos: Vec<RepositoryInterfaceHandle> = + vec![RepositoryInterfaceHandle::new(installed_repo)]; for r in composer_ref .get_repository_manager() .borrow() .get_repositories() { - all_repos.push(r.clone_box()); + all_repos.push(r.clone()); } let repos = CompositeRepository::new(all_repos); diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs index b72a986..2b2dd94 100644 --- a/crates/shirabe/src/command/show_command.rs +++ b/crates/shirabe/src/command/show_command.rs @@ -40,6 +40,7 @@ use crate::repository::InstalledRepository; use crate::repository::PlatformRepository; use crate::repository::RepositoryFactory; use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; use crate::repository::RepositorySet; use crate::repository::RepositoryUtils; use crate::repository::RootPackageRepository; @@ -198,13 +199,13 @@ impl ShowCommand { let make_platform_repo = || -> anyhow::Result<PlatformRepository> { PlatformRepository::new(vec![], platform_overrides.clone()) }; - let mut locked_repo: Option<Box<dyn RepositoryInterface>> = None; + let mut locked_repo: Option<RepositoryInterfaceHandle> = None; // The single-package $package binding from PHP gets surfaced here. let mut single_package: Option<crate::package::CompletePackageInterfaceHandle> = None; let mut versions_map: IndexMap<String, String> = IndexMap::new(); - let installed_repo: Box<InstalledRepository>; - let repos: Box<dyn RepositoryInterface>; + let installed_repo: RepositoryInterfaceHandle; + let repos: RepositoryInterfaceHandle; if input.get_option("self").as_bool() == Some(true) && input.get_option("installed").as_bool() != Some(true) @@ -227,95 +228,95 @@ impl ShowCommand { } .into()); } - installed_repo = Box::new(InstalledRepository::new(vec![Box::new( - RootPackageRepository::new(package.clone()), - )])); - repos = Box::new(InstalledRepository::new(vec![Box::new( - RootPackageRepository::new(package.clone()), - )])); + installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ + RepositoryInterfaceHandle::new(RootPackageRepository::new(package.clone())), + ])); + repos = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ + RepositoryInterfaceHandle::new(RootPackageRepository::new(package.clone())), + ])); // TODO(phase-c): need to convert the root package handle to a CompletePackageInterfaceHandle single_package = todo!("convert package to CompletePackageInterfaceHandle"); } else if input.get_option("platform").as_bool() == Some(true) { - installed_repo = Box::new(InstalledRepository::new(vec![Box::new( - make_platform_repo()?, - )])); - repos = Box::new(InstalledRepository::new(vec![Box::new( - make_platform_repo()?, - )])); + installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ + RepositoryInterfaceHandle::new(make_platform_repo()?), + ])); + repos = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ + RepositoryInterfaceHandle::new(make_platform_repo()?), + ])); } else if input.get_option("available").as_bool() == Some(true) { - let mut ir = InstalledRepository::new(vec![Box::new(make_platform_repo()?)]); + let mut ir = InstalledRepository::new(vec![RepositoryInterfaceHandle::new( + make_platform_repo()?, + )]); if let Some(ref composer) = composer { let composer = crate::command::composer_full(composer); - repos = Box::new(CompositeRepository::new( + repos = RepositoryInterfaceHandle::new(CompositeRepository::new( composer .get_repository_manager() .borrow() .get_repositories() .iter() - .map(|r| r.clone_box()) + .map(|r| r.clone()) .collect(), )); ir.add_repository( composer .get_repository_manager() .borrow() - .get_local_repository() - .clone_box(), - ); - installed_repo = Box::new(ir); + .get_local_repository(), + )?; + installed_repo = RepositoryInterfaceHandle::new(ir); } else { let default_repos = RepositoryFactory::default_repos_with_default_manager(self.get_io())?; let names: Vec<String> = default_repos.keys().cloned().collect(); - repos = Box::new(CompositeRepository::new( + repos = RepositoryInterfaceHandle::new(CompositeRepository::new( default_repos.into_values().collect(), )); self.get_io().write_error(&format!( "No composer.json found in the current directory, showing available packages from {}", names.join(", ") )); - installed_repo = Box::new(ir); + installed_repo = RepositoryInterfaceHandle::new(ir); } } else if input.get_option("all").as_bool() == Some(true) && composer.is_some() { let mut composer_ref = crate::command::composer_full_mut(composer.as_ref().unwrap()); - let local_repo_cloned = composer_ref + let local_repo = composer_ref .get_repository_manager() .borrow() - .get_local_repository() - .clone_box(); + .get_local_repository(); 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![ - lr.clone_box(), - local_repo_cloned, - Box::new(make_platform_repo()?), + let lr_handle = RepositoryInterfaceHandle::new(locker.get_locked_repository(true)?); + installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ + lr_handle.clone(), + local_repo, + RepositoryInterfaceHandle::new(make_platform_repo()?), ])); - // TODO(phase-b): wrap lr (LockArrayRepository) as Box<dyn RepositoryInterface> - locked_repo = Some(todo!("share lr as Box<dyn RepositoryInterface>")); - let _ = lr; + locked_repo = Some(lr_handle); } else { - installed_repo = Box::new(InstalledRepository::new(vec![ - local_repo_cloned, - Box::new(make_platform_repo()?), + installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ + local_repo, + RepositoryInterfaceHandle::new(make_platform_repo()?), ])); } - let mut composite_input: Vec<Box<dyn RepositoryInterface>> = vec![Box::new( - FilterRepository::new(installed_repo.clone_box(), { - let mut m = IndexMap::new(); - m.insert("canonical".to_string(), PhpMixed::Bool(false)); - m - })?, - )]; + let mut composite_input: Vec<RepositoryInterfaceHandle> = + vec![RepositoryInterfaceHandle::new(FilterRepository::new( + installed_repo.clone(), + { + let mut m = IndexMap::new(); + m.insert("canonical".to_string(), PhpMixed::Bool(false)); + m + }, + )?)]; for r in composer_ref .get_repository_manager() .borrow() .get_repositories() { - composite_input.push(r.clone_box()); + composite_input.push(r.clone()); } - repos = Box::new(CompositeRepository::new(composite_input)); + repos = RepositoryInterfaceHandle::new(CompositeRepository::new(composite_input)); } else if input.get_option("all").as_bool() == Some(true) { let default_repos = RepositoryFactory::default_repos_with_default_manager(self.get_io())?; @@ -324,15 +325,14 @@ impl ShowCommand { "No composer.json found in the current directory, showing available packages from {}", names.join(", ") )); - installed_repo = Box::new(InstalledRepository::new(vec![Box::new( - make_platform_repo()?, - )])); - let mut composite_input: Vec<Box<dyn RepositoryInterface>> = - vec![installed_repo.clone_box()]; + installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ + RepositoryInterfaceHandle::new(make_platform_repo()?), + ])); + let mut composite_input: Vec<RepositoryInterfaceHandle> = vec![installed_repo.clone()]; for (_k, v) in default_repos.into_iter() { composite_input.push(v); } - repos = Box::new(CompositeRepository::new(composite_input)); + repos = RepositoryInterfaceHandle::new(CompositeRepository::new(composite_input)); } else if input.get_option("locked").as_bool() == Some(true) { if composer.is_none() || !crate::command::composer_full_mut(composer.as_ref().unwrap()) @@ -349,18 +349,19 @@ impl ShowCommand { 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 = + let lr = locker.get_locked_repository(input.get_option("no-dev").as_bool() != Some(true))?; + let lr_handle = RepositoryInterfaceHandle::new(lr); if input.get_option("self").as_bool() == Some(true) { // TODO(phase-b): LockArrayRepository needs add_package via WritableRepositoryInterface; // skipping the insertion here keeps compile clean. - let _ = &mut lr; + let _ = &lr_handle; } - installed_repo = Box::new(InstalledRepository::new(vec![lr.clone_box()])); - repos = Box::new(InstalledRepository::new(vec![lr.clone_box()])); - // TODO(phase-b): wrap lr (LockArrayRepository) as Box<dyn RepositoryInterface> - locked_repo = Some(todo!("share lr as Box<dyn RepositoryInterface>")); - let _ = lr; + installed_repo = + RepositoryInterfaceHandle::new(InstalledRepository::new(vec![lr_handle.clone()])); + repos = + RepositoryInterfaceHandle::new(InstalledRepository::new(vec![lr_handle.clone()])); + locked_repo = Some(lr_handle); } else { // --installed / default case // TODO(phase-b): PHP shares the Composer object by reference. Phase B @@ -382,13 +383,13 @@ impl ShowCommand { }; let root_pkg = composer_local.get_package(); - let root_repo: Box<dyn RepositoryInterface> = + let root_repo: RepositoryInterfaceHandle = if input.get_option("self").as_bool() == Some(true) { - Box::new(RootPackageRepository::new( + RepositoryInterfaceHandle::new(RootPackageRepository::new( composer_local.get_package().clone(), )) } else { - Box::new(InstalledArrayRepository::new()?) + RepositoryInterfaceHandle::new(InstalledArrayRepository::new()?) }; if input.get_option("no-dev").as_bool() == Some(true) { let local_packages = composer_local @@ -404,23 +405,28 @@ impl ShowCommand { ); let cloned: Vec<crate::package::PackageInterfaceHandle> = packages.into_iter().map(|p| p.into()).collect(); - installed_repo = Box::new(InstalledRepository::new(vec![ - root_repo.clone_box(), - Box::new(InstalledArrayRepository::new_with_packages(cloned)?), + installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ + root_repo.clone(), + RepositoryInterfaceHandle::new(InstalledArrayRepository::new_with_packages( + cloned, + )?), ])); - repos = Box::new(InstalledRepository::new(vec![ + repos = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ root_repo, - Box::new(InstalledArrayRepository::new_with_packages(Vec::new())?), + RepositoryInterfaceHandle::new(InstalledArrayRepository::new_with_packages( + Vec::new(), + )?), ])); } else { 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(), + installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ + root_repo.clone(), + lr.clone(), ])); - repos = Box::new(InstalledRepository::new(vec![root_repo, lr.clone_box()])); + repos = + RepositoryInterfaceHandle::new(InstalledRepository::new(vec![root_repo, lr])); } if installed_repo.get_packages().is_empty() { @@ -475,8 +481,12 @@ impl ShowCommand { versions_map.insert(pkg.get_pretty_version(), pkg.get_version()); } else if let Some(ref pf) = package_filter { if !pf.contains('*') { - let (matched_package, vers) = - self.get_package(&*installed_repo, &*repos, pf, input.get_argument("version"))?; + let (matched_package, vers) = self.get_package( + &*installed_repo.borrow(), + &repos, + pf, + input.get_argument("version"), + )?; if let Some(ref pkg) = matched_package { if input.get_option("direct").as_bool() == Some(true) { @@ -546,8 +556,11 @@ impl ShowCommand { let mut exit_code: i64 = 0; if input.get_option("tree").as_bool() == Some(true) { - let array_tree = - self.generate_package_tree(package.clone().into(), &*installed_repo, &*repos); + let array_tree = self.generate_package_tree( + package.clone().into(), + &*installed_repo.borrow(), + &repos, + ); if format == "json" { let mut wrapper: IndexMap<String, PhpMixed> = IndexMap::new(); @@ -625,14 +638,14 @@ impl ShowCommand { self.print_package_info_as_json( package.clone(), &versions_map, - &*installed_repo, + &*installed_repo.borrow(), latest_package, )?; } else { self.print_package_info( package.clone(), &versions_map, - &*installed_repo, + &*installed_repo.borrow(), latest_package, )?; } @@ -663,8 +676,8 @@ impl ShowCommand { ) { array_tree.push(self.generate_package_tree( package.clone(), - &*installed_repo, - &*repos, + &*installed_repo.borrow(), + &repos, )); } } @@ -715,13 +728,13 @@ impl ShowCommand { input.set_option("path", PhpMixed::Bool(false)); } - for repo in RepositoryUtils::flatten_repositories(repos.clone_box(), false) { + for repo in RepositoryUtils::flatten_repositories(repos.clone(), false) { // TODO(phase-b): InstalledRepository needs as_repository_interface / get_repositories // wired through; placeholder classification until then. - let r#type = if Self::same_repository(&*repo, &platform_repo) { + let r#type = if Self::same_repository(&*repo.borrow(), &platform_repo) { "platform" } else if let Some(ref lr) = locked_repo { - if Self::same_repository_dyn(&*repo, &**lr) { + if Self::same_repository_dyn(&*repo.borrow(), &*lr.borrow()) { "locked" } else { "available" @@ -776,7 +789,7 @@ impl ShowCommand { } } } - if Self::same_repository(&*repo, &platform_repo) { + if Self::same_repository(&*repo.borrow(), &platform_repo) { for (name, p) in platform_repo.get_disabled_packages() { packages .entry(type_owned.clone()) @@ -1470,8 +1483,8 @@ impl ShowCommand { /// finds a package by name and version if provided pub(crate) fn get_package( &mut self, - installed_repo: &InstalledRepository, - repos: &dyn RepositoryInterface, + installed_repo: &dyn RepositoryInterface, + repos: &RepositoryInterfaceHandle, name: &str, version: PhpMixed, ) -> anyhow::Result<( @@ -1498,7 +1511,7 @@ impl ShowCommand { IndexMap::new(), ); repository_set.allow_installed_repositories(true); - repository_set.add_repository(repos.clone_box())?; + repository_set.add_repository(repos.clone())?; let mut matched_package: Option<crate::package::PackageInterfaceHandle> = None; let mut versions: IndexMap<String, String> = IndexMap::new(); @@ -1555,7 +1568,7 @@ impl ShowCommand { &mut self, package: CompletePackageInterfaceHandle, versions: &IndexMap<String, String>, - installed_repo: &InstalledRepository, + installed_repo: &dyn RepositoryInterface, latest_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<()> { self.print_meta(package.clone(), versions, installed_repo, latest_package); @@ -1585,7 +1598,7 @@ impl ShowCommand { &mut self, package: CompletePackageInterfaceHandle, versions: &IndexMap<String, String>, - installed_repo: &InstalledRepository, + installed_repo: &dyn RepositoryInterface, latest_package: Option<PackageInterfaceHandle>, ) { let is_installed_package = !PlatformRepository::is_platform_package(&package.get_name()) @@ -1743,7 +1756,7 @@ impl ShowCommand { &mut self, package: CompletePackageInterfaceHandle, versions: &IndexMap<String, String>, - installed_repo: &InstalledRepository, + installed_repo: &dyn RepositoryInterface, ) { let mut versions_keys: Vec<String> = versions.keys().cloned().collect(); versions_keys = Semver::rsort(versions_keys); @@ -1830,7 +1843,7 @@ impl ShowCommand { &mut self, package: CompletePackageInterfaceHandle, versions: &IndexMap<String, String>, - installed_repo: &InstalledRepository, + installed_repo: &dyn RepositoryInterface, latest_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<()> { let mut json: IndexMap<String, PhpMixed> = IndexMap::new(); @@ -2268,8 +2281,8 @@ impl ShowCommand { pub(crate) fn generate_package_tree( &mut self, package: PackageInterfaceHandle, - installed_repo: &InstalledRepository, - remote_repos: &dyn RepositoryInterface, + installed_repo: &dyn RepositoryInterface, + remote_repos: &RepositoryInterfaceHandle, ) -> IndexMap<String, PhpMixed> { let requires = { let mut r: IndexMap<String, Link> = package.get_requires().clone(); @@ -2425,8 +2438,8 @@ impl ShowCommand { &mut self, name: &str, link: &Link, - installed_repo: &InstalledRepository, - remote_repos: &dyn RepositoryInterface, + installed_repo: &dyn RepositoryInterface, + remote_repos: &RepositoryInterfaceHandle, packages_in_tree: &[PhpMixed], ) -> anyhow::Result<Vec<IndexMap<String, PhpMixed>>> { let mut children: Vec<IndexMap<String, PhpMixed>> = Vec::new(); @@ -2693,13 +2706,13 @@ impl ShowCommand { IndexMap::new(), IndexMap::new(), ); - rs.add_repository(Box::new(CompositeRepository::new( + rs.add_repository(RepositoryInterfaceHandle::new(CompositeRepository::new( composer .get_repository_manager() .borrow() .get_repositories() .iter() - .map(|r| r.clone_box()) + .map(|r| r.clone()) .collect(), )))?; self.repository_set = Some(rs); diff --git a/crates/shirabe/src/command/suggests_command.rs b/crates/shirabe/src/command/suggests_command.rs index aa1ae15..9105474 100644 --- a/crates/shirabe/src/command/suggests_command.rs +++ b/crates/shirabe/src/command/suggests_command.rs @@ -8,6 +8,7 @@ use crate::io::IOInterface; use crate::repository::InstalledRepository; use crate::repository::PlatformRepository; use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; use crate::repository::RootPackageRepository; use anyhow::Result; use indexmap::IndexMap; @@ -49,8 +50,10 @@ impl SuggestsCommand { let root_package_handle: crate::package::RootPackageInterfaceHandle = composer.get_package().clone(); - let mut installed_repos: Vec<Box<dyn RepositoryInterface>> = - vec![Box::new(RootPackageRepository::new(root_package_handle))]; + let mut installed_repos: Vec<RepositoryInterfaceHandle> = + vec![RepositoryInterfaceHandle::new(RootPackageRepository::new( + root_package_handle, + ))]; if composer.get_locker().borrow_mut().is_locked() { // TODO(phase-b): get_platform_overrides returns IndexMap<String, String>; PlatformRepository::new expects IndexMap<String, PhpMixed> @@ -60,7 +63,7 @@ impl SuggestsCommand { .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( + installed_repos.push(RepositoryInterfaceHandle::new(PlatformRepository::new( vec![], platform_overrides, )?)); @@ -68,13 +71,13 @@ impl SuggestsCommand { .get_locker() .borrow_mut() .get_locked_repository(!input.get_option("no-dev").as_bool().unwrap_or(false))?; - installed_repos.push(Box::new(locked_repo)); + installed_repos.push(RepositoryInterfaceHandle::new(locked_repo)); } else { // TODO(phase-b): Config::get returns PhpMixed; need to coerce to IndexMap<String, PhpMixed> let _platform_cfg = composer.get_config().borrow().get("platform"); let platform_overrides: IndexMap<String, PhpMixed> = todo!("extract IndexMap<String, PhpMixed> from PhpMixed config value"); - installed_repos.push(Box::new(PlatformRepository::new( + installed_repos.push(RepositoryInterfaceHandle::new(PlatformRepository::new( vec![], platform_overrides, )?)); @@ -82,8 +85,7 @@ impl SuggestsCommand { composer .get_repository_manager() .borrow() - .get_local_repository() - .clone_box(), + .get_local_repository(), ); } diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs index f879283..f1b05af 100644 --- a/crates/shirabe/src/command/update_command.rs +++ b/crates/shirabe/src/command/update_command.rs @@ -635,13 +635,12 @@ impl UpdateCommand { .get_repository_manager() .borrow() .get_repositories(); - let _ = |repository: &Box<dyn RepositoryInterface>| -> bool { - repository - .as_any() - .downcast_ref::<PlatformRepository>() - .is_none() + let _ = |repository: &crate::repository::RepositoryInterfaceHandle| -> bool { + !repository.is::<PlatformRepository>() }; - repository_set.add_repository(Box::new(CompositeRepository::new(Vec::new())))?; + repository_set.add_repository(crate::repository::RepositoryInterfaceHandle::new( + CompositeRepository::new(Vec::new()), + ))?; let _ = array_filter::<i64, fn(&i64) -> bool>; VersionSelector::new(repository_set, None) diff --git a/crates/shirabe/src/dependency_resolver/pool_builder.rs b/crates/shirabe/src/dependency_resolver/pool_builder.rs index 489b358..273d19f 100644 --- a/crates/shirabe/src/dependency_resolver/pool_builder.rs +++ b/crates/shirabe/src/dependency_resolver/pool_builder.rs @@ -34,8 +34,10 @@ use crate::package::version::StabilityFilter; use crate::plugin::PluginEvents; use crate::plugin::PrePoolCreateEvent; use crate::repository::CanonicalPackagesTrait; +use crate::repository::LockArrayRepository; use crate::repository::PlatformRepository; use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; use crate::repository::RootPackageRepository; #[derive(Debug)] @@ -134,7 +136,7 @@ impl PoolBuilder { pub fn build_pool( &mut self, - repositories: Vec<Box<dyn RepositoryInterface>>, + repositories: Vec<RepositoryInterfaceHandle>, request: &mut Request, ) -> anyhow::Result<Pool> { self.restricted_packages_list = if request.get_restricted_packages().is_some() { @@ -206,13 +208,9 @@ impl PoolBuilder { // TODO in how far can we do the above for conflicts? It's more tricky cause conflicts can be limited to // specific versions while replace is a conflict with all versions of the name - // TODO(phase-c): package->repository back-reference not yet on handles - let in_root_or_platform = None - .map(|r: &dyn RepositoryInterface| { - r.as_any().is::<RootPackageRepository>() - || r.as_any().is::<PlatformRepository>() - }) - .unwrap_or(false); + let in_root_or_platform = package.get_repository().map_or(false, |r| { + r.is::<RootPackageRepository>() || r.is::<PlatformRepository>() + }); if in_root_or_platform || StabilityFilter::is_package_acceptable( &self.acceptable_stabilities, @@ -446,7 +444,7 @@ impl PoolBuilder { fn load_packages_marked_for_loading( &mut self, request: &mut Request, - repositories: &Vec<Box<dyn RepositoryInterface>>, + repositories: &Vec<RepositoryInterfaceHandle>, ) -> anyhow::Result<()> { let to_remove: Vec<String> = self .packages_to_load @@ -493,16 +491,14 @@ impl PoolBuilder { for (repo_index, repository) in repositories.iter().enumerate() { // these repos have their packages fixed or locked if they need to be loaded so we // never need to load anything else from them - let is_locked_repo = request - .get_locked_repository() - .map(|lr| { - std::ptr::eq( - lr as *const _ as *const u8, - repository.as_ref() as *const _ as *const u8, - ) - }) - .unwrap_or(false); - if repository.as_any().is::<PlatformRepository>() || is_locked_repo { + // TODO(phase-c): PHP compares `$request->getLockedRepository() === $repository` by + // strict identity, but `Request.locked_repository` is held by value, not as a handle. + // This approximates the check by matching any `LockArrayRepository` when the request + // has a locked repository set. Tighten to `ptr_eq` once `Request` stores the locked + // repository as `RepositoryInterfaceHandle`. + let is_locked_repo = + request.get_locked_repository().is_some() && repository.is::<LockArrayRepository>(); + if repository.is::<PlatformRepository>() || is_locked_repo { continue; } @@ -611,7 +607,7 @@ impl PoolBuilder { fn load_package( &mut self, request: &mut Request, - repositories: &Vec<Box<dyn RepositoryInterface>>, + repositories: &Vec<RepositoryInterfaceHandle>, package: BasePackageHandle, propagate_update: bool, ) -> anyhow::Result<()> { @@ -872,7 +868,7 @@ impl PoolBuilder { fn unlock_package( &mut self, request: &mut Request, - repositories: &Vec<Box<dyn RepositoryInterface>>, + repositories: &Vec<RepositoryInterfaceHandle>, name: &str, ) -> anyhow::Result<()> { let skipped: Vec<PackageInterfaceHandle> = self @@ -1029,15 +1025,19 @@ impl PoolBuilder { fn remove_loaded_package( &mut self, _request: &Request, - repositories: &Vec<Box<dyn RepositoryInterface>>, + repositories: &Vec<RepositoryInterfaceHandle>, package: BasePackageHandle, index: i64, ) { - let repos_box: Vec<Box<dyn RepositoryInterface>> = - repositories.iter().map(|r| r.clone_box()).collect(); - let _ = &repos_box; - // TODO(phase-c): package->repository back-reference not yet on handles - let repo_index: i64 = -1; + let repo_index: i64 = package + .get_repository() + .and_then(|pkg_repo| { + repositories + .iter() + .position(|r| r.ptr_eq(&pkg_repo)) + .map(|i| i as i64) + }) + .unwrap_or(-1); if repo_index >= 0 { if let Some(repo_map) = self.loaded_per_repo.get_mut(&repo_index) { @@ -1115,7 +1115,7 @@ impl PoolBuilder { fn run_security_advisory_filter( &mut self, pool: Pool, - repositories: &Vec<Box<dyn RepositoryInterface>>, + repositories: &Vec<RepositoryInterfaceHandle>, request: &Request, ) -> Pool { if self.security_advisory_pool_filter.is_none() { @@ -1127,8 +1127,7 @@ impl PoolBuilder { let before = microtime(true); let total = pool.get_packages().len() as f64; - let repos_owned: Vec<Box<dyn RepositoryInterface>> = - repositories.iter().map(|r| r.clone_box()).collect(); + let repos_owned: Vec<RepositoryInterfaceHandle> = repositories.iter().cloned().collect(); let pool = self.security_advisory_pool_filter .as_mut() diff --git a/crates/shirabe/src/dependency_resolver/problem.rs b/crates/shirabe/src/dependency_resolver/problem.rs index 58d47f1..740909f 100644 --- a/crates/shirabe/src/dependency_resolver/problem.rs +++ b/crates/shirabe/src/dependency_resolver/problem.rs @@ -1127,11 +1127,14 @@ impl Problem { if available.len() > 0 { let mut selected: Option<&BasePackageHandle> = None; - // TODO(phase-c): the handle does not expose get_repository (a `RefCell`-borrowed - // back-reference); preferring the package from a PlatformRepository needs repository - // back-references on handles. Falling back to the first candidate for now. for pkg in &available { - let _ = pkg; + if pkg + .get_repository() + .map_or(false, |r| r.is::<PlatformRepository>()) + { + selected = Some(pkg); + break; + } } if selected.is_none() { selected = available.first(); @@ -1240,13 +1243,23 @@ impl Problem { reason: &str, constraint: Option<&AnyConstraint>, ) -> (String, String) { - // TODO(phase-c): selecting the next repository's packages relies on each package's - // repository back-reference, which the handle does not yet expose (phase-c handoff - // item #1). Both `next_repo_packages` and `next_repo` are blocked on that decision. - let _ = all_repos_packages; - let next_repo_packages: Vec<BasePackageHandle> = Vec::new(); - let next_repo: Box<dyn crate::repository::RepositoryInterface> = - todo!("repository back-reference on handle pending (phase-c handoff item #1)"); + let mut next_repo_packages: Vec<BasePackageHandle> = Vec::new(); + let mut next_repo: Option<crate::repository::RepositoryInterfaceHandle> = None; + for package in all_repos_packages { + let pkg_repo = package.get_repository(); + let same_repo = match (&next_repo, &pkg_repo) { + (None, _) => true, + (Some(nr), Some(pr)) => nr.ptr_eq(pr), + _ => false, + }; + if same_repo { + next_repo_packages.push(package.clone()); + next_repo = pkg_repo; + } else { + break; + } + } + let next_repo = next_repo.expect("next_repo must be set"); if higher_repo_packages.len() > 0 { let top_package = higher_repo_packages.first().unwrap(); @@ -1274,11 +1287,7 @@ impl Problem { } } - if next_repo - .as_any() - .downcast_ref::<LockArrayRepository>() - .is_some() - { + if next_repo.is::<LockArrayRepository>() { let singular = higher_repo_packages.len() == 1; let mut suggestion = format!( @@ -1351,10 +1360,11 @@ impl Problem { constraint, false ), - // TODO(phase-c): the higher repo's name needs the handle's repository - // back-reference (phase-c handoff item #1); unreachable until `next_repo` above - // is resolved. - String::new(), + higher_repo_packages + .first() + .and_then(|p| p.get_repository()) + .map(|r| r.get_repo_name()) + .unwrap_or_default(), reason ), ) diff --git a/crates/shirabe/src/dependency_resolver/security_advisory_pool_filter.rs b/crates/shirabe/src/dependency_resolver/security_advisory_pool_filter.rs index 785e758..ec224b4 100644 --- a/crates/shirabe/src/dependency_resolver/security_advisory_pool_filter.rs +++ b/crates/shirabe/src/dependency_resolver/security_advisory_pool_filter.rs @@ -6,7 +6,6 @@ use crate::advisory::PartialSecurityAdvisory; use crate::dependency_resolver::Pool; use crate::dependency_resolver::Request; use crate::package::BasePackageHandle; -use crate::repository::RepositoryInterface; use indexmap::IndexMap; use shirabe_semver::constraint::AnyConstraint; use shirabe_semver::constraint::SimpleConstraint; @@ -28,7 +27,7 @@ impl SecurityAdvisoryPoolFilter { pub fn filter( &self, pool: Pool, - repositories: Vec<Box<dyn RepositoryInterface>>, + repositories: Vec<crate::repository::RepositoryInterfaceHandle>, request: &Request, ) -> Pool { // TODO(phase-c): port the filter() body. Blockers: diff --git a/crates/shirabe/src/downloader/perforce_downloader.rs b/crates/shirabe/src/downloader/perforce_downloader.rs index 4a54511..fcea57c 100644 --- a/crates/shirabe/src/downloader/perforce_downloader.rs +++ b/crates/shirabe/src/downloader/perforce_downloader.rs @@ -90,10 +90,10 @@ impl PerforceDownloader { return; } - let package_rc = package.as_rc().borrow(); - let repository = package_rc.as_package_interface().get_repository(); + let repository = package.get_repository(); let repo_config: Option<IndexMap<String, PhpMixed>> = if let Some(repo) = repository { - if let Some(vcs_repo) = repo.as_any().downcast_ref::<VcsRepository>() { + let repo_ref = repo.borrow(); + if let Some(vcs_repo) = repo_ref.as_any().downcast_ref::<VcsRepository>() { Some(self.get_repo_config(vcs_repo)) } else { None @@ -101,7 +101,6 @@ impl PerforceDownloader { } else { None }; - drop(package_rc); self.perforce = Some(Perforce::create( repo_config.unwrap_or_default(), url, diff --git a/crates/shirabe/src/downloader/svn_downloader.rs b/crates/shirabe/src/downloader/svn_downloader.rs index 9f5d2ed..98456a4 100644 --- a/crates/shirabe/src/downloader/svn_downloader.rs +++ b/crates/shirabe/src/downloader/svn_downloader.rs @@ -70,10 +70,10 @@ impl SvnDownloader { let r#ref = package.get_source_reference(); { - let package_ref = package.as_rc().borrow(); - let repo = package_ref.as_package_interface().get_repository(); + let repo = package.get_repository(); if let Some(repo) = repo { - if let Some(vcs_repo) = repo.as_any().downcast_ref::<VcsRepository>() { + let repo_ref = repo.borrow(); + if let Some(vcs_repo) = repo_ref.as_any().downcast_ref::<VcsRepository>() { let repo_config = vcs_repo.get_repo_config(); if repo_config.contains_key("svn-cache-credentials") { if let Some(val) = repo_config diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index 80f1548..f2fa99b 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -886,7 +886,7 @@ impl Factory { let fs = process .map(|p| std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(Some(p.clone()))))); - rm.set_local_repository(Box::new( + rm.set_local_repository(crate::repository::RepositoryInterfaceHandle::new( InstalledFilesystemRepository::new( JsonFile::new( format!("{}/composer/installed.json", vendor_dir), diff --git a/crates/shirabe/src/installer.rs b/crates/shirabe/src/installer.rs index 14e75c0..f0c07bf 100644 --- a/crates/shirabe/src/installer.rs +++ b/crates/shirabe/src/installer.rs @@ -145,7 +145,7 @@ pub struct Installer { pub(crate) update_allow_transitive_dependencies: i64, pub(crate) suggested_packages_reporter: SuggestedPackagesReporter, pub(crate) platform_requirement_filter: Box<dyn PlatformRequirementFilterInterface>, - pub(crate) additional_fixed_repository: Option<Box<dyn RepositoryInterface>>, + pub(crate) additional_fixed_repository: Option<crate::repository::RepositoryInterfaceHandle>, pub(crate) temporary_constraints: IndexMap<String, AnyConstraint>, } @@ -293,17 +293,13 @@ impl Installer { .borrow_mut() .set_prefer_dist(self.prefer_dist); - let local_repo_box = self - .repository_manager - .borrow() - .get_local_repository() - .clone_installed_repository_box(); + let local_repo = self.repository_manager.borrow().get_local_repository(); let install = self.install; let res_result: anyhow::Result<i64> = if self.update { - self.do_update(local_repo_box, install) + self.do_update(local_repo, install) } else { - self.do_install(local_repo_box, false) + self.do_install(local_repo, false) }; let res = match res_result { @@ -347,15 +343,17 @@ impl Installer { } if self.update { - let locked_repository_box = self - .locker - .borrow_mut() - .get_locked_repository(self.dev_mode)? - .clone_box(); + let locked_repository_handle = crate::repository::RepositoryInterfaceHandle::new( + self.locker + .borrow_mut() + .get_locked_repository(self.dev_mode)?, + ); let installed_repo = InstalledRepository::new(vec![ - locked_repository_box, - Box::new(self.create_platform_repo(false)), - Box::new(RootPackageRepository::new(self.package.clone())), + locked_repository_handle, + crate::repository::RepositoryInterfaceHandle::new(self.create_platform_repo(false)), + crate::repository::RepositoryInterfaceHandle::new(RootPackageRepository::new( + self.package.clone(), + )), ]); if is_fresh_install { self.suggested_packages_reporter @@ -407,9 +405,11 @@ impl Installer { self.autoload_generator .borrow_mut() .set_platform_requirement_filter(self.platform_requirement_filter.clone_box()); + let local_repo_handle = self.repository_manager.borrow().get_local_repository(); + let local_repo_ref = local_repo_handle.borrow(); self.autoload_generator.borrow_mut().dump( &*self.config.borrow(), - self.repository_manager.borrow().get_local_repository(), + local_repo_ref.as_installed_repository_interface().unwrap(), self.package.clone(), &mut *self.installation_manager.borrow_mut(), "composer", @@ -512,7 +512,7 @@ impl Installer { let repository_manager = self.repository_manager.clone(); let repository_manager = repository_manager.borrow(); for repo in repository_manager.get_repositories() { - repo_set.add_repository(repo.clone_box())?; + repo_set.add_repository(repo.clone())?; } // TODO(phase-b): Auditor::audit takes owned packages/ignore lists; need cloning @@ -555,7 +555,7 @@ impl Installer { pub(crate) fn do_update( &mut self, - local_repo: Box<dyn InstalledRepositoryInterface>, + local_repo: crate::repository::RepositoryInterfaceHandle, do_install: bool, ) -> anyhow::Result<i64> { let platform_repo = self.create_platform_repo(true); @@ -618,10 +618,16 @@ impl Installer { let repository_manager = repository_manager.borrow(); let repositories = repository_manager.get_repositories(); for repository in repositories { - repository_set.add_repository(repository.clone_box())?; + repository_set.add_repository(repository.clone())?; } - if let Some(ref lr) = locked_repository { - repository_set.add_repository(lr.clone_box())?; + if let Some(ref _lr) = locked_repository { + // TODO(phase-c): LockArrayRepository is held by value and is not Clone; share it as a + // RepositoryInterfaceHandle so it can be added here without copying. + repository_set.add_repository(crate::repository::RepositoryInterfaceHandle::new::< + crate::repository::LockArrayRepository, + >(todo!( + "share locked LockArrayRepository as a handle" + )))?; } let fixed_root_package = self.fixed_root_package.clone(); @@ -839,9 +845,11 @@ impl Installer { if self.io.is_very_verbose() && strpos(&operation.get_operation_type(), "Alias").is_none() { - // TODO(phase-c): package->repository back-reference not yet on handles - let repository: Option<&dyn RepositoryInterface> = None; - if let Some(repo) = repository { + let operation_pkg = match operation.as_update_operation() { + Some(uo) => uo.get_target_package(), + None => operation.get_package(), + }; + if let Some(repo) = operation_pkg.get_repository() { source_repo = format!(" from {}", repo.get_repo_name()); } } @@ -921,7 +929,9 @@ impl Installer { } let mut repository_set = self.create_repository_set(true, platform_repo, aliases, None); - repository_set.add_repository(Box::new(result_repo))?; + repository_set.add_repository(crate::repository::RepositoryInterfaceHandle::new( + result_repo, + ))?; let mut request = self.create_request(self.fixed_root_package.clone(), platform_repo, None); self.require_packages_for_update(&mut request, locked_repository, false)?; @@ -959,7 +969,7 @@ impl Installer { /// Whether the function is called as part of an update command or independently pub(crate) fn do_install( &mut self, - mut local_repo: Box<dyn InstalledRepositoryInterface>, + local_repo: crate::repository::RepositoryInterfaceHandle, already_solved: bool, ) -> anyhow::Result<i64> { if self @@ -1001,7 +1011,13 @@ impl Installer { &vec![], Some(&locked_repository), ); - repository_set.add_repository(locked_repository.clone_box())?; + // TODO(phase-c): LockArrayRepository is held by value and is not Clone; share it as a + // RepositoryInterfaceHandle so it can be added here without copying. + repository_set.add_repository(crate::repository::RepositoryInterfaceHandle::new::< + crate::repository::LockArrayRepository, + >(todo!( + "share locked LockArrayRepository as a handle" + )))?; // creating requirements request let fixed_root_package = self.fixed_root_package.clone(); @@ -1103,7 +1119,13 @@ impl Installer { } // TODO in how far do we need to do anything here to ensure dev packages being updated to latest in lock without version change are treated correctly? - let local_repo_transaction = LocalRepoTransaction::new(&locked_repository, &*local_repo); + let local_repo_transaction = { + let local_repo_ref = local_repo.borrow(); + LocalRepoTransaction::new( + &locked_repository, + local_repo_ref.as_installed_repository_interface().unwrap(), + ) + }; // TODO(phase-b): dispatch_installer_event takes owned Transaction, not &LocalRepoTransaction // self.event_dispatcher.borrow_mut().dispatch_installer_event( // InstallerEvents::PRE_OPERATIONS_EXEC, @@ -1172,13 +1194,17 @@ impl Installer { if self.execute_operations { local_repo.set_dev_package_names(self.locker.borrow_mut().get_dev_package_names()?); + let mut local_repo_ref = local_repo.borrow_mut(); self.installation_manager.borrow_mut().execute( - &mut *local_repo, + local_repo_ref + .as_installed_repository_interface_mut() + .unwrap(), local_repo_transaction.get_operations(), self.dev_mode, self.run_scripts, self.download_only, )?; + drop(local_repo_ref); // see https://github.com/composer/composer/issues/2764 if local_repo_transaction.get_operations().len() > 0 { @@ -1349,32 +1375,23 @@ impl Installer { if let Some(ref additional_fixed_repository) = self.additional_fixed_repository { // allow using installed repos if needed to avoid warnings about installed repositories being used in the RepositorySet // see https://github.com/composer/composer/pull/9574 - let additional_fixed_repositories: Vec<Box<dyn RepositoryInterface>> = - if let Some(composite) = additional_fixed_repository - .as_any() - .downcast_ref::<CompositeRepository>() - { - composite - .get_repositories() - .iter() - .map(|r| r.clone_box()) - .collect() + let additional_fixed_repositories: Vec<crate::repository::RepositoryInterfaceHandle> = { + let repo_ref = additional_fixed_repository.borrow(); + if let Some(composite) = repo_ref.as_any().downcast_ref::<CompositeRepository>() { + composite.get_repositories().clone() } else { - vec![additional_fixed_repository.clone_box()] - }; + drop(repo_ref); + vec![additional_fixed_repository.clone()] + } + }; for additional_fixed_repository in &additional_fixed_repositories { - // TODO(phase-b): as_installed_repository_interface not on RepositoryInterface trait - if additional_fixed_repository - .as_any() - .downcast_ref::<InstalledRepository>() - .is_some() - { + if additional_fixed_repository.is::<InstalledRepository>() { repository_set.allow_installed_repositories(true); break; } } - let _ = repository_set.add_repository(additional_fixed_repository.clone_box()); + let _ = repository_set.add_repository(additional_fixed_repository.clone()); } repository_set @@ -1457,27 +1474,22 @@ impl Installer { let provided = root_package.get_provides(); for package in fixed_packages { // skip platform packages that are provided by the root package - // TODO(phase-c): the handle does not expose get_repository (a `RefCell`-borrowed - // back-reference); detecting the platform repo needs repository back-refs on handles. - let pkg_repo_is_platform = false; + let pkg_repo_is_platform = package + .get_repository() + .map_or(false, |r| r.is::<PlatformRepository>()); + let name = package.get_name(); if !pkg_repo_is_platform - || !provided.contains_key(&package.get_name()) - || !provided - .get(&package.get_name()) - .unwrap() - .get_constraint() - .matches( - &SimpleConstraint::new( - "=".to_string(), - package.get_version().to_string(), - None, - ) - .into(), + || !provided.contains_key(&name) + || !provided.get(&name).unwrap().get_constraint().matches( + &SimpleConstraint::new( + "=".to_string(), + package.get_version().to_string(), + None, ) + .into(), + ) { - // TODO(phase-c): wire up once the platform-repo detection above is implemented. - let _ = &package; - // request.fix_package(package.clone()); + request.fix_package(package); } } @@ -1590,7 +1602,7 @@ impl Installer { packages.insert(key, new_alias_package); } } - rm.set_local_repository(Box::new( + rm.set_local_repository(crate::repository::RepositoryInterfaceHandle::new( InstalledArrayRepository::new_with_packages(packages.into_values().collect()) .expect("InstalledArrayRepository::new_with_packages should not fail"), )); @@ -1678,7 +1690,7 @@ impl Installer { pub fn set_additional_fixed_repository( &mut self, - additional_fixed_repository: Box<dyn RepositoryInterface>, + additional_fixed_repository: crate::repository::RepositoryInterfaceHandle, ) -> &mut Self { self.additional_fixed_repository = Some(additional_fixed_repository); diff --git a/crates/shirabe/src/package/alias_package.rs b/crates/shirabe/src/package/alias_package.rs index 997685b..69e0723 100644 --- a/crates/shirabe/src/package/alias_package.rs +++ b/crates/shirabe/src/package/alias_package.rs @@ -11,14 +11,13 @@ use crate::package::Link; use crate::package::PackageHandle; use crate::package::PackageInterface; use crate::package::version::VersionParser; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; #[derive(Debug)] pub struct AliasPackage { id: i64, name: String, pretty_name: String, - repository: Option<Box<dyn RepositoryInterface>>, /// @var string pub(crate) version: String, @@ -64,7 +63,6 @@ impl AliasPackage { id: -1, name: alias_name.to_lowercase(), pretty_name: alias_name, - repository: None, version, pretty_version, dev, @@ -452,12 +450,12 @@ impl PackageInterface for AliasPackage { self.alias_of.get_pretty_string() } - fn set_repository(&mut self, repository: Box<dyn RepositoryInterface>) -> anyhow::Result<()> { + fn set_repository(&mut self, repository: RepositoryInterfaceHandle) -> anyhow::Result<()> { self.alias_of.set_repository(repository) } - fn get_repository(&self) -> Option<&dyn RepositoryInterface> { - todo!("AliasPackage::get_repository cannot return a borrow across the aliasOf handle") + fn get_repository(&self) -> Option<RepositoryInterfaceHandle> { + self.alias_of.get_repository() } } @@ -486,15 +484,17 @@ impl BasePackage for AliasPackage { &mut self.pretty_name } - fn repository_opt(&self) -> Option<&dyn RepositoryInterface> { - self.repository.as_deref() + fn repository_opt(&self) -> Option<RepositoryInterfaceHandle> { + // PHP `AliasPackage::getRepository()` delegates to `$this->aliasOf->getRepository()`. + self.alias_of.get_repository() } - fn set_repository_box(&mut self, repository: Box<dyn RepositoryInterface>) { - todo!() + fn set_repository_box(&mut self, repository: RepositoryInterfaceHandle) { + let _ = self.alias_of.set_repository(repository); } - fn take_repository(&mut self) -> Option<Box<dyn RepositoryInterface>> { - todo!() + fn take_repository(&mut self) -> Option<RepositoryInterfaceHandle> { + // AliasPackage holds no repository of its own; never mutate the aliased package here. + None } } diff --git a/crates/shirabe/src/package/base_package.rs b/crates/shirabe/src/package/base_package.rs index 64a5919..ca6f171 100644 --- a/crates/shirabe/src/package/base_package.rs +++ b/crates/shirabe/src/package/base_package.rs @@ -8,7 +8,7 @@ use shirabe_php_shim::{LogicException, UnexpectedValueException, preg_quote}; use crate::package::Link; use crate::package::PackageInterface; use crate::repository::PlatformRepository; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; pub struct SupportedLinkType { pub description: &'static str, @@ -79,15 +79,9 @@ pub trait BasePackage: PackageInterface + std::fmt::Display { fn name_mut(&mut self) -> &mut String; fn pretty_name(&self) -> &str; fn pretty_name_mut(&mut self) -> &mut String; - fn repository_opt(&self) -> Option<&dyn RepositoryInterface>; - fn set_repository_box(&mut self, repository: Box<dyn RepositoryInterface>); - fn take_repository(&mut self) -> Option<Box<dyn RepositoryInterface>>; - - /// PHP `setRepository($this)` from the containing repository — Rust port marker until - /// the borrow story for repository-package back-references is finalized in phase B. - fn set_repository_self(&mut self) { - // TODO(phase-b): wire up a back-reference to the containing repository when needed. - } + fn repository_opt(&self) -> Option<RepositoryInterfaceHandle>; + fn set_repository_box(&mut self, repository: RepositoryInterfaceHandle); + fn take_repository(&mut self) -> Option<RepositoryInterfaceHandle>; // as_alias_package / as_complete_package_interface inherited from PackageInterface. @@ -104,8 +98,7 @@ pub trait BasePackage: PackageInterface + std::fmt::Display { fn is_platform(&self) -> bool { self.repository_opt() - .and_then(|r| r.as_any().downcast_ref::<PlatformRepository>()) - .is_some() + .map_or(false, |r| r.is::<PlatformRepository>()) } fn equals(&self, _package: &dyn PackageInterface) -> bool { diff --git a/crates/shirabe/src/package/complete_package.rs b/crates/shirabe/src/package/complete_package.rs index b0adf52..a598389 100644 --- a/crates/shirabe/src/package/complete_package.rs +++ b/crates/shirabe/src/package/complete_package.rs @@ -311,13 +311,13 @@ impl PackageInterface for CompletePackage { fn set_repository( &mut self, - repository: Box<dyn crate::repository::RepositoryInterface>, + repository: crate::repository::RepositoryInterfaceHandle, ) -> anyhow::Result<()> { - todo!() + self.inner.set_repository(repository) } - fn get_repository(&self) -> Option<&dyn crate::repository::RepositoryInterface> { - todo!() + fn get_repository(&self) -> Option<crate::repository::RepositoryInterfaceHandle> { + self.inner.get_repository() } fn get_binaries(&self) -> Vec<String> { diff --git a/crates/shirabe/src/package/handle.rs b/crates/shirabe/src/package/handle.rs index 931e196..e939c3a 100644 --- a/crates/shirabe/src/package/handle.rs +++ b/crates/shirabe/src/package/handle.rs @@ -273,11 +273,11 @@ macro_rules! delegate_package_interface_to_inner { } fn set_repository( &mut self, - repository: Box<dyn crate::repository::RepositoryInterface>, + repository: crate::repository::RepositoryInterfaceHandle, ) -> anyhow::Result<()> { self.$field.set_repository(repository) } - fn get_repository(&self) -> Option<&dyn crate::repository::RepositoryInterface> { + fn get_repository(&self) -> Option<crate::repository::RepositoryInterfaceHandle> { self.$field.get_repository() } fn get_binaries(&self) -> Vec<String> { @@ -588,7 +588,7 @@ macro_rules! impl_package_interface_handle { pub fn set_repository( &self, - repository: Box<dyn crate::repository::RepositoryInterface>, + repository: crate::repository::RepositoryInterfaceHandle, ) -> anyhow::Result<()> { self.0 .borrow_mut() @@ -596,6 +596,10 @@ macro_rules! impl_package_interface_handle { .set_repository(repository) } + pub fn get_repository(&self) -> Option<crate::repository::RepositoryInterfaceHandle> { + self.0.borrow().as_package_interface().get_repository() + } + pub fn get_binaries(&self) -> Vec<String> { self.0.borrow().as_package_interface().get_binaries() } diff --git a/crates/shirabe/src/package/package.rs b/crates/shirabe/src/package/package.rs index 92836a2..680443e 100644 --- a/crates/shirabe/src/package/package.rs +++ b/crates/shirabe/src/package/package.rs @@ -1,17 +1,20 @@ //! ref: composer/src/Composer/Package/Package.php +use std::rc::Rc; + use chrono::{DateTime, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; use shirabe_external_packages::composer::util::ComposerMirror; -use shirabe_php_shim::{E_USER_DEPRECATED, PhpMixed, strpos, trigger_error}; +use shirabe_php_shim::{E_USER_DEPRECATED, LogicException, PhpMixed, strpos, trigger_error}; use crate::package::BasePackage; use crate::package::Link; use crate::package::PackageInterface; use crate::package::version::VersionParser; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; +use crate::repository::RepositoryInterfaceWeakHandle; /// Mirror entry, e.g. `['url' => 'https://...', 'preferred' => true]`. #[derive(Debug, Clone)] @@ -26,7 +29,8 @@ pub struct Package { id: i64, name: String, pretty_name: String, - repository: Option<Box<dyn RepositoryInterface>>, + /// Back-reference to the owning repository. `Weak` breaks the repository -> packages cycle. + repository: Option<RepositoryInterfaceWeakHandle>, pub(crate) r#type: Option<String>, pub(crate) target_dir: Option<String>, @@ -559,16 +563,22 @@ impl BasePackage for Package { &mut self.pretty_name } - fn repository_opt(&self) -> Option<&dyn RepositoryInterface> { - self.repository.as_deref() + fn repository_opt(&self) -> Option<RepositoryInterfaceHandle> { + self.repository + .as_ref() + .and_then(|w| w.upgrade()) + .map(RepositoryInterfaceHandle::from_rc) } - fn set_repository_box(&mut self, repository: Box<dyn RepositoryInterface>) { - todo!() + fn set_repository_box(&mut self, repository: RepositoryInterfaceHandle) { + self.repository = Some(repository.downgrade()); } - fn take_repository(&mut self) -> Option<Box<dyn RepositoryInterface>> { - todo!() + fn take_repository(&mut self) -> Option<RepositoryInterfaceHandle> { + self.repository + .take() + .and_then(|w| w.upgrade()) + .map(RepositoryInterfaceHandle::from_rc) } } @@ -696,11 +706,24 @@ impl PackageInterface for Package { fn get_php_ext(&self) -> Option<IndexMap<String, PhpMixed>> { todo!() } - fn set_repository(&mut self, _repository: Box<dyn RepositoryInterface>) -> anyhow::Result<()> { - todo!() + fn set_repository(&mut self, repository: RepositoryInterfaceHandle) -> anyhow::Result<()> { + if let Some(existing) = self.repository.as_ref().and_then(|w| w.upgrade()) { + if !Rc::ptr_eq(&existing, repository.as_rc()) { + return Err(LogicException { + message: "A package can only be added to one repository".to_string(), + code: 0, + } + .into()); + } + } + self.repository = Some(repository.downgrade()); + Ok(()) } - fn get_repository(&self) -> Option<&dyn RepositoryInterface> { - todo!() + fn get_repository(&self) -> Option<RepositoryInterfaceHandle> { + self.repository + .as_ref() + .and_then(|w| w.upgrade()) + .map(RepositoryInterfaceHandle::from_rc) } fn get_binaries(&self) -> Vec<String> { todo!() diff --git a/crates/shirabe/src/package/package_interface.rs b/crates/shirabe/src/package/package_interface.rs index 0ec6e26..92729a4 100644 --- a/crates/shirabe/src/package/package_interface.rs +++ b/crates/shirabe/src/package/package_interface.rs @@ -5,7 +5,7 @@ use indexmap::IndexMap; use shirabe_php_shim::PhpMixed; use crate::package::Link; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; /// Defines the essential information a package has that is used during solving/installation /// @@ -244,10 +244,10 @@ pub trait PackageInterface: std::fmt::Display + std::fmt::Debug { fn get_php_ext(&self) -> Option<IndexMap<String, PhpMixed>>; /// Stores a reference to the repository that owns the package - fn set_repository(&mut self, repository: Box<dyn RepositoryInterface>) -> anyhow::Result<()>; + fn set_repository(&mut self, repository: RepositoryInterfaceHandle) -> anyhow::Result<()>; /// Returns a reference to the repository that owns the package - fn get_repository(&self) -> Option<&dyn RepositoryInterface>; + fn get_repository(&self) -> Option<RepositoryInterfaceHandle>; /// Returns the package binaries /// diff --git a/crates/shirabe/src/package/root_package.rs b/crates/shirabe/src/package/root_package.rs index df676ff..028f43b 100644 --- a/crates/shirabe/src/package/root_package.rs +++ b/crates/shirabe/src/package/root_package.rs @@ -9,7 +9,7 @@ use crate::package::CompletePackageInterface; use crate::package::Link; use crate::package::PackageInterface; use crate::package::RootPackageInterface; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; #[derive(Debug)] pub struct RootPackage { @@ -351,11 +351,11 @@ impl PackageInterface for RootPackage { fn get_php_ext(&self) -> Option<IndexMap<String, PhpMixed>> { todo!() } - fn set_repository(&mut self, _repository: Box<dyn RepositoryInterface>) -> anyhow::Result<()> { - todo!() + fn set_repository(&mut self, repository: RepositoryInterfaceHandle) -> anyhow::Result<()> { + self.inner.set_repository(repository) } - fn get_repository(&self) -> Option<&dyn RepositoryInterface> { - todo!() + fn get_repository(&self) -> Option<RepositoryInterfaceHandle> { + self.inner.get_repository() } fn get_binaries(&self) -> Vec<String> { todo!() diff --git a/crates/shirabe/src/plugin/plugin_manager.rs b/crates/shirabe/src/plugin/plugin_manager.rs index e2528ff..611ddfa 100644 --- a/crates/shirabe/src/plugin/plugin_manager.rs +++ b/crates/shirabe/src/plugin/plugin_manager.rs @@ -129,30 +129,28 @@ impl PluginManager { // TODO(phase-b): PHP returns a shared object reference; we clone the repository // 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 + let repo = self .composer_full() .borrow() .get_repository_manager() .borrow() - .get_local_repository() - .clone_box(); + .get_local_repository(); // The root package borrow is also tied to `self.composer`; clone the package handle // (shared Rc) for the same reason as above. let root_package = self.composer_full().borrow().get_package().clone(); - self.load_repository(&*repo, false, Some(root_package))?; + self.load_repository(&*repo.borrow(), false, Some(root_package))?; } if self.global_composer.is_some() && !self.are_plugins_disabled("global") { - let repo: Box<dyn RepositoryInterface> = self + let repo = self .global_composer .as_ref() .unwrap() .borrow_partial() .get_repository_manager() .borrow() - .get_local_repository() - .clone_box(); - self.load_repository(&*repo, true, None)?; + .get_local_repository(); + self.load_repository(&*repo.borrow(), true, None)?; } Ok(()) } @@ -161,27 +159,25 @@ impl PluginManager { pub fn deactivate_installed_plugins(&mut self) { // TODO(plugin): deactivation is part of the plugin API if !self.are_plugins_disabled("local") { - let repo: Box<dyn RepositoryInterface> = self + let repo = self .composer_full() .borrow() .get_repository_manager() .borrow() - .get_local_repository() - .clone_box(); - self.deactivate_repository(&*repo, false); + .get_local_repository(); + self.deactivate_repository(&*repo.borrow(), false); } if self.global_composer.is_some() && !self.are_plugins_disabled("global") { - let repo: Box<dyn RepositoryInterface> = self + let repo = self .global_composer .as_ref() .unwrap() .borrow_partial() .get_repository_manager() .borrow() - .get_local_repository() - .clone_box(); - self.deactivate_repository(&*repo, true); + .get_local_repository(); + self.deactivate_repository(&*repo.borrow(), true); } } diff --git a/crates/shirabe/src/repository/array_repository.rs b/crates/shirabe/src/repository/array_repository.rs index 6467c14..3a05513 100644 --- a/crates/shirabe/src/repository/array_repository.rs +++ b/crates/shirabe/src/repository/array_repository.rs @@ -2,6 +2,7 @@ use std::any::Any; use std::cell::RefCell; +use std::rc::Weak; use anyhow::Result; use indexmap::IndexMap; @@ -17,7 +18,7 @@ use crate::package::version::StabilityFilter; use crate::package::version::VersionParser; use crate::repository::{ AbandonedInfo, FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, - SearchResult, + RepositoryInterfaceHandle, RepositoryInterfaceWeakHandle, SearchResult, }; /// A repository implementation that simply stores packages in an array @@ -29,6 +30,10 @@ pub struct ArrayRepository { /// @var ?array<BasePackage> indexed by package unique name and used to cache hasPackage calls pub(crate) package_map: RefCell<Option<IndexMap<String, BasePackageHandle>>>, + + /// Weak reference to the outermost repository handle wrapping this `ArrayRepository`, + /// injected via `set_self_handle`. Used to wire package -> repository back-references. + self_weak: RefCell<Option<RepositoryInterfaceWeakHandle>>, } impl ArrayRepository { @@ -37,6 +42,7 @@ impl ArrayRepository { let this = Self { packages: RefCell::new(None), package_map: RefCell::new(None), + self_weak: RefCell::new(None), }; for package in packages { this.add_package(package)?; @@ -49,8 +55,12 @@ impl ArrayRepository { if self.packages.borrow().is_none() { self.initialize(); } - // TODO(phase-b): pass a reference to self, not a clone - package.set_repository(todo!("self as Box<dyn RepositoryInterface>"))?; + // PHP: $package->setRepository($this). The back-reference is wired only once this + // repository (or its outermost wrapper) has been put behind a RepositoryInterfaceHandle; + // packages added during construction are wired later in set_self_handle. + if let Some(handle) = self.self_weak.borrow().as_ref().and_then(Weak::upgrade) { + package.set_repository(RepositoryInterfaceHandle::from_rc(handle))?; + } let aliased_package: Option<PackageHandle> = package.as_alias().map(|alias| alias.get_alias_of()); @@ -59,13 +69,13 @@ impl ArrayRepository { .borrow_mut() .as_mut() .unwrap() - .push(package.into()); + .push(package.clone()); if let Some(aliased_package) = aliased_package { // PHP: if ($aliasedPackage->getRepository() === null) $this->addPackage($aliasedPackage); - // TODO(phase-c): the handle does not expose get_repository (a `RefCell`-borrowed - // back-reference); this needs repository back-references on handles. - let _ = aliased_package; + if aliased_package.get_repository().is_none() { + self.add_package(aliased_package.into())?; + } } // invalidate package map cache @@ -438,4 +448,23 @@ impl RepositoryInterface for ArrayRepository { fn as_any(&self) -> &dyn std::any::Any { self } + + fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + *self.self_weak.borrow_mut() = Some(weak.clone()); + // Wire back-references for packages added before this repository was wrapped in a handle + // (e.g. those passed to ArrayRepository::new). Reads the field directly to avoid + // triggering lazy initialization in wrapper repositories. + if let Some(handle) = weak.upgrade() { + let handle = RepositoryInterfaceHandle::from_rc(handle); + let pkgs: Vec<BasePackageHandle> = self + .packages + .borrow() + .as_ref() + .map(|v| v.clone()) + .unwrap_or_default(); + for pkg in pkgs { + let _ = pkg.set_repository(handle.clone()); + } + } + } } diff --git a/crates/shirabe/src/repository/composer_repository.rs b/crates/shirabe/src/repository/composer_repository.rs index ddf8e31..9931a73 100644 --- a/crates/shirabe/src/repository/composer_repository.rs +++ b/crates/shirabe/src/repository/composer_repository.rs @@ -38,6 +38,8 @@ use crate::repository::ConfigurableRepositoryInterface; use crate::repository::LoadPackagesResult; use crate::repository::PlatformRepository; use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; +use crate::repository::RepositoryInterfaceWeakHandle; use crate::repository::RepositorySecurityException; use crate::repository::{PartialOrSecurityAdvisory, SecurityAdvisoryResult}; use crate::repository::{SEARCH_FULLTEXT, SEARCH_VENDOR}; @@ -78,6 +80,9 @@ pub struct ProviderListingEntry { #[derive(Debug)] pub struct ComposerRepository { inner: ArrayRepository, + /// Weak reference to the outermost repository handle wrapping this `ComposerRepository`, + /// injected via `set_self_handle`. Used to wire package -> repository back-references. + self_weak: std::cell::RefCell<Option<RepositoryInterfaceWeakHandle>>, /// @phpstan-var array{url: string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} repo_config: IndexMap<String, PhpMixed>, options: IndexMap<String, PhpMixed>, @@ -274,6 +279,7 @@ impl ComposerRepository { let mut this = Self { inner: inner?, + self_weak: std::cell::RefCell::new(None), repo_config, options, url, @@ -1745,13 +1751,20 @@ impl ComposerRepository { versions_to_load.values().cloned().collect(); let loaded_packages = self.create_packages_flat(versions_to_load_vec, packages_source)?; let uids: Vec<String> = versions_to_load.keys().cloned().collect(); + let self_handle = self.self_handle(); for (index, package) in loaded_packages.into_iter().enumerate() { - // TODO(phase-c): wire the repository back-reference onto the shared package handle. + if let Some(h) = self_handle.as_ref() { + package.set_repository(h.clone())?; + } let uid = &uids[index]; if let Some(alias) = package.as_alias() { let aliased = alias.get_alias_of(); + if let Some(h) = self_handle.as_ref() { + let aliased_pkg: PackageInterfaceHandle = aliased.clone().into(); + aliased_pkg.set_repository(h.clone())?; + } result.insert(uid.clone(), aliased.into()); result.insert(format!("{}-alias", uid), package); } else { @@ -1784,6 +1797,21 @@ impl ComposerRepository { self.inner.add_package(package.into()); } + /// Forwards the outermost handle's weak to the inner `ArrayRepository` so that packages added + /// through `add_package` get the back-reference to this `ComposerRepository` wrapper. + pub fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + *self.self_weak.borrow_mut() = Some(weak.clone()); + self.inner.set_self_handle(weak); + } + + fn self_handle(&self) -> Option<RepositoryInterfaceHandle> { + self.self_weak + .borrow() + .as_ref() + .and_then(std::rc::Weak::upgrade) + .map(RepositoryInterfaceHandle::from_rc) + } + /// @param packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only packages matching it will be loaded fn load_async_packages( &mut self, @@ -1976,11 +2004,18 @@ impl ComposerRepository { let loaded_packages: Vec<BasePackageHandle> = ComposerRepository::create_packages_static(versions_to_load, packages_source)?; + let self_handle = self.self_handle(); for package in loaded_packages.into_iter() { - // TODO(phase-c): wire the repository back-reference onto the shared package handle. + if let Some(h) = self_handle.as_ref() { + package.set_repository(h.clone())?; + } let hash_c = package.ptr_id().to_string(); if let Some(alias) = package.as_alias() { let aliased = alias.get_alias_of(); + if let Some(h) = self_handle.as_ref() { + let aliased_pkg: PackageInterfaceHandle = aliased.clone().into(); + aliased_pkg.set_repository(h.clone())?; + } let aliased_hash = aliased.ptr_id().to_string(); if !packages.contains_key(&aliased_hash) { packages.insert(aliased_hash, aliased.into()); diff --git a/crates/shirabe/src/repository/composite_repository.rs b/crates/shirabe/src/repository/composite_repository.rs index 12f1837..6c167f2 100644 --- a/crates/shirabe/src/repository/composite_repository.rs +++ b/crates/shirabe/src/repository/composite_repository.rs @@ -8,16 +8,17 @@ use shirabe_semver::constraint::AnyConstraint; use crate::package::BasePackageHandle; use crate::package::PackageInterfaceHandle; use crate::repository::{ - FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, + RepositoryInterfaceHandle, SearchResult, }; #[derive(Debug)] pub struct CompositeRepository { - repositories: Vec<Box<dyn RepositoryInterface>>, + repositories: Vec<RepositoryInterfaceHandle>, } impl CompositeRepository { - pub fn new(repositories: Vec<Box<dyn RepositoryInterface>>) -> Self { + pub fn new(repositories: Vec<RepositoryInterfaceHandle>) -> Self { let mut this = Self { repositories: vec![], }; @@ -27,7 +28,7 @@ impl CompositeRepository { this } - pub fn get_repositories(&self) -> &Vec<Box<dyn RepositoryInterface>> { + pub fn get_repositories(&self) -> &Vec<RepositoryInterfaceHandle> { &self.repositories } @@ -39,10 +40,17 @@ impl CompositeRepository { } } - pub fn add_repository(&mut self, repository: Box<dyn RepositoryInterface>) { - if let Some(composite) = repository.as_any().downcast_ref::<CompositeRepository>() { - for repo in composite.get_repositories() { - self.repositories.push(repo.clone_box()); + pub fn add_repository(&mut self, repository: RepositoryInterfaceHandle) { + let nested: Option<Vec<RepositoryInterfaceHandle>> = { + let repo_ref = repository.borrow(); + repo_ref + .as_any() + .downcast_ref::<CompositeRepository>() + .map(|composite| composite.get_repositories().clone()) + }; + if let Some(nested) = nested { + for repo in nested { + self.repositories.push(repo); } } else { self.repositories.push(repository); @@ -163,6 +171,6 @@ impl RepositoryInterface for CompositeRepository { } fn as_any(&self) -> &dyn std::any::Any { - todo!() + self } } diff --git a/crates/shirabe/src/repository/filesystem_repository.rs b/crates/shirabe/src/repository/filesystem_repository.rs index 8232ecf..a721b4f 100644 --- a/crates/shirabe/src/repository/filesystem_repository.rs +++ b/crates/shirabe/src/repository/filesystem_repository.rs @@ -78,6 +78,10 @@ impl FilesystemRepository { self.dev_mode } + pub fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } + pub fn get_repo_name(&self) -> String { format!("file ({})", self.file.get_path()) } diff --git a/crates/shirabe/src/repository/filter_repository.rs b/crates/shirabe/src/repository/filter_repository.rs index 2a339f5..ecf1f0f 100644 --- a/crates/shirabe/src/repository/filter_repository.rs +++ b/crates/shirabe/src/repository/filter_repository.rs @@ -5,7 +5,8 @@ use crate::package::PackageInterfaceHandle; use crate::package::base_package::{self}; use crate::repository::{AdvisoryProviderInterface, SecurityAdvisoryResult}; use crate::repository::{ - FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, + RepositoryInterfaceHandle, SearchResult, }; use anyhow::Result; use indexmap::IndexMap; @@ -18,12 +19,12 @@ pub struct FilterRepository { only: Option<String>, exclude: Option<String>, canonical: bool, - repo: Box<dyn RepositoryInterface>, + repo: RepositoryInterfaceHandle, } impl FilterRepository { pub fn new( - repo: Box<dyn RepositoryInterface>, + repo: RepositoryInterfaceHandle, options: IndexMap<String, PhpMixed>, ) -> Result<Self> { let mut only: Option<String> = None; @@ -126,8 +127,8 @@ impl FilterRepository { }) } - pub fn get_repository(&self) -> &dyn RepositoryInterface { - self.repo.as_ref() + pub fn get_repository(&self) -> RepositoryInterfaceHandle { + self.repo.clone() } fn is_allowed(&self, name: &str) -> bool { @@ -254,7 +255,13 @@ impl RepositoryInterface for FilterRepository { } fn as_advisory_provider(&self) -> Option<&dyn AdvisoryProviderInterface> { - self.repo.as_advisory_provider() + // FilterRepository is itself an advisory provider (it filters), but only meaningfully so + // when the wrapped repository provides advisories. + if self.repo.borrow().as_advisory_provider().is_some() { + Some(self) + } else { + None + } } fn as_any(&self) -> &dyn std::any::Any { @@ -264,7 +271,8 @@ impl RepositoryInterface for FilterRepository { impl AdvisoryProviderInterface for FilterRepository { fn has_security_advisories(&self) -> bool { - if let Some(advisory_repo) = self.repo.as_advisory_provider() { + let repo = self.repo.borrow(); + if let Some(advisory_repo) = repo.as_advisory_provider() { advisory_repo.has_security_advisories() } else { false @@ -276,7 +284,8 @@ impl AdvisoryProviderInterface for FilterRepository { mut package_constraint_map: IndexMap<String, AnyConstraint>, allow_partial_advisories: bool, ) -> anyhow::Result<SecurityAdvisoryResult> { - if let Some(advisory_repo) = self.repo.as_advisory_provider() { + let repo = self.repo.borrow(); + if let Some(advisory_repo) = repo.as_advisory_provider() { package_constraint_map.retain(|name, _| self.is_allowed(name)); advisory_repo.get_security_advisories(package_constraint_map, allow_partial_advisories) } else { diff --git a/crates/shirabe/src/repository/handle.rs b/crates/shirabe/src/repository/handle.rs new file mode 100644 index 0000000..f7a4104 --- /dev/null +++ b/crates/shirabe/src/repository/handle.rs @@ -0,0 +1,174 @@ +//! Shared handle over `RepositoryInterface`. + +use std::cell::{Ref, RefCell, RefMut}; +use std::rc::{Rc, Weak}; + +use indexmap::IndexMap; +use shirabe_php_shim::Countable; +use shirabe_semver::constraint::AnyConstraint; + +use crate::package::BasePackageHandle; +use crate::package::PackageInterfaceHandle; +use crate::repository::{ + FindPackageConstraint, InstalledRepositoryInterface, LoadPackagesResult, ProviderInfo, + RepositoryInterface, SearchResult, WritableRepositoryInterface, +}; + +/// Shared reference to a repository. Corresponds to PHP `RepositoryInterface`. +#[derive(Debug, Clone)] +pub struct RepositoryInterfaceHandle(Rc<RefCell<dyn RepositoryInterface>>); + +/// Weak back-reference held by packages to the repository that owns them. +pub type RepositoryInterfaceWeakHandle = Weak<RefCell<dyn RepositoryInterface>>; + +impl RepositoryInterfaceHandle { + /// Wraps a concrete repository in a shared handle and injects its own weak reference so that + /// `add_package` can wire package -> repository back-references (PHP `setRepository($this)`). + pub fn new<T: RepositoryInterface + 'static>(repository: T) -> Self { + let rc: Rc<RefCell<dyn RepositoryInterface>> = Rc::new(RefCell::new(repository)); + rc.borrow().set_self_handle(Rc::downgrade(&rc)); + Self(rc) + } + + pub fn from_rc(rc: Rc<RefCell<dyn RepositoryInterface>>) -> Self { + Self(rc) + } + + pub fn as_rc(&self) -> &Rc<RefCell<dyn RepositoryInterface>> { + &self.0 + } + + pub fn downgrade(&self) -> RepositoryInterfaceWeakHandle { + Rc::downgrade(&self.0) + } + + pub fn borrow(&self) -> Ref<'_, dyn RepositoryInterface> { + self.0.borrow() + } + + pub fn borrow_mut(&self) -> RefMut<'_, dyn RepositoryInterface> { + self.0.borrow_mut() + } + + /// PHP `===` (reference identity). + pub fn ptr_eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } + + /// Stable identity usable as a map key (PHP `spl_object_hash`). + pub fn ptr_id(&self) -> usize { + Rc::as_ptr(&self.0) as *const () as usize + } + + /// PHP `instanceof T` for a concrete repository type. Keeps the `RefCell` borrow internal. + pub fn is<T: RepositoryInterface + 'static>(&self) -> bool { + self.0.borrow().as_any().is::<T>() + } + + pub fn count(&self) -> i64 { + self.0.borrow().count() + } + + pub fn get_repo_name(&self) -> String { + self.0.borrow().get_repo_name() + } + + pub fn get_packages(&self) -> Vec<BasePackageHandle> { + self.0.borrow().get_packages() + } + + pub fn has_package(&self, package: PackageInterfaceHandle) -> bool { + self.0.borrow().has_package(package) + } + + pub fn find_package( + &self, + name: &str, + constraint: FindPackageConstraint, + ) -> Option<BasePackageHandle> { + self.0.borrow().find_package(name, constraint) + } + + pub fn find_packages( + &self, + name: &str, + constraint: Option<FindPackageConstraint>, + ) -> Vec<BasePackageHandle> { + self.0.borrow().find_packages(name, constraint) + } + + pub fn load_packages( + &self, + package_name_map: IndexMap<String, Option<AnyConstraint>>, + acceptable_stabilities: IndexMap<String, i64>, + stability_flags: IndexMap<String, i64>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, + ) -> LoadPackagesResult { + self.0.borrow().load_packages( + package_name_map, + acceptable_stabilities, + stability_flags, + already_loaded, + ) + } + + pub fn search(&self, query: String, mode: i64, r#type: Option<String>) -> Vec<SearchResult> { + self.0.borrow().search(query, mode, r#type) + } + + pub fn get_providers(&self, package_name: String) -> IndexMap<String, ProviderInfo> { + self.0.borrow().get_providers(package_name) + } + + // --- InstalledRepositoryInterface helpers (valid only when the wrapped repository is one) --- + + pub fn is_fresh(&self) -> bool { + self.0 + .borrow() + .as_installed_repository_interface() + .map_or(false, |r| r.is_fresh()) + } + + pub fn get_dev_mode(&self) -> Option<bool> { + self.0 + .borrow() + .as_installed_repository_interface() + .and_then(|r| r.get_dev_mode()) + } + + pub fn get_canonical_packages(&self) -> Vec<PackageInterfaceHandle> { + self.0 + .borrow() + .as_installed_repository_interface() + .map(|r| r.get_canonical_packages()) + .unwrap_or_default() + } + + pub fn get_dev_package_names(&self) -> Vec<String> { + self.0 + .borrow() + .as_installed_repository_interface() + .map(|r| r.get_dev_package_names().clone()) + .unwrap_or_default() + } + + pub fn set_dev_package_names(&self, dev_package_names: Vec<String>) { + if let Some(r) = self.0.borrow_mut().as_installed_repository_interface_mut() { + r.set_dev_package_names(dev_package_names); + } + } +} + +impl PartialEq for RepositoryInterfaceHandle { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for RepositoryInterfaceHandle {} + +impl std::hash::Hash for RepositoryInterfaceHandle { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.ptr_id().hash(state); + } +} diff --git a/crates/shirabe/src/repository/installed_array_repository.rs b/crates/shirabe/src/repository/installed_array_repository.rs index ca2d8c7..1520497 100644 --- a/crates/shirabe/src/repository/installed_array_repository.rs +++ b/crates/shirabe/src/repository/installed_array_repository.rs @@ -133,7 +133,18 @@ impl RepositoryInterface for InstalledArrayRepository { fn as_advisory_provider(&self) -> Option<&dyn AdvisoryProviderInterface> { None } + fn as_installed_repository_interface(&self) -> Option<&dyn InstalledRepositoryInterface> { + Some(self) + } + fn as_installed_repository_interface_mut( + &mut self, + ) -> Option<&mut dyn InstalledRepositoryInterface> { + Some(self) + } fn as_any(&self) -> &dyn std::any::Any { todo!() } + fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/installed_filesystem_repository.rs b/crates/shirabe/src/repository/installed_filesystem_repository.rs index 352d86f..73a9f1a 100644 --- a/crates/shirabe/src/repository/installed_filesystem_repository.rs +++ b/crates/shirabe/src/repository/installed_filesystem_repository.rs @@ -143,7 +143,18 @@ impl RepositoryInterface for InstalledFilesystemRepository { fn as_advisory_provider(&self) -> Option<&dyn AdvisoryProviderInterface> { None } + fn as_installed_repository_interface(&self) -> Option<&dyn InstalledRepositoryInterface> { + Some(self) + } + fn as_installed_repository_interface_mut( + &mut self, + ) -> Option<&mut dyn InstalledRepositoryInterface> { + Some(self) + } fn as_any(&self) -> &dyn std::any::Any { todo!() } + fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/installed_repository.rs b/crates/shirabe/src/repository/installed_repository.rs index 486d4bd..b857bc2 100644 --- a/crates/shirabe/src/repository/installed_repository.rs +++ b/crates/shirabe/src/repository/installed_repository.rs @@ -16,7 +16,8 @@ use crate::repository::LockArrayRepository; use crate::repository::PlatformRepository; use crate::repository::RootPackageRepository; use crate::repository::{ - FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, + RepositoryInterfaceHandle, SearchResult, }; pub enum NeedleInput { @@ -36,7 +37,7 @@ pub struct InstalledRepository { } impl InstalledRepository { - pub fn new(repositories: Vec<Box<dyn RepositoryInterface>>) -> Self { + pub fn new(repositories: Vec<RepositoryInterfaceHandle>) -> Self { let mut this = Self { inner: CompositeRepository::new(vec![]), }; @@ -381,25 +382,21 @@ impl InstalledRepository { results } - pub fn add_repository( - &mut self, - repository: Box<dyn RepositoryInterface>, - ) -> anyhow::Result<()> { - // TODO(phase-b): cannot Any::is::<dyn InstalledRepositoryInterface>; replace with a - // dedicated downcast/marker method on RepositoryInterface. - if repository.as_any().is::<LockArrayRepository>() - || repository.as_any().is::<RootPackageRepository>() - || repository.as_any().is::<PlatformRepository>() + pub fn add_repository(&mut self, repository: RepositoryInterfaceHandle) -> anyhow::Result<()> { + if repository.is::<LockArrayRepository>() + || repository.is::<RootPackageRepository>() + || repository.is::<PlatformRepository>() { self.inner.add_repository(repository); return Ok(()); } + let type_name = std::any::type_name_of_val(&*repository.borrow()).to_string(); + let repo_name = repository.get_repo_name(); Err(anyhow::anyhow!(LogicException { message: format!( "An InstalledRepository can not contain a repository of type {} ({})", - std::any::type_name_of_val(&*repository), - repository.get_repo_name(), + type_name, repo_name, ), code: 0, })) diff --git a/crates/shirabe/src/repository/lock_array_repository.rs b/crates/shirabe/src/repository/lock_array_repository.rs index cbc8564..f9b64a7 100644 --- a/crates/shirabe/src/repository/lock_array_repository.rs +++ b/crates/shirabe/src/repository/lock_array_repository.rs @@ -5,7 +5,8 @@ use crate::package::PackageInterfaceHandle; use crate::repository::ArrayRepository; use crate::repository::CanonicalPackagesTrait; use crate::repository::{ - FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, + RepositoryInterfaceWeakHandle, SearchResult, }; use indexmap::IndexMap; use shirabe_php_shim::Countable; @@ -89,4 +90,8 @@ impl RepositoryInterface for LockArrayRepository { fn as_any(&self) -> &dyn std::any::Any { self } + + fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/mod.rs b/crates/shirabe/src/repository/mod.rs index 6f037d0..d678764 100644 --- a/crates/shirabe/src/repository/mod.rs +++ b/crates/shirabe/src/repository/mod.rs @@ -7,6 +7,7 @@ pub mod composite_repository; pub mod configurable_repository_interface; pub mod filesystem_repository; pub mod filter_repository; +pub mod handle; pub mod installed_array_repository; pub mod installed_filesystem_repository; pub mod installed_repository; @@ -39,6 +40,7 @@ pub use composite_repository::*; pub use configurable_repository_interface::*; pub use filesystem_repository::*; pub use filter_repository::*; +pub use handle::*; pub use installed_array_repository::*; pub use installed_filesystem_repository::*; pub use installed_repository::*; diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index 1b95ff7..b8c4f52 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -2012,4 +2012,8 @@ impl crate::repository::RepositoryInterface for PlatformRepository { fn as_any(&self) -> &dyn std::any::Any { self } + + fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/repository_factory.rs b/crates/shirabe/src/repository/repository_factory.rs index 908704c..1159da1 100644 --- a/crates/shirabe/src/repository/repository_factory.rs +++ b/crates/shirabe/src/repository/repository_factory.rs @@ -12,7 +12,7 @@ use crate::factory::Factory; use crate::io::IOInterface; use crate::json::JsonFile; use crate::repository::FilesystemRepository; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; use crate::repository::RepositoryManager; use crate::util::HttpDownloader; use crate::util::ProcessExecutor; @@ -102,7 +102,7 @@ impl RepositoryFactory { repository: &str, allow_filesystem: bool, rm: Option<&mut RepositoryManager>, - ) -> anyhow::Result<Box<dyn RepositoryInterface>> { + ) -> anyhow::Result<RepositoryInterfaceHandle> { let repo_config = Self::config_from_string(io.clone(), config, repository, allow_filesystem)?; Self::create_repo(io, config, repo_config, rm) @@ -113,7 +113,7 @@ impl RepositoryFactory { config: &std::rc::Rc<std::cell::RefCell<Config>>, repo_config: IndexMap<String, PhpMixed>, rm: Option<&mut RepositoryManager>, - ) -> anyhow::Result<Box<dyn RepositoryInterface>> { + ) -> anyhow::Result<RepositoryInterfaceHandle> { let mut owned_rm; let rm = if let Some(rm) = rm { rm @@ -145,7 +145,7 @@ impl RepositoryFactory { io: Option<std::rc::Rc<std::cell::RefCell<dyn IOInterface>>>, config: Option<std::rc::Rc<std::cell::RefCell<Config>>>, rm: Option<&mut RepositoryManager>, - ) -> anyhow::Result<IndexMap<String, Box<dyn RepositoryInterface>>> { + ) -> anyhow::Result<IndexMap<String, RepositoryInterfaceHandle>> { let config = match config { Some(c) => c, None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_config(None, None)?)), @@ -233,7 +233,7 @@ impl RepositoryFactory { pub fn default_repos_with_default_manager( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - ) -> anyhow::Result<IndexMap<String, Box<dyn RepositoryInterface>>> { + ) -> anyhow::Result<IndexMap<String, RepositoryInterfaceHandle>> { let config = std::rc::Rc::new(std::cell::RefCell::new(Factory::create_config( Some(io.clone()), None, @@ -247,8 +247,8 @@ impl RepositoryFactory { fn create_repos( rm: &mut RepositoryManager, repo_configs: Vec<PhpMixed>, - ) -> anyhow::Result<IndexMap<String, Box<dyn RepositoryInterface>>> { - let mut repo_map: IndexMap<String, Box<dyn RepositoryInterface>> = IndexMap::new(); + ) -> anyhow::Result<IndexMap<String, RepositoryInterfaceHandle>> { + let mut repo_map: IndexMap<String, RepositoryInterfaceHandle> = IndexMap::new(); for (index, repo) in repo_configs.into_iter().enumerate() { match &repo { @@ -290,7 +290,7 @@ impl RepositoryFactory { .to_string(); // TODO(phase-b): FilesystemRepository does not yet implement // RepositoryInterface; once it does, construct it from JsonFile here. - let created: Box<dyn RepositoryInterface> = + let created: RepositoryInterfaceHandle = todo!("FilesystemRepository as dyn RepositoryInterface"); repo_map.insert(name, created); } else { @@ -323,7 +323,7 @@ impl RepositoryFactory { pub fn generate_repository_name( index: &PhpMixed, repo: &IndexMap<String, PhpMixed>, - existing_repos: &IndexMap<String, Box<dyn RepositoryInterface>>, + existing_repos: &IndexMap<String, RepositoryInterfaceHandle>, ) -> String { let mut name = match index { PhpMixed::Int(_) => { @@ -344,7 +344,7 @@ impl RepositoryFactory { fn generate_repository_name_indexed( index: usize, repo: &IndexMap<String, PhpMixed>, - existing_repos: &IndexMap<String, Box<dyn RepositoryInterface>>, + existing_repos: &IndexMap<String, RepositoryInterfaceHandle>, ) -> String { let mut name = if let Some(url) = repo.get("url").and_then(|v| v.as_string()) { Preg::replace("{^https?://}i", "", url).unwrap_or_else(|_| url.to_string()) diff --git a/crates/shirabe/src/repository/repository_interface.rs b/crates/shirabe/src/repository/repository_interface.rs index 316f509..21914f6 100644 --- a/crates/shirabe/src/repository/repository_interface.rs +++ b/crates/shirabe/src/repository/repository_interface.rs @@ -93,8 +93,23 @@ pub trait RepositoryInterface: Countable + std::fmt::Debug { None } + fn as_installed_repository_interface_mut( + &mut self, + ) -> Option<&mut dyn crate::repository::InstalledRepositoryInterface> { + None + } + fn as_any(&self) -> &dyn std::any::Any; + /// Injects this repository's own weak handle so that `add_package` can wire package -> + /// repository back-references (PHP `setRepository($this)`). Called once when the repository is + /// wrapped in a [`RepositoryInterfaceHandle`](crate::repository::RepositoryInterfaceHandle). + /// Wrapper repositories forward the same weak (the outermost handle) to their inner + /// `ArrayRepository`. + fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + let _ = weak; + } + fn clone_box(&self) -> Box<dyn RepositoryInterface> { todo!() } diff --git a/crates/shirabe/src/repository/repository_manager.rs b/crates/shirabe/src/repository/repository_manager.rs index 72c3643..dd69731 100644 --- a/crates/shirabe/src/repository/repository_manager.rs +++ b/crates/shirabe/src/repository/repository_manager.rs @@ -10,15 +10,14 @@ use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::package::PackageInterfaceHandle; use crate::repository::FilterRepository; -use crate::repository::InstalledRepositoryInterface; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; use crate::util::HttpDownloader; use crate::util::ProcessExecutor; #[derive(Debug)] pub struct RepositoryManager { - local_repository: Option<Box<dyn InstalledRepositoryInterface>>, - repositories: Vec<Box<dyn RepositoryInterface>>, + local_repository: Option<RepositoryInterfaceHandle>, + repositories: Vec<RepositoryInterfaceHandle>, repository_classes: IndexMap<String, String>, io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, config: std::rc::Rc<std::cell::RefCell<Config>>, @@ -62,7 +61,7 @@ impl RepositoryManager { name, crate::repository::FindPackageConstraint::Constraint(constraint.clone()), ) { - return Some(package.clone().into()); + return Some(package.into()); } } None @@ -81,17 +80,17 @@ impl RepositoryManager { constraint.clone(), )), ) { - packages.push(p.clone().into()); + packages.push(p); } } packages } - pub fn add_repository(&mut self, repository: Box<dyn RepositoryInterface>) { + pub fn add_repository(&mut self, repository: RepositoryInterfaceHandle) { self.repositories.push(repository); } - pub fn prepend_repository(&mut self, repository: Box<dyn RepositoryInterface>) { + pub fn prepend_repository(&mut self, repository: RepositoryInterfaceHandle) { self.repositories.insert(0, repository); } @@ -100,7 +99,7 @@ impl RepositoryManager { r#type: &str, config: IndexMap<String, PhpMixed>, name: Option<&str>, - ) -> anyhow::Result<Box<dyn RepositoryInterface>> { + ) -> anyhow::Result<RepositoryInterfaceHandle> { if !self.repository_classes.contains_key(r#type) { return Err(InvalidArgumentException { message: format!("Repository type is not registered: {}", r#type), @@ -140,7 +139,10 @@ impl RepositoryManager { let repository = self.create_repository_by_class(&class, cleaned_config)?; if let Some(filter_config) = filter_config { - return Ok(Box::new(FilterRepository::new(repository, filter_config)?)); + return Ok(RepositoryInterfaceHandle::new(FilterRepository::new( + repository, + filter_config, + )?)); } Ok(repository) @@ -150,7 +152,7 @@ impl RepositoryManager { &self, _class: &str, _config: IndexMap<String, PhpMixed>, - ) -> anyhow::Result<Box<dyn RepositoryInterface>> { + ) -> anyhow::Result<RepositoryInterfaceHandle> { todo!("Phase B: dynamic class instantiation by class name") } @@ -159,15 +161,15 @@ impl RepositoryManager { .insert(r#type.to_string(), class.to_string()); } - pub fn get_repositories(&self) -> &Vec<Box<dyn RepositoryInterface>> { + pub fn get_repositories(&self) -> &Vec<RepositoryInterfaceHandle> { &self.repositories } - pub fn set_local_repository(&mut self, repository: Box<dyn InstalledRepositoryInterface>) { + pub fn set_local_repository(&mut self, repository: RepositoryInterfaceHandle) { self.local_repository = Some(repository); } - pub fn get_local_repository(&self) -> &dyn InstalledRepositoryInterface { - self.local_repository.as_ref().unwrap().as_ref() + pub fn get_local_repository(&self) -> RepositoryInterfaceHandle { + self.local_repository.clone().unwrap() } } diff --git a/crates/shirabe/src/repository/repository_set.rs b/crates/shirabe/src/repository/repository_set.rs index 3a81eda..f4a4057 100644 --- a/crates/shirabe/src/repository/repository_set.rs +++ b/crates/shirabe/src/repository/repository_set.rs @@ -35,7 +35,7 @@ use crate::repository::InstalledRepositoryInterface; use crate::repository::LockArrayRepository; use crate::repository::PlatformRepository; use crate::repository::{AdvisoryProviderInterface, PartialOrSecurityAdvisory}; -use crate::repository::{FindPackageConstraint, RepositoryInterface}; +use crate::repository::{FindPackageConstraint, RepositoryInterface, RepositoryInterfaceHandle}; #[derive(Debug, Clone)] pub struct RootAliasEntry { @@ -66,7 +66,7 @@ pub struct RepositorySet { pub(crate) root_references: IndexMap<String, String>, /// @var RepositoryInterface[] - pub(crate) repositories: Vec<Box<dyn RepositoryInterface>>, + pub(crate) repositories: Vec<RepositoryInterfaceHandle>, /// @var int[] array of stability => BasePackage::STABILITY_* value /// @phpstan-var array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*> @@ -170,7 +170,7 @@ impl RepositorySet { /// repository the search for that package ends, and following repos will not be consulted. /// /// @param RepositoryInterface $repo A package repository - pub fn add_repository(&mut self, repo: Box<dyn RepositoryInterface>) -> Result<()> { + pub fn add_repository(&mut self, repo: RepositoryInterfaceHandle) -> Result<()> { if self.locked { return Err(RuntimeException { message: "Pool has already been created from this repository set, it cannot be modified anymore.".to_string(), @@ -179,17 +179,15 @@ impl RepositorySet { .into()); } - let repos: Vec<Box<dyn RepositoryInterface>> = - if let Some(composite) = repo.as_any().downcast_ref::<CompositeRepository>() { - // TODO(phase-b): clone composite.get_repositories() — Box<dyn RepositoryInterface> cloning - composite - .get_repositories() - .iter() - .map(|r| r.clone_box()) - .collect() + let repos: Vec<RepositoryInterfaceHandle> = { + let repo_ref = repo.borrow(); + if let Some(composite) = repo_ref.as_any().downcast_ref::<CompositeRepository>() { + composite.get_repositories().clone() } else { + drop(repo_ref); vec![repo] - }; + } + }; for repo in repos { self.repositories.push(repo); @@ -379,7 +377,8 @@ impl RepositorySet { for repository in &self.repositories { // TODO(phase-b): use anyhow::Result<Result<T, E>> to model PHP try/catch let attempt: Result<()> = (|| -> Result<()> { - let Some(advisory_repo) = repository.as_advisory_provider() else { + let repo_ref = repository.borrow(); + let Some(advisory_repo) = repo_ref.as_advisory_provider() else { return Ok(()); }; if !advisory_repo.has_security_advisories() { @@ -480,11 +479,11 @@ impl RepositorySet { pool_builder.set_allowed_types(allowed_types); for repo in &self.repositories { - let is_installed = repo.as_installed_repository_interface().is_some() - || repo - .as_any() - .downcast_ref::<InstalledRepository>() - .is_some(); + let is_installed = { + let repo_ref = repo.borrow(); + repo_ref.as_installed_repository_interface().is_some() + || repo_ref.as_any().is::<InstalledRepository>() + }; if is_installed && !self.allow_installed_repositories { return Err(LogicException { message: "The pool can not accept packages from an installed repository" @@ -507,11 +506,11 @@ impl RepositorySet { /// Create a pool for dependency resolution from the packages in this repository set. pub fn create_pool_with_all_packages(&mut self) -> Result<Pool> { for repo in &self.repositories { - let is_installed = repo.as_installed_repository_interface().is_some() - || repo - .as_any() - .downcast_ref::<InstalledRepository>() - .is_some(); + let is_installed = { + let repo_ref = repo.borrow(); + repo_ref.as_installed_repository_interface().is_some() + || repo_ref.as_any().is::<InstalledRepository>() + }; if is_installed && !self.allow_installed_repositories { return Err(LogicException { message: "The pool can not accept packages from an installed repository" diff --git a/crates/shirabe/src/repository/repository_utils.rs b/crates/shirabe/src/repository/repository_utils.rs index 667cea2..4522039 100644 --- a/crates/shirabe/src/repository/repository_utils.rs +++ b/crates/shirabe/src/repository/repository_utils.rs @@ -3,7 +3,7 @@ use crate::package::Link; use crate::repository::CompositeRepository; use crate::repository::FilterRepository; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; use indexmap::IndexMap; use std::any::Any; @@ -39,23 +39,31 @@ impl RepositoryUtils { } pub fn flatten_repositories( - repo: Box<dyn RepositoryInterface>, + repo: RepositoryInterfaceHandle, unwrap_filter_repos: bool, - ) -> Vec<Box<dyn RepositoryInterface>> { - let repo: Box<dyn RepositoryInterface> = if unwrap_filter_repos { - if let Some(filter_repo) = repo.as_any().downcast_ref::<FilterRepository>() { - filter_repo.get_repository().clone_box() - } else { - repo - } + ) -> Vec<RepositoryInterfaceHandle> { + let repo: RepositoryInterfaceHandle = if unwrap_filter_repos { + let unwrapped = { + let r = repo.borrow(); + r.as_any() + .downcast_ref::<FilterRepository>() + .map(|filter_repo| filter_repo.get_repository()) + }; + unwrapped.unwrap_or(repo) } else { repo }; - if let Some(composite_repo) = repo.as_any().downcast_ref::<CompositeRepository>() { + let nested = { + let r = repo.borrow(); + r.as_any() + .downcast_ref::<CompositeRepository>() + .map(|composite_repo| composite_repo.get_repositories().clone()) + }; + if let Some(nested) = nested { let mut repos = Vec::new(); - for r in composite_repo.get_repositories() { - for r2 in Self::flatten_repositories(r.clone_box(), unwrap_filter_repos) { + for r in nested { + for r2 in Self::flatten_repositories(r, unwrap_filter_repos) { repos.push(r2); } } diff --git a/crates/shirabe/src/repository/root_package_repository.rs b/crates/shirabe/src/repository/root_package_repository.rs index cb01a7c..22ca437 100644 --- a/crates/shirabe/src/repository/root_package_repository.rs +++ b/crates/shirabe/src/repository/root_package_repository.rs @@ -4,6 +4,7 @@ use crate::package::BasePackageHandle; use crate::package::PackageInterfaceHandle; use crate::package::RootPackageInterfaceHandle; use crate::repository::ArrayRepository; +use crate::repository::RepositoryInterfaceWeakHandle; use crate::repository::{ProviderInfo, RepositoryInterface, SearchResult}; use indexmap::IndexMap; @@ -85,4 +86,8 @@ impl RepositoryInterface for RootPackageRepository { fn as_any(&self) -> &dyn std::any::Any { self } + + fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/writable_array_repository.rs b/crates/shirabe/src/repository/writable_array_repository.rs index 282f3c0..ac9df8f 100644 --- a/crates/shirabe/src/repository/writable_array_repository.rs +++ b/crates/shirabe/src/repository/writable_array_repository.rs @@ -3,6 +3,7 @@ use crate::installer::InstallationManager; use crate::repository::ArrayRepository; use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceWeakHandle; use anyhow::Result; use shirabe_php_shim::Countable; @@ -52,6 +53,10 @@ impl WritableArrayRepository { self.inner.add_package(package) } + pub fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } + pub fn remove_package( &mut self, package: crate::package::PackageInterfaceHandle, |
