diff options
| -rw-r--r-- | crates/shirabe/src/repository/array_repository.rs | 128 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/artifact_repository.rs | 109 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/path_repository.rs | 119 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs_repository.rs | 333 |
4 files changed, 543 insertions, 146 deletions
diff --git a/crates/shirabe/src/repository/array_repository.rs b/crates/shirabe/src/repository/array_repository.rs index e1f8806..afe2cc5 100644 --- a/crates/shirabe/src/repository/array_repository.rs +++ b/crates/shirabe/src/repository/array_repository.rs @@ -48,6 +48,78 @@ impl ArrayRepository { self.packages.borrow().as_ref().unwrap().to_vec() } + // The RepositoryInterface findPackage/findPackages only need shared access to the package list; + // the `&mut self` on the trait methods is an artifact of the shared interface signature. These + // `&self` variants let callers that already hold a shared borrow (e.g. wrapper repositories + // initializing from a `&self` context) reuse the same logic. + pub(crate) fn find_package_internal( + &self, + name: &str, + constraint: FindPackageConstraint, + ) -> anyhow::Result<Option<BasePackageHandle>> { + let name = strtolower(name); + + let constraint: AnyConstraint = match constraint { + FindPackageConstraint::Constraint(c) => c, + FindPackageConstraint::String(s) => { + let version_parser = VersionParser::new(); + version_parser.parse_constraints(&s).unwrap().clone() + } + }; + + for package in self.get_packages_internal() { + if name == package.get_name() { + let pkg_constraint = SimpleConstraint::new( + "==".to_string(), + package.get_version().to_string(), + None, + ); + if constraint.matches(&pkg_constraint.into()) { + return Ok(Some(package)); + } + } + } + + Ok(None) + } + + pub(crate) fn find_packages_internal( + &self, + name: &str, + constraint: Option<FindPackageConstraint>, + ) -> anyhow::Result<Vec<BasePackageHandle>> { + // normalize name + let name = strtolower(name); + let mut packages = vec![]; + + let constraint: Option<AnyConstraint> = match constraint { + None => None, + Some(FindPackageConstraint::Constraint(c)) => Some(c), + Some(FindPackageConstraint::String(s)) => { + let version_parser = VersionParser::new(); + Some(version_parser.parse_constraints(&s).unwrap().clone()) + } + }; + + for package in self.get_packages_internal() { + if name == package.get_name() + && (constraint.is_none() + || constraint.as_ref().unwrap().matches( + &SimpleConstraint::new( + "==".to_string(), + package.get_version().to_string(), + None, + ) + .into(), + )) + { + packages.push(package); + } + } + + Ok(packages) + } + /// @param array<PackageInterface> $packages pub fn new(packages: Vec<PackageInterfaceHandle>) -> Result<Self> { let this = Self { @@ -243,30 +315,7 @@ impl RepositoryInterface for ArrayRepository { name: &str, constraint: FindPackageConstraint, ) -> anyhow::Result<Option<BasePackageHandle>> { - let name = strtolower(name); - - let constraint: AnyConstraint = match constraint { - FindPackageConstraint::Constraint(c) => c, - FindPackageConstraint::String(s) => { - let version_parser = VersionParser::new(); - version_parser.parse_constraints(&s).unwrap().clone() - } - }; - - for package in self.get_packages_internal() { - if name == package.get_name() { - let pkg_constraint = SimpleConstraint::new( - "==".to_string(), - package.get_version().to_string(), - None, - ); - if constraint.matches(&pkg_constraint.into()) { - return Ok(Some(package)); - } - } - } - - Ok(None) + self.find_package_internal(name, constraint) } fn find_packages( @@ -274,36 +323,7 @@ impl RepositoryInterface for ArrayRepository { name: &str, constraint: Option<FindPackageConstraint>, ) -> anyhow::Result<Vec<BasePackageHandle>> { - // normalize name - let name = strtolower(name); - let mut packages = vec![]; - - let constraint: Option<AnyConstraint> = match constraint { - None => None, - Some(FindPackageConstraint::Constraint(c)) => Some(c), - Some(FindPackageConstraint::String(s)) => { - let version_parser = VersionParser::new(); - Some(version_parser.parse_constraints(&s).unwrap().clone()) - } - }; - - for package in self.get_packages_internal() { - if name == package.get_name() - && (constraint.is_none() - || constraint.as_ref().unwrap().matches( - &SimpleConstraint::new( - "==".to_string(), - package.get_version().to_string(), - None, - ) - .into(), - )) - { - packages.push(package); - } - } - - Ok(packages) + self.find_packages_internal(name, constraint) } fn search( diff --git a/crates/shirabe/src/repository/artifact_repository.rs b/crates/shirabe/src/repository/artifact_repository.rs index 01612a4..ac83e6b 100644 --- a/crates/shirabe/src/repository/artifact_repository.rs +++ b/crates/shirabe/src/repository/artifact_repository.rs @@ -11,13 +11,20 @@ use shirabe_php_shim::{ use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::json::JsonFile; +use crate::package::BasePackageHandle; +use crate::package::PackageInterfaceHandle; use crate::package::loader::ArrayLoader; use crate::package::loader::LoaderInterface; use crate::repository::ArrayRepository; use crate::repository::ConfigurableRepositoryInterface; +use crate::repository::RepositoryInterfaceWeakHandle; +use crate::repository::{ + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, +}; use crate::util::Platform; use crate::util::Tar; use crate::util::Zip; +use shirabe_semver::constraint::AnyConstraint; pub struct ArtifactRepository { inner: ArrayRepository, @@ -73,13 +80,24 @@ impl ArtifactRepository { self.inner.get_packages() } - fn initialize(&mut self) -> anyhow::Result<()> { + fn initialize(&self) -> anyhow::Result<()> { self.inner.initialize(); let lookup = self.lookup.clone(); self.scan_directory(&lookup) } - fn scan_directory(&mut self, path: &str) -> anyhow::Result<()> { + // In PHP the inherited ArrayRepository methods lazily call the overridden initialize() to scan + // the lookup directory. Without virtual dispatch we trigger that scan here before delegating to + // the inner repository; ArrayRepository's own lazy check then sees the populated array and skips + // re-initializing it. + fn ensure_initialized(&self) -> anyhow::Result<()> { + if !self.inner.is_initialized() { + self.initialize()?; + } + Ok(()) + } + + fn scan_directory(&self, path: &str) -> anyhow::Result<()> { let entries = std::fs::read_dir(path)?; for entry in entries { let entry = entry?; @@ -226,3 +244,90 @@ impl ConfigurableRepositoryInterface for ArtifactRepository { self.repo_config.clone() } } + +impl RepositoryInterface for ArtifactRepository { + // The structural methods are inherited from ArrayRepository in PHP, where the lazy directory + // scan is driven by the overridden initialize(). Here each one first ensures that scan has + // happened (see ensure_initialized), then delegates to the inner ArrayRepository. + fn count(&self) -> anyhow::Result<usize> { + self.ensure_initialized()?; + self.inner.count() + } + + fn has_package(&self, package: PackageInterfaceHandle) -> bool { + // TODO(phase-d): hasPackage returns bool and cannot surface an initialization error; a + // failed scan leaves the inner repository with whatever packages were added before the + // failure. + let _ = self.ensure_initialized(); + self.inner.has_package(package) + } + + fn find_package( + &mut self, + name: &str, + constraint: FindPackageConstraint, + ) -> anyhow::Result<Option<BasePackageHandle>> { + self.ensure_initialized()?; + self.inner.find_package(name, constraint) + } + + fn find_packages( + &mut self, + name: &str, + constraint: Option<FindPackageConstraint>, + ) -> anyhow::Result<Vec<BasePackageHandle>> { + self.ensure_initialized()?; + self.inner.find_packages(name, constraint) + } + + fn get_packages(&mut self) -> anyhow::Result<Vec<BasePackageHandle>> { + self.ensure_initialized()?; + self.inner.get_packages() + } + + fn load_packages( + &mut 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>>, + ) -> anyhow::Result<LoadPackagesResult> { + self.ensure_initialized()?; + self.inner.load_packages( + package_name_map, + acceptable_stabilities, + stability_flags, + already_loaded, + ) + } + + fn search( + &mut self, + query: String, + mode: i64, + r#type: Option<String>, + ) -> anyhow::Result<Vec<SearchResult>> { + self.ensure_initialized()?; + self.inner.search(query, mode, r#type) + } + + fn get_providers( + &mut self, + package_name: String, + ) -> anyhow::Result<IndexMap<String, ProviderInfo>> { + self.ensure_initialized()?; + self.inner.get_providers(package_name) + } + + fn get_repo_name(&self) -> String { + ArtifactRepository::get_repo_name(self) + } + + 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/path_repository.rs b/crates/shirabe/src/repository/path_repository.rs index e5dff0e..f573ee9 100644 --- a/crates/shirabe/src/repository/path_repository.rs +++ b/crates/shirabe/src/repository/path_repository.rs @@ -11,12 +11,18 @@ use crate::config::Config; use crate::event_dispatcher::EventDispatcher; use crate::io::IOInterface; use crate::json::JsonFile; +use crate::package::BasePackageHandle; +use crate::package::PackageInterfaceHandle; use crate::package::loader::ArrayLoader; use crate::package::loader::LoaderInterface; use crate::package::version::VersionGuesser; use crate::package::version::VersionParser; use crate::repository::ArrayRepository; use crate::repository::ConfigurableRepositoryInterface; +use crate::repository::RepositoryInterfaceWeakHandle; +use crate::repository::{ + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, +}; use crate::util::Filesystem; use crate::util::Git as GitUtil; use crate::util::HttpDownloader; @@ -28,7 +34,7 @@ use crate::util::Url; pub struct PathRepository { inner: ArrayRepository, loader: ArrayLoader, - version_guesser: VersionGuesser, + version_guesser: std::cell::RefCell<VersionGuesser>, url: String, repo_config: IndexMap<String, PhpMixed>, process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, @@ -92,7 +98,7 @@ impl PathRepository { Ok(Self { inner: ArrayRepository::new(vec![])?, loader: ArrayLoader::new(None, true), - version_guesser, + version_guesser: std::cell::RefCell::new(version_guesser), url, repo_config, process, @@ -139,7 +145,18 @@ impl PathRepository { Ok(self.inner.has_package(package)) } - pub(crate) fn initialize(&mut self) -> anyhow::Result<()> { + // In PHP the inherited ArrayRepository methods lazily call the overridden initialize() to glob + // the configured url and load each path package. Without virtual dispatch we trigger that load + // here before delegating to the inner repository; ArrayRepository's own lazy check then sees the + // populated array and skips re-initializing it. + fn ensure_initialized(&self) -> anyhow::Result<()> { + if !self.inner.is_initialized() { + self.initialize()?; + } + Ok(()) + } + + pub(crate) fn initialize(&self) -> anyhow::Result<()> { self.inner.initialize(); let url_matches = self.get_url_matches()?; @@ -260,7 +277,9 @@ impl PathRepository { if code1 == 0 && code2 == 0 && ref1.as_string() == ref2.as_string() { package.insert( "version".to_string(), - PhpMixed::String(self.version_guesser.get_root_version_from_env()?), + PhpMixed::String( + self.version_guesser.borrow().get_root_version_from_env()?, + ), ); } } @@ -294,7 +313,10 @@ impl PathRepository { } if !package.contains_key("version") { - let version_data = self.version_guesser.guess_version(&package, &path)?; + let version_data = self + .version_guesser + .borrow_mut() + .guess_version(&package, &path)?; if let Some(version_data) = version_data { if let Some(pretty_version) = version_data .pretty_version @@ -371,3 +393,90 @@ impl PathRepository { .collect()) } } + +impl RepositoryInterface for PathRepository { + // The structural methods are inherited from ArrayRepository in PHP, where the lazy package load + // is driven by the overridden initialize(). Here each one first ensures that load has happened + // (see ensure_initialized), then delegates to the inner ArrayRepository. + fn count(&self) -> anyhow::Result<usize> { + self.ensure_initialized()?; + self.inner.count() + } + + fn has_package(&self, package: PackageInterfaceHandle) -> bool { + // TODO(phase-d): hasPackage returns bool and cannot surface an initialization error; a + // failed load leaves the inner repository with whatever packages were added before the + // failure. + let _ = self.ensure_initialized(); + self.inner.has_package(package) + } + + fn find_package( + &mut self, + name: &str, + constraint: FindPackageConstraint, + ) -> anyhow::Result<Option<BasePackageHandle>> { + self.ensure_initialized()?; + self.inner.find_package(name, constraint) + } + + fn find_packages( + &mut self, + name: &str, + constraint: Option<FindPackageConstraint>, + ) -> anyhow::Result<Vec<BasePackageHandle>> { + self.ensure_initialized()?; + self.inner.find_packages(name, constraint) + } + + fn get_packages(&mut self) -> anyhow::Result<Vec<BasePackageHandle>> { + self.ensure_initialized()?; + self.inner.get_packages() + } + + fn load_packages( + &mut self, + package_name_map: IndexMap<String, Option<shirabe_semver::constraint::AnyConstraint>>, + acceptable_stabilities: IndexMap<String, i64>, + stability_flags: IndexMap<String, i64>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, + ) -> anyhow::Result<LoadPackagesResult> { + self.ensure_initialized()?; + self.inner.load_packages( + package_name_map, + acceptable_stabilities, + stability_flags, + already_loaded, + ) + } + + fn search( + &mut self, + query: String, + mode: i64, + r#type: Option<String>, + ) -> anyhow::Result<Vec<SearchResult>> { + self.ensure_initialized()?; + self.inner.search(query, mode, r#type) + } + + fn get_providers( + &mut self, + package_name: String, + ) -> anyhow::Result<IndexMap<String, ProviderInfo>> { + self.ensure_initialized()?; + self.inner.get_providers(package_name) + } + + fn get_repo_name(&self) -> String { + PathRepository::get_repo_name(self) + } + + 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/vcs_repository.rs b/crates/shirabe/src/repository/vcs_repository.rs index c4e89bc..901276a 100644 --- a/crates/shirabe/src/repository/vcs_repository.rs +++ b/crates/shirabe/src/repository/vcs_repository.rs @@ -12,6 +12,8 @@ use crate::downloader::TransportException; use crate::event_dispatcher::EventDispatcher; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; +use crate::package::BasePackageHandle; +use crate::package::PackageInterfaceHandle; use crate::package::loader::ArrayLoader; use crate::package::loader::InvalidPackageException; use crate::package::loader::LoaderInterface; @@ -21,8 +23,10 @@ use crate::repository::ArrayRepository; use crate::repository::ConfigurableRepositoryInterface; use crate::repository::InvalidRepositoryException; use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceWeakHandle; use crate::repository::vcs::VcsDriverInterface; use crate::repository::vcs::VcsDriverKind; +use crate::repository::{FindPackageConstraint, LoadPackagesResult, ProviderInfo, SearchResult}; use crate::repository::{VersionCacheInterface, VersionCacheResult}; use crate::util::HttpDownloader; use crate::util::Platform; @@ -36,7 +40,10 @@ pub struct VcsRepository { /// @var string pub(crate) url: String, /// @var ?string - pub(crate) package_name: Option<String>, + /// + /// Interior mutability: set lazily by the (now `&self`) `initialize`, mirroring how PHP's + /// inherited ArrayRepository methods drive the overridden `initialize()` on first access. + pub(crate) package_name: std::cell::RefCell<Option<String>>, /// @var bool pub(crate) is_verbose: bool, /// @var bool @@ -46,11 +53,11 @@ pub struct VcsRepository { /// @var Config pub(crate) config: std::rc::Rc<std::cell::RefCell<Config>>, /// @var VersionParser - pub(crate) version_parser: Option<VersionParser>, + pub(crate) version_parser: std::cell::RefCell<Option<VersionParser>>, /// @var string pub(crate) r#type: String, /// @var ?LoaderInterface - pub(crate) loader: Option<Box<dyn LoaderInterface>>, + pub(crate) loader: std::cell::RefCell<Option<Box<dyn LoaderInterface>>>, /// @var array<string, mixed> pub(crate) repo_config: IndexMap<String, PhpMixed>, /// @var HttpDownloader @@ -58,20 +65,24 @@ pub struct VcsRepository { /// @var ProcessExecutor pub(crate) process_executor: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, /// @var bool - pub(crate) branch_error_occurred: bool, + pub(crate) branch_error_occurred: std::cell::Cell<bool>, /// @var array<string, class-string<VcsDriverInterface>> drivers: IndexMap<String, VcsDriverKind>, /// @var ?VcsDriverInterface - driver: Option<Box<dyn VcsDriverInterface>>, + /// + /// Interior mutability: memoized by `ensure_driver` (PHP `getDriver`), which is reached from + /// the `&self` RepositoryInterface methods (`count`, `has_package`, `get_repo_name`). + driver: std::cell::RefCell<Option<Box<dyn VcsDriverInterface>>>, /// Kind of the resolved `driver`, used by `get_repo_name` to recover the driver type /// (PHP `array_search(get_class($driver), $this->drivers)`). - driver_kind: Option<VcsDriverKind>, + driver_kind: std::cell::Cell<Option<VcsDriverKind>>, /// @var ?VersionCacheInterface version_cache: Option<Box<dyn VersionCacheInterface>>, /// @var list<string> - empty_references: Vec<String>, + empty_references: std::cell::RefCell<Vec<String>>, /// @var array<'tags'|'branches', array<string, TransportException>> - version_transport_exceptions: IndexMap<String, IndexMap<String, TransportException>>, + version_transport_exceptions: + std::cell::RefCell<IndexMap<String, IndexMap<String, TransportException>>>, /// @var ?EventDispatcher (preserved for plugin events) _dispatcher: Option<std::rc::Rc<std::cell::RefCell<EventDispatcher>>>, } @@ -134,33 +145,34 @@ impl VcsRepository { Ok(Self { inner, url, - package_name: None, + package_name: std::cell::RefCell::new(None), is_verbose, is_very_verbose, io, config, - version_parser: None, + version_parser: std::cell::RefCell::new(None), r#type, - loader: None, + loader: std::cell::RefCell::new(None), repo_config, http_downloader, process_executor, - branch_error_occurred: false, + branch_error_occurred: std::cell::Cell::new(false), drivers, - driver: None, - driver_kind: None, + driver: std::cell::RefCell::new(None), + driver_kind: std::cell::Cell::new(None), version_cache, - empty_references: vec![], - version_transport_exceptions: IndexMap::new(), + empty_references: std::cell::RefCell::new(vec![]), + version_transport_exceptions: std::cell::RefCell::new(IndexMap::new()), _dispatcher: dispatcher, }) } - pub fn get_repo_name(&mut self) -> String { + pub fn get_repo_name(&self) -> String { // Ensure the driver is resolved so `driver_kind` is populated. - let _ = self.get_driver().expect("driver should be available"); + self.ensure_driver(); + assert!(self.driver.borrow().is_some(), "driver should be available"); // PHP: array_search(get_class($driver), $this->drivers), falling back to the class name. - let driver_type = match self.driver_kind { + let driver_type = match self.driver_kind.get() { Some(kind) => self .drivers .iter() @@ -182,12 +194,16 @@ impl VcsRepository { } pub fn set_loader(&mut self, loader: Box<dyn LoaderInterface>) { - self.loader = Some(loader); + *self.loader.borrow_mut() = Some(loader); } - pub fn get_driver(&mut self) -> Option<&mut Box<dyn VcsDriverInterface>> { - if self.driver.is_some() { - return self.driver.as_mut(); + /// PHP `getDriver()` lazily instantiates and memoizes the matching VCS driver. Because Rust's + /// `RefCell` cannot hand out a `&mut` reference that outlives the borrow, this is split from the + /// reference-returning shape into a resolver: it populates `self.driver`/`self.driver_kind` once, + /// and callers borrow `self.driver` as needed afterwards. + pub fn ensure_driver(&self) { + if self.driver.borrow().is_some() { + return; } if let Some(kind) = self.drivers.get(&self.r#type).copied() { @@ -199,9 +215,9 @@ impl VcsRepository { self.process_executor.clone(), ); let _ = driver.initialize(); - self.driver = Some(driver); - self.driver_kind = Some(kind); - return self.driver.as_mut(); + *self.driver.borrow_mut() = Some(driver); + self.driver_kind.set(Some(kind)); + return; } let kinds: Vec<VcsDriverKind> = self.drivers.values().copied().collect(); @@ -219,9 +235,9 @@ impl VcsRepository { self.process_executor.clone(), ); let _ = driver.initialize(); - self.driver = Some(driver); - self.driver_kind = Some(*kind); - return self.driver.as_mut(); + *self.driver.borrow_mut() = Some(driver); + self.driver_kind.set(Some(*kind)); + return; } } @@ -238,29 +254,27 @@ impl VcsRepository { self.process_executor.clone(), ); let _ = driver.initialize(); - self.driver = Some(driver); - self.driver_kind = Some(*kind); - return self.driver.as_mut(); + *self.driver.borrow_mut() = Some(driver); + self.driver_kind.set(Some(*kind)); + return; } } - - None } pub fn had_invalid_branches(&self) -> bool { - self.branch_error_occurred + self.branch_error_occurred.get() } /// @return list<string> - pub fn get_empty_references(&self) -> &Vec<String> { - &self.empty_references + pub fn get_empty_references(&self) -> Vec<String> { + self.empty_references.borrow().clone() } /// @return array<'tags'|'branches', array<string, TransportException>> pub fn get_version_transport_exceptions( &self, - ) -> &IndexMap<String, IndexMap<String, TransportException>> { - &self.version_transport_exceptions + ) -> IndexMap<String, IndexMap<String, TransportException>> { + self.version_transport_exceptions.borrow().clone() } /// For testing only: drives `initialize` (which shells out to the VCS driver to discover @@ -273,49 +287,65 @@ impl VcsRepository { self.inner.get_packages() } - pub fn initialize(&mut self) -> Result<()> { + // In PHP the inherited ArrayRepository methods lazily call the overridden initialize() to drive + // the VCS driver and load each tag/branch package. Without virtual dispatch we trigger that load + // here before delegating to the inner repository; ArrayRepository's own lazy check then sees the + // populated array and skips re-initializing it. + fn ensure_initialized(&self) -> anyhow::Result<()> { + if !self.inner.is_initialized() { + self.initialize()?; + } + Ok(()) + } + + pub fn initialize(&self) -> Result<()> { self.inner.initialize(); let is_verbose = self.is_verbose; let is_very_verbose = self.is_very_verbose; let driver_url = self.url.clone(); - let driver = self.get_driver(); - if driver.is_none() { + self.ensure_driver(); + if self.driver.borrow().is_none() { return Err(InvalidArgumentException { message: format!("No driver found to handle VCS repository {}", driver_url), code: 0, } .into()); } - self.version_parser = Some(VersionParser::new()); - if self.loader.is_none() { - self.loader = Some(Box::new(ArrayLoader::new( - self.version_parser.clone(), - false, - ))); + *self.version_parser.borrow_mut() = Some(VersionParser::new()); + if self.loader.borrow().is_none() { + let version_parser = self.version_parser.borrow().clone(); + *self.loader.borrow_mut() = Some(Box::new(ArrayLoader::new(version_parser, false))); } let mut has_root_identifier_composer_json = false; - let root_identifier_result = self.driver.as_mut().unwrap().get_root_identifier(); + let root_identifier_result = self + .driver + .borrow_mut() + .as_mut() + .unwrap() + .get_root_identifier(); if let Ok(root_identifier) = root_identifier_result { - match self + let has_composer_file = self .driver + .borrow_mut() .as_mut() .unwrap() - .has_composer_file(&root_identifier) - { + .has_composer_file(&root_identifier); + match has_composer_file { Ok(b) => { has_root_identifier_composer_json = b; if has_root_identifier_composer_json { - match self + let composer_information = self .driver + .borrow_mut() .as_mut() .unwrap() - .get_composer_information(&root_identifier) - { + .get_composer_information(&root_identifier); + match composer_information { Ok(Some(data)) => { - self.package_name = data + *self.package_name.borrow_mut() = data .get("name") .and_then(|v| v.as_string()) .filter(|s| !s.is_empty()) @@ -354,12 +384,13 @@ impl VcsRepository { } } - let driver = self.driver.as_mut().unwrap(); - for (tag, identifier) in driver.get_tags()? { + let tags = self.driver.borrow_mut().as_mut().unwrap().get_tags()?; + for (tag, identifier) in tags { let mut tag = tag; let msg = format!( "Reading composer.json of <info>{}</info> (<comment>{}</comment>)", self.package_name + .borrow() .clone() .unwrap_or_else(|| self.url.clone()), tag @@ -381,7 +412,7 @@ impl VcsRepository { continue; } CachedPackageResult::Missing => { - self.empty_references.push(identifier.clone()); + self.empty_references.borrow_mut().push(identifier.clone()); continue; } CachedPackageResult::None => {} @@ -407,8 +438,12 @@ impl VcsRepository { } let result: Result<()> = (|| -> Result<()> { - let driver = self.driver.as_mut().unwrap(); - let data_opt = driver.get_composer_information(&identifier)?; + let data_opt = self + .driver + .borrow_mut() + .as_mut() + .unwrap() + .get_composer_information(&identifier)?; if data_opt.is_none() { if is_very_verbose { self.io.write_error(&format!( @@ -416,14 +451,14 @@ impl VcsRepository { tag )); } - self.empty_references.push(identifier.clone()); + self.empty_references.borrow_mut().push(identifier.clone()); return Ok(()); } let mut data = data_opt.unwrap(); // manually versioned package if data.contains_key("version") { - let normalized = self.version_parser.as_ref().unwrap().normalize( + let normalized = self.version_parser.borrow().as_ref().unwrap().normalize( data.get("version") .and_then(|v| v.as_string()) .unwrap_or(""), @@ -493,6 +528,7 @@ impl VcsRepository { let tag_package_name = self .package_name + .borrow() .clone() .filter(|s| !s.is_empty()) .unwrap_or_else(|| { @@ -501,7 +537,7 @@ impl VcsRepository { .unwrap_or("") .to_string() }); - if let Some(existing_package) = self.inner.find_package( + if let Some(existing_package) = self.inner.find_package_internal( &tag_package_name, crate::repository::FindPackageConstraint::Constraint( SimpleConstraint::new( @@ -526,20 +562,29 @@ impl VcsRepository { .write_error(&format!("Importing tag {} ({})", tag, version_normalized)); } - let driver = self.driver.as_ref().unwrap(); - let processed = self.pre_process(&**driver, data, &identifier)?; - let loaded = self.loader.as_ref().unwrap().load(processed, None)?; + let processed = { + let driver_ref = self.driver.borrow(); + let driver = driver_ref.as_ref().unwrap(); + self.pre_process(&**driver, data, &identifier)? + }; + let loaded = self + .loader + .borrow() + .as_ref() + .unwrap() + .load(processed, None)?; self.inner.add_package(loaded)?; Ok(()) })(); if let Err(e) = result { if let Some(te) = e.downcast_ref::<TransportException>() { self.version_transport_exceptions + .borrow_mut() .entry("tags".to_string()) .or_default() .insert(tag.clone(), te.clone()); if te.get_code() == 404 { - self.empty_references.push(identifier.clone()); + self.empty_references.borrow_mut().push(identifier.clone()); } if self.should_rethrow_transport_exception(te) { return Err(e); @@ -568,9 +613,14 @@ impl VcsRepository { .overwrite_error4("", false, None, io_interface::NORMAL); } - let mut branches = self.driver.as_mut().unwrap().get_branches()?; + let mut branches = self.driver.borrow_mut().as_mut().unwrap().get_branches()?; // make sure the root identifier branch gets loaded first - let root_identifier = self.driver.as_mut().unwrap().get_root_identifier()?; + let root_identifier = self + .driver + .borrow_mut() + .as_mut() + .unwrap() + .get_root_identifier()?; if has_root_identifier_composer_json && branches.contains_key(&root_identifier) { let mut new_branches: IndexMap<String, String> = IndexMap::new(); new_branches.insert( @@ -589,6 +639,7 @@ impl VcsRepository { let msg = format!( "Reading composer.json of <info>{}</info> (<comment>{}</comment>)", self.package_name + .borrow() .clone() .unwrap_or_else(|| self.url.clone()), branch @@ -632,7 +683,13 @@ impl VcsRepository { ); } - let is_default_branch = self.driver.as_mut().unwrap().get_root_identifier()? == branch; + let is_default_branch = self + .driver + .borrow_mut() + .as_mut() + .unwrap() + .get_root_identifier()? + == branch; let cached_package = self.get_cached_package_version( &version, &identifier, @@ -646,7 +703,7 @@ impl VcsRepository { continue; } CachedPackageResult::Missing => { - self.empty_references.push(identifier.clone()); + self.empty_references.borrow_mut().push(identifier.clone()); continue; } CachedPackageResult::None => {} @@ -655,6 +712,7 @@ impl VcsRepository { let result: Result<()> = (|| -> Result<()> { let data_opt = self .driver + .borrow_mut() .as_mut() .unwrap() .get_composer_information(&identifier)?; @@ -665,7 +723,7 @@ impl VcsRepository { branch )); } - self.empty_references.push(identifier.clone()); + self.empty_references.borrow_mut().push(identifier.clone()); return Ok(()); } let mut data = data_opt.unwrap(); @@ -678,7 +736,14 @@ impl VcsRepository { ); data.shift_remove("default-branch"); - if self.driver.as_mut().unwrap().get_root_identifier()? == branch { + if self + .driver + .borrow_mut() + .as_mut() + .unwrap() + .get_root_identifier()? + == branch + { data.insert("default-branch".to_string(), PhpMixed::Bool(true)); } @@ -692,10 +757,14 @@ impl VcsRepository { )); } - let driver = self.driver.as_ref().unwrap(); - let package_data = self.pre_process(&**driver, data, &identifier)?; + let package_data = { + let driver_ref = self.driver.borrow(); + let driver = driver_ref.as_ref().unwrap(); + self.pre_process(&**driver, data, &identifier)? + }; let package = self .loader + .borrow() .as_ref() .unwrap() .load(package_data.clone(), None)?; @@ -705,8 +774,8 @@ impl VcsRepository { // stored in `self.loader` and this downcast is always None. Production never calls // setLoader so the default ArrayLoader matches upstream, but the InvalidPackageException // path stays dead until the trait is reworked. - let loader_as_validating = self - .loader + let loader_ref = self.loader.borrow(); + let loader_as_validating = loader_ref .as_ref() .and_then(|l| l.as_any().downcast_ref::<ValidatingArrayLoader>()); if let Some(validating) = loader_as_validating @@ -719,17 +788,19 @@ impl VcsRepository { ) .into()); } + drop(loader_ref); self.inner.add_package(package)?; Ok(()) })(); if let Err(e) = result { if let Some(te) = e.downcast_ref::<TransportException>() { self.version_transport_exceptions + .borrow_mut() .entry("branches".to_string()) .or_default() .insert(branch.clone(), te.clone()); if te.get_code() == 404 { - self.empty_references.push(identifier.clone()); + self.empty_references.borrow_mut().push(identifier.clone()); } if self.should_rethrow_transport_exception(te) { return Err(e); @@ -745,21 +816,21 @@ impl VcsRepository { if !is_very_verbose { self.io.write_error(""); } - self.branch_error_occurred = true; + self.branch_error_occurred.set(true); self.io .write_error(&format!("<error>Skipped branch {}, {}</error>", branch, e)); self.io.write_error(""); continue; } } - self.driver.as_mut().unwrap().cleanup()?; + self.driver.borrow_mut().as_mut().unwrap().cleanup()?; if !is_very_verbose { self.io .overwrite_error4("", false, None, io_interface::NORMAL); } - if self.inner.get_packages()?.is_empty() { + if self.inner.get_packages_internal().is_empty() { return Err(InvalidRepositoryException::new(format!( "No valid composer.json was found in any branch or tag of {}, could not load a package from it.", self.url @@ -787,6 +858,7 @@ impl VcsRepository { .map(String::from); let name_value = self .package_name + .borrow() .clone() .filter(|s| !s.is_empty()) .or(data_package_name); @@ -853,6 +925,7 @@ impl VcsRepository { fn validate_branch(&self, branch: &str) -> Option<String> { let result = self .version_parser + .borrow() .as_ref() .unwrap() .normalize_branch(branch); @@ -860,6 +933,7 @@ impl VcsRepository { // validate that the branch name has no weird characters conflicting with constraints if self .version_parser + .borrow() .as_ref() .unwrap() .parse_constraints(&normalized_branch) @@ -875,6 +949,7 @@ impl VcsRepository { /// @return string|false fn validate_tag(&self, version: &str) -> Option<String> { self.version_parser + .borrow() .as_ref() .unwrap() .normalize(version, None) @@ -883,7 +958,7 @@ impl VcsRepository { /// @return \Composer\Package\CompletePackage|\Composer\Package\CompleteAliasPackage|null|false null if no cache present, false if the absence of a version was cached fn get_cached_package_version( - &mut self, + &self, version: &str, identifier: &str, is_verbose: bool, @@ -914,6 +989,7 @@ impl VcsRepository { let msg = format!( "Found cached composer.json of <info>{}</info> (<comment>{}</comment>)", self.package_name + .borrow() .clone() .unwrap_or_else(|| self.url.clone()), version @@ -940,7 +1016,7 @@ impl VcsRepository { .and_then(|v| v.as_string()) .unwrap_or("") .to_string(); - if let Some(existing_package) = self.inner.find_package( + if let Some(existing_package) = self.inner.find_package_internal( &name, crate::repository::FindPackageConstraint::Constraint( SimpleConstraint::new("=".to_string(), version_normalized.to_string(), None) @@ -958,7 +1034,7 @@ impl VcsRepository { } if let VersionCacheResult::Package(data) = cached_package { - let loaded = self.loader.as_ref().unwrap().load(data, None)?; + let loaded = self.loader.borrow().as_ref().unwrap().load(data, None)?; return Ok(CachedPackageResult::Package(loaded)); } @@ -978,6 +1054,93 @@ impl VcsRepository { } } +impl RepositoryInterface for VcsRepository { + // The structural methods are inherited from ArrayRepository in PHP, where the lazy package load + // is driven by the overridden initialize(). Here each one first ensures that load has happened + // (see ensure_initialized), then delegates to the inner ArrayRepository. + fn count(&self) -> anyhow::Result<usize> { + self.ensure_initialized()?; + self.inner.count() + } + + fn has_package(&self, package: PackageInterfaceHandle) -> bool { + // TODO(phase-d): hasPackage returns bool and cannot surface an initialization error; a + // failed load leaves the inner repository with whatever packages were added before the + // failure. + let _ = self.ensure_initialized(); + self.inner.has_package(package) + } + + fn find_package( + &mut self, + name: &str, + constraint: FindPackageConstraint, + ) -> anyhow::Result<Option<BasePackageHandle>> { + self.ensure_initialized()?; + self.inner.find_package(name, constraint) + } + + fn find_packages( + &mut self, + name: &str, + constraint: Option<FindPackageConstraint>, + ) -> anyhow::Result<Vec<BasePackageHandle>> { + self.ensure_initialized()?; + self.inner.find_packages(name, constraint) + } + + fn get_packages(&mut self) -> anyhow::Result<Vec<BasePackageHandle>> { + self.ensure_initialized()?; + self.inner.get_packages() + } + + fn load_packages( + &mut self, + package_name_map: IndexMap<String, Option<shirabe_semver::constraint::AnyConstraint>>, + acceptable_stabilities: IndexMap<String, i64>, + stability_flags: IndexMap<String, i64>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, + ) -> anyhow::Result<LoadPackagesResult> { + self.ensure_initialized()?; + self.inner.load_packages( + package_name_map, + acceptable_stabilities, + stability_flags, + already_loaded, + ) + } + + fn search( + &mut self, + query: String, + mode: i64, + r#type: Option<String>, + ) -> anyhow::Result<Vec<SearchResult>> { + self.ensure_initialized()?; + self.inner.search(query, mode, r#type) + } + + fn get_providers( + &mut self, + package_name: String, + ) -> anyhow::Result<IndexMap<String, ProviderInfo>> { + self.ensure_initialized()?; + self.inner.get_providers(package_name) + } + + fn get_repo_name(&self) -> String { + VcsRepository::get_repo_name(self) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } +} + #[derive(Debug)] enum CachedPackageResult { None, |
