diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-07 10:33:53 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-07 10:33:53 +0900 |
| commit | 971824aa15334fd12d08ae0f441f6bf6079344c3 (patch) | |
| tree | d59892042f478c65d980d96f62ef633644fd8fea /crates/shirabe/src | |
| parent | 0d45c403c20ac8950f2877a21f9cd4f0ca9bf784 (diff) | |
| download | php-shirabe-971824aa15334fd12d08ae0f441f6bf6079344c3.tar.gz php-shirabe-971824aa15334fd12d08ae0f441f6bf6079344c3.tar.zst php-shirabe-971824aa15334fd12d08ae0f441f6bf6079344c3.zip | |
feat(shirabe): resolve phase-b TODOs with shared ownership
Replace TODO(phase-b) placeholders (todo!() and commented-out code)
with real implementations:
- Share JsonFile via Rc<RefCell<JsonFile>> so JsonConfigSource and the
owning command can hold the same instance (base_config_command,
config_command, repository_command, require_command, create_project,
remove_command, factory)
- Change InstallerInterface methods (is_installed, download, prepare,
cleanup, get_install_path) to &mut self so initialize_vendor_dir can
run, propagated to all installer implementations
- Pass io/config/filesystem/process by clone instead of moving or
stubbing (auth_helper, svn_driver, curl_downloader, library_installer)
- Make TransportException Clone and store it by value in VcsRepository
- Clone operations in Transaction sort, root_aliases/temporary_constraints
in RepositorySet::create_pool, and share CompletePackage via handle in
PlatformRepository
- Wire up set_option, set_requires/set_dev_requires, installation manager
setters, BumpCommand::set_composer, and clean_backups/set_local_phar
Diffstat (limited to 'crates/shirabe/src')
32 files changed, 299 insertions, 327 deletions
diff --git a/crates/shirabe/src/command/base_command.rs b/crates/shirabe/src/command/base_command.rs index 8aff0d9..24b5a2b 100644 --- a/crates/shirabe/src/command/base_command.rs +++ b/crates/shirabe/src/command/base_command.rs @@ -564,11 +564,14 @@ impl<C: HasBaseCommandData> BaseCommand for C { let prefer_install = input.borrow().get_option("prefer-install"); match prefer_install.as_string().unwrap_or("") { "dist" => { - // TODO(phase-b): InputInterface set_option needs &mut self - let _ = "input.set_option('prefer-dist', true)"; + input + .borrow_mut() + .set_option("prefer-dist", PhpMixed::Bool(true))?; } "source" => { - let _ = "input.set_option('prefer-source', true)"; + input + .borrow_mut() + .set_option("prefer-source", PhpMixed::Bool(true))?; } "auto" => { prefer_dist = false; diff --git a/crates/shirabe/src/command/base_config_command.rs b/crates/shirabe/src/command/base_config_command.rs index 3aed839..d681870 100644 --- a/crates/shirabe/src/command/base_config_command.rs +++ b/crates/shirabe/src/command/base_config_command.rs @@ -15,9 +15,8 @@ use shirabe_php_shim::{PhpMixed, chmod, touch}; pub trait BaseConfigCommand: BaseCommand { fn config(&self) -> Option<&std::rc::Rc<std::cell::RefCell<Config>>>; fn config_mut(&mut self) -> &mut Option<std::rc::Rc<std::cell::RefCell<Config>>>; - fn config_file(&self) -> Option<&JsonFile>; - fn config_file_mut(&mut self) -> Option<&mut JsonFile>; - fn set_config_file(&mut self, file: Option<JsonFile>); + fn config_file(&self) -> Option<&std::rc::Rc<std::cell::RefCell<JsonFile>>>; + fn set_config_file(&mut self, file: Option<std::rc::Rc<std::cell::RefCell<JsonFile>>>); fn config_source(&self) -> Option<&JsonConfigSource>; fn config_source_mut(&mut self) -> Option<&mut JsonConfigSource>; fn set_config_source(&mut self, source: Option<JsonConfigSource>); @@ -40,8 +39,7 @@ pub trait BaseConfigCommand: BaseCommand { return Err(anyhow::anyhow!("--file and --global can not be combined")); } - // TODO(phase-b): clone_box to release the &mut self borrow held by get_io. - let io = self.get_io().clone(); + let io = self.get_io(); *self.config_mut() = Some(std::rc::Rc::new(std::cell::RefCell::new( Factory::create_config(Some(io.clone()), None)?, ))); @@ -68,14 +66,13 @@ pub trait BaseConfigCommand: BaseCommand { { std::fs::write(&config_file, "{\n}\n")?; } - self.set_config_file(Some(JsonFile::new( + let config_file_jf = std::rc::Rc::new(std::cell::RefCell::new(JsonFile::new( config_file.clone(), None, Some(io.clone()), )?)); - // TODO(phase-b): JsonConfigSource::new takes owned JsonFile, but PHP shares the same - // instance with $this->configFile. Needs Rc<RefCell<JsonFile>> refactor on both sides. - self.set_config_source(None); + self.set_config_file(Some(config_file_jf.clone())); + self.set_config_source(Some(JsonConfigSource::new(config_file_jf, false))); // Initialize the global file if it's not there, ignoring any warnings or notices if input @@ -83,13 +80,13 @@ pub trait BaseConfigCommand: BaseCommand { .get_option("global") .as_bool() .unwrap_or(false) - && !self.config_file().as_ref().unwrap().exists() + && !self.config_file().unwrap().borrow().exists() { - let path = self.config_file().as_ref().unwrap().get_path().to_string(); + let path = self.config_file().unwrap().borrow().get_path().to_string(); touch(&path); - self.config_file_mut() - .as_mut() + self.config_file() .unwrap() + .borrow() .write(PhpMixed::Array({ let mut m = IndexMap::new(); m.insert( @@ -104,7 +101,7 @@ pub trait BaseConfigCommand: BaseCommand { }); } - if !self.config_file().as_ref().unwrap().exists() { + if !self.config_file().unwrap().borrow().exists() { return Err(anyhow::anyhow!( "File \"{}\" cannot be found in the current directory", config_file diff --git a/crates/shirabe/src/command/config_command.rs b/crates/shirabe/src/command/config_command.rs index 38b8a8d..945dfcb 100644 --- a/crates/shirabe/src/command/config_command.rs +++ b/crates/shirabe/src/command/config_command.rs @@ -38,10 +38,10 @@ pub struct ConfigCommand { base_command_data: BaseCommandData, config: Option<std::rc::Rc<std::cell::RefCell<Config>>>, - config_file: Option<JsonFile>, + config_file: Option<std::rc::Rc<std::cell::RefCell<JsonFile>>>, config_source: Option<JsonConfigSource>, - pub(crate) auth_config_file: Option<JsonFile>, + pub(crate) auth_config_file: Option<std::rc::Rc<std::cell::RefCell<JsonFile>>>, pub(crate) auth_config_source: Option<JsonConfigSource>, } @@ -128,20 +128,19 @@ impl ConfigCommand { let auth_config_file = self.get_auth_config_file(input.clone(), &*self.config.as_ref().unwrap().borrow()); - self.auth_config_file = Some(JsonFile::new( + let auth_config_file_jf = std::rc::Rc::new(std::cell::RefCell::new(JsonFile::new( auth_config_file, None, Some(self.get_io().clone()), - )?); - // TODO(phase-b): JsonConfigSource::new takes owned JsonFile (PHP sharing semantics). - // Skipping auth_config_source assignment until Rc<RefCell<JsonFile>> refactor lands. - self.auth_config_source = None; + )?)); + self.auth_config_file = Some(auth_config_file_jf.clone()); + self.auth_config_source = Some(JsonConfigSource::new(auth_config_file_jf, true)); // Initialize the global file if it's not there, ignoring any warnings or notices if input.borrow().get_option("global").as_bool() == Some(true) - && !self.auth_config_file.as_ref().unwrap().exists() + && !self.auth_config_file.as_ref().unwrap().borrow().exists() { - touch(self.auth_config_file.as_ref().unwrap().get_path()); + touch(self.auth_config_file.as_ref().unwrap().borrow().get_path()); let mut empty_objs: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); for k in &[ "bitbucket-oauth", @@ -158,13 +157,15 @@ impl ConfigCommand { ); } self.auth_config_file - .as_mut() + .as_ref() .unwrap() + .borrow() .write(PhpMixed::Array(empty_objs))?; let path_clone = self .auth_config_file .as_ref() .unwrap() + .borrow() .get_path() .to_string(); Silencer::call(|| { @@ -205,10 +206,16 @@ impl ConfigCommand { self.auth_config_file .as_ref() .unwrap() + .borrow() .get_path() .to_string() } else { - self.config_file.as_ref().unwrap().get_path().to_string() + self.config_file + .as_ref() + .unwrap() + .borrow() + .get_path() + .to_string() }; system( &format!( @@ -228,7 +235,7 @@ impl ConfigCommand { } if input.borrow().get_option("global").as_bool() != Some(true) { - let config_read = self.config_file.as_mut().unwrap().read()?; + let config_read = self.config_file.as_ref().unwrap().borrow_mut().read()?; let config_map = match config_read { PhpMixed::Array(m) => m .into_iter() @@ -236,23 +243,25 @@ impl ConfigCommand { .collect::<IndexMap<String, PhpMixed>>(), _ => IndexMap::new(), }; - self.config - .as_mut() - .unwrap() - .borrow_mut() - .merge(&config_map, self.config_file.as_ref().unwrap().get_path()); - let auth_data: PhpMixed = if self.auth_config_file.as_ref().unwrap().exists() { - self.auth_config_file.as_mut().unwrap().read()? + self.config.as_mut().unwrap().borrow_mut().merge( + &config_map, + self.config_file.as_ref().unwrap().borrow().get_path(), + ); + let auth_data: PhpMixed = if self.auth_config_file.as_ref().unwrap().borrow().exists() { + self.auth_config_file + .as_ref() + .unwrap() + .borrow_mut() + .read()? } else { PhpMixed::Array(IndexMap::new()) }; let mut wrap: IndexMap<String, PhpMixed> = IndexMap::new(); wrap.insert("config".to_string(), auth_data); - self.config - .as_mut() - .unwrap() - .borrow_mut() - .merge(&wrap, self.auth_config_file.as_ref().unwrap().get_path()); + self.config.as_mut().unwrap().borrow_mut().merge( + &wrap, + self.auth_config_file.as_ref().unwrap().borrow().get_path(), + ); } { @@ -321,7 +330,7 @@ impl ConfigCommand { properties_defaults.insert("license".to_string(), PhpMixed::List(vec![])); properties_defaults.insert("suggest".to_string(), PhpMixed::List(vec![])); properties_defaults.insert("extra".to_string(), PhpMixed::List(vec![])); - let raw_data = self.config_file.as_mut().unwrap().read()?; + let raw_data = self.config_file.as_ref().unwrap().borrow_mut().read()?; let mut data = self.config.as_ref().unwrap().borrow_mut().all(0)?; let mut source = self .config @@ -464,7 +473,13 @@ impl ConfigCommand { && in_array(setting_key.as_str().into(), &properties.into(), true) { value = (**raw_data.as_array().unwrap().get(&setting_key).unwrap()).clone(); - source = self.config_file.as_ref().unwrap().get_path().to_string(); + source = self + .config_file + .as_ref() + .unwrap() + .borrow() + .get_path() + .to_string(); } else if let Some(v) = properties_defaults.get(&setting_key) { value = v.clone(); source = "defaults".to_string(); @@ -772,7 +787,8 @@ impl ConfigCommand { if input.borrow().get_option("json").as_bool() == Some(true) { value = JsonFile::parse_json(Some(&values[0]), Some("composer.json"))?; if input.borrow().get_option("merge").as_bool() == Some(true) { - let current_value_outer = self.config_file.as_mut().unwrap().read()?; + let current_value_outer = + self.config_file.as_ref().unwrap().borrow_mut().read()?; let bits = explode(".", &setting_key); let mut current_value: PhpMixed = current_value_outer; for bit in &bits { @@ -922,7 +938,7 @@ impl ConfigCommand { } if input.borrow().get_option("merge").as_bool() == Some(true) { - let current_config = self.config_file.as_mut().unwrap().read()?; + let current_config = self.config_file.as_ref().unwrap().borrow_mut().read()?; let key_suffix = str_replace("audit.", "", &setting_key); let current_value = current_config .as_array() @@ -2071,15 +2087,11 @@ impl BaseConfigCommand for ConfigCommand { &mut self.config } - fn config_file(&self) -> Option<&JsonFile> { + fn config_file(&self) -> Option<&std::rc::Rc<std::cell::RefCell<JsonFile>>> { self.config_file.as_ref() } - fn config_file_mut(&mut self) -> Option<&mut JsonFile> { - self.config_file.as_mut() - } - - fn set_config_file(&mut self, file: Option<JsonFile>) { + fn set_config_file(&mut self, file: Option<std::rc::Rc<std::cell::RefCell<JsonFile>>>) { self.config_file = file; } diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index d05f27d..0d5708d 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -113,8 +113,7 @@ impl CreateProjectCommand { _output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, ) -> Result<i64> { let config = std::rc::Rc::new(std::cell::RefCell::new(Factory::create_config(None, None)?)); - // TODO(phase-b): get_io returns &mut Self-borrow; clone_box for an owned Box to dodge. - let io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = self.get_io().clone(); + let io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> = self.get_io(); let (prefer_source, prefer_dist) = self.get_preferred_install_options(&config.borrow(), input.clone(), true)?; @@ -345,7 +344,11 @@ impl CreateProjectCommand { &placeholder_existing, ); let mut config_source = JsonConfigSource::new( - JsonFile::new("composer.json".to_string(), None, None)?, + std::rc::Rc::new(std::cell::RefCell::new(JsonFile::new( + "composer.json".to_string(), + None, + None, + )?)), false, ); @@ -544,7 +547,11 @@ impl CreateProjectCommand { if !has_vcs { let package = composer.get_package(); let mut config_source = JsonConfigSource::new( - JsonFile::new("composer.json".to_string(), None, None)?, + std::rc::Rc::new(std::cell::RefCell::new(JsonFile::new( + "composer.json".to_string(), + None, + None, + )?)), false, ); for (r#type, meta) in SUPPORTED_LINK_TYPES.iter() { diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs index 9bd49c2..4162676 100644 --- a/crates/shirabe/src/command/package_discovery_trait.rs +++ b/crates/shirabe/src/command/package_discovery_trait.rs @@ -260,8 +260,11 @@ pub trait PackageDiscoveryTrait { Some(s) => s.to_string(), None => break, }; - // TODO(phase-b): self.get_repos() (&mut self) conflicts with io borrow (&self) - let mut matches: Vec<SearchResult> = todo!("self.get_repos().search()"); + let mut matches: Vec<SearchResult> = self.get_repos().search( + package.clone(), + crate::repository::repository_interface::SEARCH_FULLTEXT, + None, + )?; if count(&PhpMixed::List( matches.iter().map(|_| Box::new(PhpMixed::Null)).collect(), @@ -291,9 +294,8 @@ pub trait PackageDiscoveryTrait { // no match, prompt which to pick if !exact_match { - // TODO(phase-b): self.get_repos() (&mut self) conflicts with io borrow (&self) let providers: IndexMap<String, crate::repository::ProviderInfo> = - todo!("self.get_repos().get_providers()"); + self.get_repos().get_providers(package.clone())?; if count(&PhpMixed::List( providers.iter().map(|_| Box::new(PhpMixed::Null)).collect(), )) > 0 @@ -449,7 +451,7 @@ pub trait PackageDiscoveryTrait { let (_name, c): (String, String) = self .find_best_version_and_name_for_package( io.clone(), - input, + input.clone(), &package, platform_repo, preferred_stability, diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index 6e4cd7b..e72a177 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -195,17 +195,21 @@ impl ReinstallCommand { let download_manager = composer.get_download_manager(); let package = composer.get_package(); - // TODO(phase-b): InstallationManager setters need &mut self; conflicts with the &installation_manager / &local_repo / &package borrows held below; needs shared-ownership refactor - let _no_progress = !input - .borrow() - .get_option("no-progress") - .as_bool() - .unwrap_or(false); - let _no_plugins = input + installation_manager.borrow_mut().set_output_progress( + !input + .borrow() + .get_option("no-progress") + .as_bool() + .unwrap_or(false), + ); + if input .borrow() .get_option("no-plugins") .as_bool() - .unwrap_or(false); + .unwrap_or(false) + { + installation_manager.borrow_mut().disable_plugins(); + } download_manager .borrow_mut() diff --git a/crates/shirabe/src/command/remove_command.rs b/crates/shirabe/src/command/remove_command.rs index 2cf71e7..c2a358b 100644 --- a/crates/shirabe/src/command/remove_command.rs +++ b/crates/shirabe/src/command/remove_command.rs @@ -270,7 +270,10 @@ impl RemoveCommand { let composer_backup = std::fs::read_to_string(json_file.get_path())?; let json_file_for_source = JsonFile::new(file.clone(), None, None)?; - let mut json = JsonConfigSource::new(json_file_for_source, false); + let mut json = JsonConfigSource::new( + std::rc::Rc::new(std::cell::RefCell::new(json_file_for_source)), + false, + ); let r#type = if input.borrow().get_option("dev").as_bool().unwrap_or(false) { "require-dev" @@ -464,7 +467,6 @@ impl RemoveCommand { let mut composer = crate::command::composer_full_mut(&composer_handle); if dry_run { - // TODO(phase-b): composer.get_package() returns &dyn RootPackageInterface; set_requires/set_dev_requires need &mut self; needs shared-ownership refactor let root_package = composer.get_package(); let mut links: IndexMap<String, IndexMap<String, _>> = IndexMap::new(); links.insert("require".to_string(), root_package.get_requires().clone()); @@ -479,10 +481,8 @@ impl RemoveCommand { } } } - let _ = links.remove("require").unwrap_or_default(); - let _ = links.remove("require-dev").unwrap_or_default(); - // root_package.set_requires(links.remove("require").unwrap_or_default().into_values().collect()); - // root_package.set_dev_requires(links.remove("require-dev").unwrap_or_default().into_values().collect()); + root_package.set_requires(links.remove("require").unwrap_or_default()); + root_package.set_dev_requires(links.remove("require-dev").unwrap_or_default()); } // TODO(plugin): dispatch CommandEvent(PluginEvents::COMMAND, 'remove', input, output) diff --git a/crates/shirabe/src/command/repository_command.rs b/crates/shirabe/src/command/repository_command.rs index 5ced013..975dd32 100644 --- a/crates/shirabe/src/command/repository_command.rs +++ b/crates/shirabe/src/command/repository_command.rs @@ -25,7 +25,7 @@ pub struct RepositoryCommand { base_command_data: BaseCommandData, config: Option<std::rc::Rc<std::cell::RefCell<Config>>>, - config_file: Option<JsonFile>, + config_file: Option<std::rc::Rc<std::cell::RefCell<JsonFile>>>, config_source: Option<JsonConfigSource>, } @@ -95,8 +95,14 @@ impl RepositoryCommand { .as_string() .map(|s| s.to_string()); - let config_data = self.config_file.as_mut().unwrap().read()?; - let config_file_path = self.config_file.as_ref().unwrap().get_path().to_string(); + let config_data = self.config_file.as_ref().unwrap().borrow_mut().read()?; + let config_file_path = self + .config_file + .as_ref() + .unwrap() + .borrow() + .get_path() + .to_string(); let config_data_map: IndexMap<String, PhpMixed> = match config_data { PhpMixed::Array(m) => m.into_iter().map(|(k, v)| (k, *v)).collect(), _ => IndexMap::new(), @@ -416,14 +422,10 @@ impl BaseConfigCommand for RepositoryCommand { &mut self.config } - fn config_file(&self) -> Option<&JsonFile> { + fn config_file(&self) -> Option<&std::rc::Rc<std::cell::RefCell<JsonFile>>> { self.config_file.as_ref() } - fn config_file_mut(&mut self) -> Option<&mut JsonFile> { - self.config_file.as_mut() - } - fn config_source(&self) -> Option<&JsonConfigSource> { self.config_source.as_ref() } @@ -432,7 +434,7 @@ impl BaseConfigCommand for RepositoryCommand { self.config_source.as_mut() } - fn set_config_file(&mut self, file: Option<JsonFile>) { + fn set_config_file(&mut self, file: Option<std::rc::Rc<std::cell::RefCell<JsonFile>>>) { self.config_file = file; } diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index d1143fe..871b07a 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -50,7 +50,7 @@ pub struct RequireCommand { newly_created: bool, first_require: bool, - json: Option<JsonFile>, + json: Option<std::rc::Rc<std::cell::RefCell<JsonFile>>>, file: String, composer_backup: String, /// file name @@ -186,10 +186,14 @@ impl RequireCommand { file_put_contents(&self.file, b"{\n}\n"); } - self.json = Some(JsonFile::new(self.file.clone(), None, None)?); + self.json = Some(std::rc::Rc::new(std::cell::RefCell::new(JsonFile::new( + self.file.clone(), + None, + None, + )?))); self.lock = Factory::get_lock_file(&self.file); self.composer_backup = - file_get_contents(self.json.as_ref().unwrap().get_path()).unwrap_or_default(); + file_get_contents(self.json.as_ref().unwrap().borrow().get_path()).unwrap_or_default(); self.lock_backup = if file_exists(&self.lock) { file_get_contents(&self.lock) } else { @@ -231,7 +235,7 @@ impl RequireCommand { } if input.borrow().get_option("fixed").as_bool() == Some(true) { - let config = self.json.as_mut().unwrap().read()?; + let config = self.json.as_ref().unwrap().borrow_mut().read()?; let package_type = if empty(&config.get("type").cloned().unwrap_or(PhpMixed::Null)) { "library".to_string() @@ -527,7 +531,7 @@ impl RequireCommand { self.first_require = self.newly_created; if !self.first_require { - let composer_definition = self.json.as_mut().unwrap().read()?; + let composer_definition = self.json.as_ref().unwrap().borrow_mut().read()?; let require_count = composer_definition .get("require") .and_then(|v| v.as_array()) @@ -549,18 +553,8 @@ impl RequireCommand { .as_bool() .unwrap_or(false) { - // TODO(phase-b): update_file takes &mut self, but the json argument must be borrowed - // from self.json, producing an overlapping borrow of self. Commented out until JsonFile - // is shared (Rc<RefCell> / interior mutability) so it can be passed without holding a - // borrow of self. - // self.update_file( - // self.json.as_ref().unwrap(), - // &requirements, - // require_key, - // remove_key, - // sort_packages, - // ); - let _ = sort_packages; + let json = self.json.as_ref().unwrap().clone(); + self.update_file(&json, &requirements, require_key, remove_key, sort_packages); } let updated_msg = format!( @@ -635,7 +629,7 @@ impl RequireCommand { // finally if dry_run && self.newly_created { // @unlink($this->json->getPath()); - unlink(self.json.as_ref().unwrap().get_path()); + unlink(self.json.as_ref().unwrap().borrow().get_path()); } signal_handler.unregister(); @@ -665,7 +659,13 @@ impl RequireCommand { /// @return array<string, string> fn get_packages_by_require_key(&mut self) -> IndexMap<String, String> { - let composer_definition = self.json.as_mut().unwrap().read().unwrap_or_default(); + let composer_definition = self + .json + .as_ref() + .unwrap() + .borrow_mut() + .read() + .unwrap_or_default(); let mut require: IndexMap<String, PhpMixed> = IndexMap::new(); let mut require_dev: IndexMap<String, PhpMixed> = IndexMap::new(); @@ -1122,18 +1122,8 @@ impl RequireCommand { } if !dry_run { - // TODO(phase-b): update_file takes &mut self while self.json is borrowed; needs - // refactor to pass the JsonFile owned/cloned or use interior mutability. - let json_path = self.json.as_ref().unwrap().get_path().to_string(); - let _ = ( - json_path, - &requirements, - require_key, - remove_key, - sort_packages, - ); - todo!("call self.update_file without overlapping borrows of self.json"); - #[allow(unreachable_code)] + let json = self.json.as_ref().unwrap().clone(); + self.update_file(&json, &requirements, require_key, remove_key, sort_packages); if locker_is_locked && composer .get_config() @@ -1160,7 +1150,7 @@ impl RequireCommand { /// @param array<string, string> $new fn update_file( &mut self, - json: &JsonFile, + json: &std::rc::Rc<std::cell::RefCell<JsonFile>>, new: &IndexMap<String, String>, require_key: &str, remove_key: &str, @@ -1170,7 +1160,7 @@ impl RequireCommand { return; } - let composer_definition_mixed = self.json.as_mut().unwrap().read().unwrap_or_default(); + let composer_definition_mixed = json.borrow_mut().read().unwrap_or_default(); let mut composer_definition: IndexMap<String, Box<PhpMixed>> = composer_definition_mixed .as_array() .cloned() @@ -1197,23 +1187,19 @@ impl RequireCommand { composer_definition.shift_remove(remove_key); } } - let _ = self - .json - .as_ref() - .unwrap() - .write(PhpMixed::Array(composer_definition)); + let _ = json.borrow().write(PhpMixed::Array(composer_definition)); } /// @param array<string, string> $new fn update_file_cleanly( &self, - json: &JsonFile, + json: &std::rc::Rc<std::cell::RefCell<JsonFile>>, new: &IndexMap<String, String>, require_key: &str, remove_key: &str, sort_packages: bool, ) -> bool { - let contents = file_get_contents(json.get_path()).unwrap_or_default(); + let contents = file_get_contents(json.borrow().get_path()).unwrap_or_default(); let mut manipulator = match JsonManipulator::new(contents) { Ok(m) => m, @@ -1237,7 +1223,10 @@ impl RequireCommand { let _ = manipulator.remove_main_key_if_empty(remove_key); - file_put_contents(json.get_path(), manipulator.get_contents().as_bytes()); + file_put_contents( + json.borrow().get_path(), + manipulator.get_contents().as_bytes(), + ); true } @@ -1256,7 +1245,7 @@ impl RequireCommand { self.file ); self.get_io().write_error3(&msg, true, io_interface::NORMAL); - unlink(self.json.as_ref().unwrap().get_path()); + unlink(self.json.as_ref().unwrap().borrow().get_path()); if file_exists(&self.lock) { unlink(&self.lock); } @@ -1272,7 +1261,7 @@ impl RequireCommand { ); self.get_io().write_error3(&msg, true, io_interface::NORMAL); file_put_contents( - self.json.as_ref().unwrap().get_path(), + self.json.as_ref().unwrap().borrow().get_path(), self.composer_backup.as_bytes(), ); if let Some(ref lock_backup) = self.lock_backup { diff --git a/crates/shirabe/src/command/self_update_command.rs b/crates/shirabe/src/command/self_update_command.rs index 2353d13..9c5ae5a 100644 --- a/crates/shirabe/src/command/self_update_command.rs +++ b/crates/shirabe/src/command/self_update_command.rs @@ -646,19 +646,16 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ .as_bool() .unwrap_or(false) { - // TODO(phase-b): self.clean_backups conflicts with earlier `self.get_io()` borrow + self.clean_backups(&rollback_dir, None); } - // TODO(phase-b): self.set_local_phar mutable borrow conflicts with earlier `self.get_io()` borrow - let _set_phar_ok: bool = todo!("self.set_local_phar(...)"); - if !_set_phar_ok { + if !self.set_local_phar(&local_filename, &temp_filename, Some(&backup_file))? { // @unlink let _ = unlink(&temp_filename); return Ok(1); } - // TODO(phase-b): re-borrow io because earlier borrow conflicts with the &mut self calls above let io = self.get_io(); if file_exists(&backup_file) { io.write_error3( diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs index 859ba97..4f771a8 100644 --- a/crates/shirabe/src/command/show_command.rs +++ b/crates/shirabe/src/command/show_command.rs @@ -373,11 +373,7 @@ impl ShowCommand { locked_repo = Some(lr_handle); } else { // --installed / default case - // TODO(phase-b): PHP shares the Composer object by reference. Phase B - // can't clone Composer, so we re-fetch via require_composer when missing - // but otherwise borrow the existing Option. let composer_local_owned; - // Borrow guards that keep the Ref alive for the duration of the block. let _guard_from_existing; let composer_local = match composer.as_ref() { Some(c) => { @@ -1084,11 +1080,14 @@ impl ShowCommand { } } if write_path { - // TODO(phase-b): get_installation_manager wants &mut Composer; PHP shares by ref. - let path: Option<String> = { - let _ = composer.as_ref().unwrap(); - None - }; + let installation_manager = composer + .as_ref() + .unwrap() + .borrow_partial() + .get_installation_manager(); + let path: Option<String> = installation_manager + .borrow_mut() + .get_install_path(package.clone()); if let Some(p) = path { let r = realpath(&p).unwrap_or_default(); let trimmed = @@ -1702,11 +1701,12 @@ impl ShowCommand { package.get_dist_reference().unwrap_or_default() )); if is_installed_package { - // TODO(phase-b): get_installation_manager wants &mut Composer; PHP shares by ref. - // Skipping the install path lookup keeps compile clean. let path: Option<String> = self.require_composer(None, None).ok().and_then(|c| { - let _ = c; - None::<String> + let installation_manager = c.borrow_partial().get_installation_manager(); + let p = installation_manager + .borrow_mut() + .get_install_path(package.clone().into()); + p }); if let Some(p) = path { self.get_io().write(&format!( @@ -1982,9 +1982,11 @@ impl ShowCommand { if !PlatformRepository::is_platform_package(&package.get_name()) && installed_repo.has_package(package.clone().into()) { - // TODO(phase-b): get_installation_manager wants &mut Composer; PHP shares by ref. - let _ = self.require_composer(None, None)?; - let path: Option<String> = None; + let composer = self.require_composer(None, None)?; + let installation_manager = composer.borrow_partial().get_installation_manager(); + let path: Option<String> = installation_manager + .borrow_mut() + .get_install_path(package.clone().into()); match path { Some(p) => { if let Some(r) = realpath(&p) { diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs index 7731e4f..4064e57 100644 --- a/crates/shirabe/src/command/update_command.rs +++ b/crates/shirabe/src/command/update_command.rs @@ -518,9 +518,7 @@ impl UpdateCommand { io_interface::NORMAL, ); let mut bump_command = BumpCommand::new(None); - // TODO(phase-b): Composer is a PHP class shared by reference; calling - // set_composer here requires a shared PartialComposer handle. - // bump_command.set_composer(composer); + bump_command.set_composer(composer_handle.clone()); result = bump_command.do_bump( io.clone(), bump_after_update.as_string() == Some("dev"), diff --git a/crates/shirabe/src/config/json_config_source.rs b/crates/shirabe/src/config/json_config_source.rs index 5d81793..532c1d8 100644 --- a/crates/shirabe/src/config/json_config_source.rs +++ b/crates/shirabe/src/config/json_config_source.rs @@ -18,7 +18,7 @@ use crate::util::Filesystem; #[derive(Debug)] pub struct JsonConfigSource { /// @var JsonFile - file: JsonFile, + file: std::rc::Rc<std::cell::RefCell<JsonFile>>, /// @var bool auth_config: bool, @@ -26,7 +26,7 @@ pub struct JsonConfigSource { impl JsonConfigSource { /// Constructor - pub fn new(file: JsonFile, auth_config: bool) -> Self { + pub fn new(file: std::rc::Rc<std::cell::RefCell<JsonFile>>, auth_config: bool) -> Self { Self { file, auth_config } } @@ -39,30 +39,30 @@ impl JsonConfigSource { mut args: Vec<PhpMixed>, ) -> Result<()> { let contents; - if self.file.exists() { - if !is_writable(self.file.get_path()) { + if self.file.borrow().exists() { + if !is_writable(self.file.borrow().get_path()) { return Err(RuntimeException { message: sprintf( "The file \"%s\" is not writable.", - &[PhpMixed::String(self.file.get_path().to_string())], + &[PhpMixed::String(self.file.borrow().get_path().to_string())], ), code: 0, } .into()); } - if !Filesystem::is_readable(self.file.get_path()) { + if !Filesystem::is_readable(self.file.borrow().get_path()) { return Err(RuntimeException { message: sprintf( "The file \"%s\" is not readable.", - &[PhpMixed::String(self.file.get_path().to_string())], + &[PhpMixed::String(self.file.borrow().get_path().to_string())], ), code: 0, } .into()); } - contents = file_get_contents(self.file.get_path()).unwrap_or_default(); + contents = file_get_contents(self.file.borrow().get_path()).unwrap_or_default(); } else if self.auth_config { contents = "{\n}\n".to_string(); } else { @@ -71,7 +71,7 @@ impl JsonConfigSource { let mut manipulator = JsonManipulator::new(contents.clone())?; - let new_file = !self.file.exists(); + let new_file = !self.file.borrow().exists(); // override manipulator method for auth config files let mut method = method.to_string(); @@ -103,10 +103,13 @@ impl JsonConfigSource { .as_bool() .unwrap_or(false); if manipulator_result { - file_put_contents(self.file.get_path(), manipulator.get_contents().as_bytes()); + file_put_contents( + self.file.borrow().get_path(), + manipulator.get_contents().as_bytes(), + ); } else { // on failed clean update, call the fallback and rewrite the whole file - let mut config = self.file.read()?; + let mut config = self.file.borrow_mut().read()?; self.array_unshift_ref(&mut args, &mut config); fallback(&mut config, &mut args); // avoid ending up with arrays for keys that should be objects @@ -199,17 +202,21 @@ impl JsonConfigSource { } } } - self.file.write(config)?; + self.file.borrow().write(config)?; } - match self.file.validate_schema(JsonFile::LAX_SCHEMA, None) { + match self + .file + .borrow() + .validate_schema(JsonFile::LAX_SCHEMA, None) + { Ok(_) => {} Err(e) => { let Some(jve) = e.downcast_ref::<JsonValidationException>() else { return Err(e); }; // restore contents to the original state - file_put_contents(self.file.get_path(), contents.as_bytes()); + file_put_contents(self.file.borrow().get_path(), contents.as_bytes()); return Err(RuntimeException { message: format!( "Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json). {}{}", @@ -223,7 +230,7 @@ impl JsonConfigSource { } if new_file { - let path = self.file.get_path().to_string(); + let path = self.file.borrow().get_path().to_string(); let _ = Silencer::call(|| { chmod(&path, 0o600); Ok(()) @@ -250,7 +257,7 @@ impl JsonConfigSource { impl ConfigSourceInterface for JsonConfigSource { fn get_name(&self) -> String { - self.file.get_path().to_string() + self.file.borrow().get_path().to_string() } fn add_repository(&mut self, name: &str, config: PhpMixed, append: bool) -> Result<()> { diff --git a/crates/shirabe/src/dependency_resolver/transaction.rs b/crates/shirabe/src/dependency_resolver/transaction.rs index 874085f..f9ee5f0 100644 --- a/crates/shirabe/src/dependency_resolver/transaction.rs +++ b/crates/shirabe/src/dependency_resolver/transaction.rs @@ -231,10 +231,8 @@ impl Transaction { // TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place? // if ('update' === $opType) { ... } - // PHP: return $this->operations = $operations; - // TODO(phase-b): self.operations assignment plus return — caller needs owned Vec - self.operations = todo!("operations cloned for both assignment and return"); - todo!("return cloned operations") + self.operations = operations.clone(); + operations } /// Determine which packages in the result are not required by any other packages in it. @@ -339,19 +337,12 @@ impl Transaction { // is this a plugin with no meaningful dependencies? if is_downloads_modifying_plugin && requires.is_empty() { // plugins with no dependencies go to the very front - // TODO(phase-b): move ownership of operations[idx] into the new vec - array_unshift( - &mut dl_modifying_plugins_no_deps, - todo!("operations[idx] moved out"), - ); + array_unshift(&mut dl_modifying_plugins_no_deps, operations[idx].clone()); } else { // capture the requirements for this package so those packages will be moved up as well dl_modifying_plugin_requires.extend(requires); // move the operation to the front - array_unshift( - &mut dl_modifying_plugins_with_deps, - todo!("operations[idx] moved out"), - ); + array_unshift(&mut dl_modifying_plugins_with_deps, operations[idx].clone()); } to_remove.push(idx); @@ -373,12 +364,12 @@ impl Transaction { // is this a plugin with no meaningful dependencies? if is_plugin && requires.is_empty() { // plugins with no dependencies go to the very front - array_unshift(&mut plugins_no_deps, todo!("operations[idx] moved out")); + array_unshift(&mut plugins_no_deps, operations[idx].clone()); } else { // capture the requirements for this package so those packages will be moved up as well plugin_requires.extend(requires); // move the operation to the front - array_unshift(&mut plugins_with_deps, todo!("operations[idx] moved out")); + array_unshift(&mut plugins_with_deps, operations[idx].clone()); } to_remove.push(idx); @@ -424,8 +415,7 @@ impl Transaction { .downcast_ref::<MarkAliasUninstalledOperation>() .is_some(); if is_uninstall { - // TODO(phase-b): move ownership out of operations[idx] - uninst_ops.push(todo!("operations[idx] moved out")); + uninst_ops.push(op.clone()); to_remove.push(idx); } } diff --git a/crates/shirabe/src/downloader/transport_exception.rs b/crates/shirabe/src/downloader/transport_exception.rs index 9d5b352..c457e1b 100644 --- a/crates/shirabe/src/downloader/transport_exception.rs +++ b/crates/shirabe/src/downloader/transport_exception.rs @@ -2,7 +2,7 @@ use shirabe_php_shim::PhpMixed; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TransportException { pub message: String, pub code: i64, diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index cdad33c..95511b0 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -295,7 +295,10 @@ impl Factory { config.merge(&read_data, &file_path_owned); } // TODO(phase-b): set_config_source takes Box<dyn ConfigSourceInterface> - config.set_config_source(Box::new(JsonConfigSource::new(file, false))); + config.set_config_source(Box::new(JsonConfigSource::new( + std::rc::Rc::new(std::cell::RefCell::new(file)), + false, + ))); let htaccess_protect = config.get("htaccess-protect").as_bool().unwrap_or(false); if htaccess_protect { @@ -359,7 +362,10 @@ impl Factory { config.merge(&wrapped, &auth_path_owned); } // TODO(phase-b): set_auth_config_source takes Box<dyn ConfigSourceInterface> - config.set_auth_config_source(Box::new(JsonConfigSource::new(auth_file, true))); + config.set_auth_config_source(Box::new(JsonConfigSource::new( + std::rc::Rc::new(std::cell::RefCell::new(auth_file)), + true, + ))); Self::load_composer_auth_env(&mut config, io)?; @@ -526,11 +532,11 @@ impl Factory { crate::io::DEBUG, ); config.set_config_source(Box::new(JsonConfigSource::new( - JsonFile::new( + std::rc::Rc::new(std::cell::RefCell::new(JsonFile::new( realpath(composer_file_path).unwrap_or_default(), None, Some(io.clone()), - )?, + )?)), false, ))); @@ -557,7 +563,7 @@ impl Factory { let auth_path = local_auth_file.get_path().to_string(); config.merge(&wrapped, &auth_path); config.set_local_auth_config_source(Box::new(JsonConfigSource::new( - local_auth_file, + std::rc::Rc::new(std::cell::RefCell::new(local_auth_file)), true, ))); } diff --git a/crates/shirabe/src/installer.rs b/crates/shirabe/src/installer.rs index ff8a80d..4de52b5 100644 --- a/crates/shirabe/src/installer.rs +++ b/crates/shirabe/src/installer.rs @@ -255,9 +255,8 @@ impl Installer { self.execute_operations = false; self.write_lock = false; self.dump_autoloader = false; - // TODO(phase-b): borrow conflict: passing &mut self.repository_manager while &self - // is implicit. Refactor mock_local_repositories or split borrow. - // self.mock_local_repositories(&mut self.repository_manager); + let repository_manager = self.repository_manager.clone(); + self.mock_local_repositories(&mut repository_manager.borrow_mut())?; } if self.download_only { diff --git a/crates/shirabe/src/installer/installer_interface.rs b/crates/shirabe/src/installer/installer_interface.rs index 20fdb2c..6df00c6 100644 --- a/crates/shirabe/src/installer/installer_interface.rs +++ b/crates/shirabe/src/installer/installer_interface.rs @@ -11,19 +11,19 @@ pub trait InstallerInterface: std::fmt::Debug { fn supports(&self, package_type: &str) -> bool; fn is_installed( - &self, + &mut self, repo: &dyn InstalledRepositoryInterface, package: PackageInterfaceHandle, ) -> bool; async fn download( - &self, + &mut self, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>>; async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, @@ -49,13 +49,13 @@ pub trait InstallerInterface: std::fmt::Debug { ) -> anyhow::Result<Option<PhpMixed>>; async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>>; - fn get_install_path(&self, package: PackageInterfaceHandle) -> Option<String>; + fn get_install_path(&mut self, package: PackageInterfaceHandle) -> Option<String>; fn as_binary_presence_interface(&mut self) -> Option<&mut dyn BinaryPresenceInterface> { None diff --git a/crates/shirabe/src/installer/library_installer.rs b/crates/shirabe/src/installer/library_installer.rs index 58706d6..daf5e33 100644 --- a/crates/shirabe/src/installer/library_installer.rs +++ b/crates/shirabe/src/installer/library_installer.rs @@ -65,8 +65,7 @@ impl LibraryInstaller { ); let binary_installer = binary_installer.unwrap_or_else(|| { BinaryInstaller::new( - // TODO(phase-b): pass io by reference/clone - todo!("io reference"), + io.clone(), rtrim( &composer_ref .get_config() @@ -81,8 +80,7 @@ impl LibraryInstaller { .borrow_mut() .get_str("bin-compat") .unwrap_or_default(), - // TODO(phase-b): pass filesystem reference - todo!("filesystem reference"), + Some(filesystem.clone()), Some(vendor_dir.clone()), ) }); @@ -109,7 +107,7 @@ impl LibraryInstaller { /// /// It is used for BC as getInstallPath tends to be overridden by /// installer plugins but not getPackageBasePath - pub(crate) fn get_package_base_path(&self, package: PackageInterfaceHandle) -> String { + pub(crate) fn get_package_base_path(&mut self, package: PackageInterfaceHandle) -> String { let install_path = self.get_install_path(package.clone()).unwrap(); let target_dir = package.get_target_dir(); @@ -133,7 +131,7 @@ impl LibraryInstaller { /// @return PromiseInterface|null /// @phpstan-return PromiseInterface<void|null>|null pub(crate) async fn install_code( - &self, + &mut self, package: PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { let download_path = self.get_install_path(package.clone()).unwrap(); @@ -147,7 +145,7 @@ impl LibraryInstaller { /// @return PromiseInterface|null /// @phpstan-return PromiseInterface<void|null>|null pub(crate) async fn update_code( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { @@ -178,7 +176,7 @@ impl LibraryInstaller { /// @return PromiseInterface|null /// @phpstan-return PromiseInterface<void|null>|null pub(crate) async fn remove_code( - &self, + &mut self, package: PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { let download_path = self.get_package_base_path(package.clone()); @@ -226,7 +224,7 @@ impl InstallerInterface for LibraryInstaller { } fn is_installed( - &self, + &mut self, repo: &dyn InstalledRepositoryInterface, package: PackageInterfaceHandle, ) -> bool { @@ -256,12 +254,11 @@ impl InstallerInterface for LibraryInstaller { } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - // TODO(phase-b): initialize_vendor_dir requires &mut self - // self.initialize_vendor_dir(); + self.initialize_vendor_dir(); let download_path = self.get_install_path(package.clone()).unwrap(); self.get_download_manager() @@ -271,13 +268,12 @@ impl InstallerInterface for LibraryInstaller { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - // TODO(phase-b): initialize_vendor_dir requires &mut self - // self.initialize_vendor_dir(); + self.initialize_vendor_dir(); let download_path = self.get_install_path(package.clone()).unwrap(); self.get_download_manager() @@ -287,13 +283,12 @@ impl InstallerInterface for LibraryInstaller { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - // TODO(phase-b): initialize_vendor_dir requires &mut self - // self.initialize_vendor_dir(); + self.initialize_vendor_dir(); let download_path = self.get_install_path(package.clone()).unwrap(); self.get_download_manager() @@ -307,8 +302,7 @@ impl InstallerInterface for LibraryInstaller { repo: &mut dyn InstalledRepositoryInterface, package: PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { - // TODO(phase-b): initialize_vendor_dir requires &mut self - // self.initialize_vendor_dir(); + self.initialize_vendor_dir(); let download_path = self.get_install_path(package.clone()).unwrap(); // remove the binaries if it appears the package files are missing @@ -342,8 +336,7 @@ impl InstallerInterface for LibraryInstaller { .into()); } - // TODO(phase-b): initialize_vendor_dir requires &mut self - // self.initialize_vendor_dir(); + self.initialize_vendor_dir(); self.binary_installer.remove_binaries(initial.clone()); let _ = self.update_code(initial.clone(), target.clone()).await?; @@ -393,9 +386,8 @@ impl InstallerInterface for LibraryInstaller { Ok(None) } - fn get_install_path(&self, package: PackageInterfaceHandle) -> Option<String> { - // TODO(phase-b): initialize_vendor_dir requires &mut self - // self.initialize_vendor_dir(); + fn get_install_path(&mut self, package: PackageInterfaceHandle) -> Option<String> { + self.initialize_vendor_dir(); let base_path = format!( "{}{}", diff --git a/crates/shirabe/src/installer/metapackage_installer.rs b/crates/shirabe/src/installer/metapackage_installer.rs index 0c67e9a..cebec2c 100644 --- a/crates/shirabe/src/installer/metapackage_installer.rs +++ b/crates/shirabe/src/installer/metapackage_installer.rs @@ -30,7 +30,7 @@ impl InstallerInterface for MetapackageInstaller { } fn is_installed( - &self, + &mut self, repo: &dyn InstalledRepositoryInterface, package: PackageInterfaceHandle, ) -> bool { @@ -38,7 +38,7 @@ impl InstallerInterface for MetapackageInstaller { } async fn download( - &self, + &mut self, _package: PackageInterfaceHandle, _prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { @@ -46,7 +46,7 @@ impl InstallerInterface for MetapackageInstaller { } async fn prepare( - &self, + &mut self, _type: &str, _package: PackageInterfaceHandle, _prev_package: Option<PackageInterfaceHandle>, @@ -55,7 +55,7 @@ impl InstallerInterface for MetapackageInstaller { } async fn cleanup( - &self, + &mut self, _type: &str, _package: PackageInterfaceHandle, _prev_package: Option<PackageInterfaceHandle>, @@ -132,7 +132,7 @@ impl InstallerInterface for MetapackageInstaller { Ok(None) } - fn get_install_path(&self, _package: PackageInterfaceHandle) -> Option<String> { + fn get_install_path(&mut self, _package: PackageInterfaceHandle) -> Option<String> { None } } diff --git a/crates/shirabe/src/installer/noop_installer.rs b/crates/shirabe/src/installer/noop_installer.rs index 3041e14..9b8aa08 100644 --- a/crates/shirabe/src/installer/noop_installer.rs +++ b/crates/shirabe/src/installer/noop_installer.rs @@ -15,7 +15,7 @@ impl InstallerInterface for NoopInstaller { } fn is_installed( - &self, + &mut self, repo: &dyn InstalledRepositoryInterface, package: PackageInterfaceHandle, ) -> bool { @@ -23,7 +23,7 @@ impl InstallerInterface for NoopInstaller { } async fn download( - &self, + &mut self, _package: PackageInterfaceHandle, _prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { @@ -31,7 +31,7 @@ impl InstallerInterface for NoopInstaller { } async fn prepare( - &self, + &mut self, _type: &str, _package: PackageInterfaceHandle, _prev_package: Option<PackageInterfaceHandle>, @@ -40,7 +40,7 @@ impl InstallerInterface for NoopInstaller { } async fn cleanup( - &self, + &mut self, _type: &str, _package: PackageInterfaceHandle, _prev_package: Option<PackageInterfaceHandle>, @@ -99,7 +99,7 @@ impl InstallerInterface for NoopInstaller { Ok(None) } - fn get_install_path(&self, package: PackageInterfaceHandle) -> Option<String> { + fn get_install_path(&mut self, package: PackageInterfaceHandle) -> Option<String> { let target_dir = package.get_target_dir(); let pretty_name = package.get_pretty_name(); diff --git a/crates/shirabe/src/installer/plugin_installer.rs b/crates/shirabe/src/installer/plugin_installer.rs index 00b2210..23d7098 100644 --- a/crates/shirabe/src/installer/plugin_installer.rs +++ b/crates/shirabe/src/installer/plugin_installer.rs @@ -70,7 +70,7 @@ impl InstallerInterface for PluginInstaller { } fn is_installed( - &self, + &mut self, repo: &dyn InstalledRepositoryInterface, package: PackageInterfaceHandle, ) -> bool { @@ -78,7 +78,7 @@ impl InstallerInterface for PluginInstaller { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, @@ -103,7 +103,7 @@ impl InstallerInterface for PluginInstaller { } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { @@ -166,7 +166,7 @@ impl InstallerInterface for PluginInstaller { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, @@ -174,7 +174,7 @@ impl InstallerInterface for PluginInstaller { self.inner.cleanup(r#type, package, prev_package).await } - fn get_install_path(&self, package: PackageInterfaceHandle) -> Option<String> { + fn get_install_path(&mut self, package: PackageInterfaceHandle) -> Option<String> { self.inner.get_install_path(package) } diff --git a/crates/shirabe/src/installer/project_installer.rs b/crates/shirabe/src/installer/project_installer.rs index 6c8a279..351fd35 100644 --- a/crates/shirabe/src/installer/project_installer.rs +++ b/crates/shirabe/src/installer/project_installer.rs @@ -36,7 +36,7 @@ impl InstallerInterface for ProjectInstaller { } fn is_installed( - &self, + &mut self, _repo: &dyn InstalledRepositoryInterface, _package: PackageInterfaceHandle, ) -> bool { @@ -44,7 +44,7 @@ impl InstallerInterface for ProjectInstaller { } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { @@ -69,7 +69,7 @@ impl InstallerInterface for ProjectInstaller { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, @@ -81,7 +81,7 @@ impl InstallerInterface for ProjectInstaller { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, prev_package: Option<PackageInterfaceHandle>, @@ -128,7 +128,7 @@ impl InstallerInterface for ProjectInstaller { .into()) } - fn get_install_path(&self, _package: PackageInterfaceHandle) -> Option<String> { + fn get_install_path(&mut self, _package: PackageInterfaceHandle) -> Option<String> { Some(self.install_path.clone()) } } diff --git a/crates/shirabe/src/plugin/plugin_manager.rs b/crates/shirabe/src/plugin/plugin_manager.rs index d4c3113..9dcf754 100644 --- a/crates/shirabe/src/plugin/plugin_manager.rs +++ b/crates/shirabe/src/plugin/plugin_manager.rs @@ -126,19 +126,17 @@ impl PluginManager { pub fn load_installed_plugins(&mut self) -> anyhow::Result<()> { // TODO(plugin): plugin loading is part of the plugin API if !self.are_plugins_disabled("local") { - // 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 = self .composer_full() .borrow() .get_repository_manager() .borrow() .get_local_repository(); - let root_package = crate::package::RootPackageInterfaceHandle::dup( - self.composer_full().borrow().get_package(), - ); - self.load_repository(&mut *repo.borrow_mut(), false, Some(root_package))?; + self.load_repository( + &mut *repo.borrow_mut(), + false, + Some(self.composer_full().borrow().get_package().clone()), + )?; } if self.global_composer.is_some() && !self.are_plugins_disabled("global") { diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index f194e69..a35874d 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -1641,14 +1641,14 @@ impl PlatformRepository { name: self.overrides["php"].name.clone(), version: self.overrides["php"].version.clone(), }; - let mut overrider = + let overrider = self.add_overridden_package(&php_override, Some(package.get_pretty_name()))?; let actual_text = if package.get_version() == overrider.get_version() { "same as actual".to_string() } else { format!("actual: {}", package.get_pretty_version()) }; - let current_description = overrider.get_description().unwrap_or("").to_string(); + let current_description = overrider.get_description().unwrap_or_default(); overrider.set_description(format!("{}, {}", current_description, actual_text)); return Ok(()); @@ -1662,7 +1662,7 @@ impl PlatformRepository { &mut self, r#override: &PlatformOverride, name: Option<String>, - ) -> anyhow::Result<CompletePackage> { + ) -> anyhow::Result<CompletePackageHandle> { let version_str = match &r#override.version { PhpMixed::String(s) => s.clone(), _ => "".to_string(), @@ -1681,14 +1681,11 @@ impl PlatformRepository { let mut extra: IndexMap<String, PhpMixed> = IndexMap::new(); extra.insert("config.platform".to_string(), PhpMixed::Bool(true)); package.inner.set_extra(extra); - // TODO(phase-b): CompletePackage is a PHP class (shared by ref); cannot Clone. - // The container should likely store Rc<RefCell<CompletePackage>> so both the inner - // ArrayRepository and the function caller can share ownership. - let _: () = todo!("share CompletePackage via Rc between add_package and return"); + let package = CompletePackageHandle::from_complete_package(package); + self.add_package(package.clone().into())?; - #[allow(unreachable_code)] if package.get_name() == "php" { - let parts = explode(".", package.get_version()); + let parts = explode(".", &package.get_version()); let head = array_slice_strs(&parts, 0, Some(3)); *LAST_SEEN_PLATFORM_PHP.lock().unwrap() = Some(implode(".", &head)); } diff --git a/crates/shirabe/src/repository/repository_set.rs b/crates/shirabe/src/repository/repository_set.rs index 3cc4b9f..367a46a 100644 --- a/crates/shirabe/src/repository/repository_set.rs +++ b/crates/shirabe/src/repository/repository_set.rs @@ -459,7 +459,7 @@ impl RepositorySet { /// @param list<string>|null $allowedTypes Only packages of those types are allowed if set to non-null pub fn create_pool( &mut self, - request: Request, + mut request: Request, io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, event_dispatcher: Option<std::rc::Rc<std::cell::RefCell<EventDispatcher>>>, pool_optimizer: Option<PoolOptimizer>, @@ -467,17 +467,34 @@ impl RepositorySet { allowed_types: Option<Vec<String>>, security_advisory_pool_filter: Option<SecurityAdvisoryPoolFilter>, ) -> Result<Pool> { + let root_aliases = self + .root_aliases + .iter() + .map(|(name, versions)| { + let versions = versions + .iter() + .map(|(version, entry)| { + let mut fields = IndexMap::new(); + fields.insert("alias".to_string(), entry.alias.clone()); + fields.insert( + "alias_normalized".to_string(), + entry.alias_normalized.clone(), + ); + (version.clone(), fields) + }) + .collect(); + (name.clone(), versions) + }) + .collect(); let mut pool_builder = PoolBuilder::new( self.acceptable_stabilities.clone(), self.stability_flags.clone(), - // TODO(phase-b): clone root_aliases into PoolBuilder's expected type - todo!("self.root_aliases.clone()"), + root_aliases, self.root_references.clone(), io, event_dispatcher, pool_optimizer, - // TODO(phase-b): clone temporary_constraints - todo!("self.temporary_constraints.clone()"), + self.temporary_constraints.clone(), security_advisory_pool_filter, ); pool_builder.set_ignored_types(ignored_types); @@ -501,11 +518,7 @@ impl RepositorySet { self.locked = true; - // TODO(phase-b): pool_builder.build_pool takes owned Vec and &mut Request; revisit sharing model - pool_builder.build_pool( - todo!("share self.repositories"), - todo!("share request as &mut"), - ) + pool_builder.build_pool(self.repositories.clone(), &mut request) } /// Create a pool for dependency resolution from the packages in this repository set. diff --git a/crates/shirabe/src/repository/vcs/svn_driver.rs b/crates/shirabe/src/repository/vcs/svn_driver.rs index 3d344ec..9b2ea47 100644 --- a/crates/shirabe/src/repository/vcs/svn_driver.rs +++ b/crates/shirabe/src/repository/vcs/svn_driver.rs @@ -85,8 +85,7 @@ impl SvnDriver { } self.inner.cache = Some(Cache::new( - // TODO(phase-b): pass io by reference/clone - todo!("self.inner.io clone"), + self.inner.io.clone(), &format!( "{}/{}", self.inner @@ -551,10 +550,9 @@ impl SvnDriver { if self.util.is_none() { self.util = Some(SvnUtil::new( self.base_url.clone(), - // TODO(phase-b): clone or borrow io/config - todo!("self.inner.io clone"), - todo!("self.inner.config clone"), - Some(todo!("self.inner.process clone")), + self.inner.io.clone(), + self.inner.config.clone(), + Some(self.inner.process.clone()), )); self.util .as_mut() diff --git a/crates/shirabe/src/repository/vcs_repository.rs b/crates/shirabe/src/repository/vcs_repository.rs index e90ac62..4c2492a 100644 --- a/crates/shirabe/src/repository/vcs_repository.rs +++ b/crates/shirabe/src/repository/vcs_repository.rs @@ -70,9 +70,7 @@ pub struct VcsRepository { /// @var list<string> empty_references: Vec<String>, /// @var array<'tags'|'branches', array<string, TransportException>> - // TODO(phase-b): TransportException is a PHP class; uses Rc<T> for shared ownership. - version_transport_exceptions: - IndexMap<String, IndexMap<String, std::rc::Rc<TransportException>>>, + version_transport_exceptions: IndexMap<String, IndexMap<String, TransportException>>, /// @var ?EventDispatcher (preserved for plugin events) _dispatcher: Option<std::rc::Rc<std::cell::RefCell<EventDispatcher>>>, } @@ -277,7 +275,7 @@ impl VcsRepository { /// @return array<'tags'|'branches', array<string, TransportException>> pub fn get_version_transport_exceptions( &self, - ) -> &IndexMap<String, IndexMap<String, std::rc::Rc<TransportException>>> { + ) -> &IndexMap<String, IndexMap<String, TransportException>> { &self.version_transport_exceptions } @@ -543,15 +541,10 @@ impl VcsRepository { })(); if let Err(e) = result { if let Some(te) = e.downcast_ref::<TransportException>() { - // TODO(phase-b): TransportException is a PHP class (shared by ref). We only - // have &TransportException from downcast_ref; obtaining the Rc requires the - // anyhow::Error chain to carry an Rc. For now we insert a todo!() placeholder. - let shared_te: std::rc::Rc<TransportException> = - todo!("share TransportException via Rc through anyhow::Error chain"); self.version_transport_exceptions .entry("tags".to_string()) .or_insert_with(IndexMap::new) - .insert(tag.clone(), shared_te); + .insert(tag.clone(), te.clone()); if te.get_code() == 404 { self.empty_references.push(identifier.clone()); } @@ -728,14 +721,10 @@ impl VcsRepository { })(); if let Err(e) = result { if let Some(te) = e.downcast_ref::<TransportException>() { - // TODO(phase-b): TransportException is a PHP class (shared by ref). - // See the matching tags block above; same Rc story applies. - let shared_te: std::rc::Rc<TransportException> = - todo!("share TransportException via Rc through anyhow::Error chain"); self.version_transport_exceptions .entry("branches".to_string()) .or_insert_with(IndexMap::new) - .insert(branch.clone(), shared_te); + .insert(branch.clone(), te.clone()); if te.get_code() == 404 { self.empty_references.push(identifier.clone()); } diff --git a/crates/shirabe/src/util/auth_helper.rs b/crates/shirabe/src/util/auth_helper.rs index 61970f2..2709751 100644 --- a/crates/shirabe/src/util/auth_helper.rs +++ b/crates/shirabe/src/util/auth_helper.rs @@ -147,13 +147,7 @@ impl AuthHelper { .any(|v| v.as_string() == Some(origin)); if in_github_domains { - let mut git_hub_util = GitHub::new( - // TODO(phase-b): clone or borrow io/config rather than moving - todo!("io clone"), - todo!("config clone"), - None, - None, - )?; + let mut git_hub_util = GitHub::new(self.io.clone(), self.config.clone(), None, None)?; let mut message = "\n".to_string(); let rate_limited = git_hub_util.is_rate_limited(&headers); @@ -258,13 +252,7 @@ impl AuthHelper { "to go over the API rate limit" }, ); - let mut git_lab_util = GitLab::new( - // TODO(phase-b): clone or borrow io/config rather than moving - todo!("io clone"), - todo!("config clone"), - None, - None, - )?; + let mut git_lab_util = GitLab::new(self.io.clone(), self.config.clone(), None, None)?; let mut auth: Option<IndexMap<String, Option<String>>> = None; if self.io.has_authentication(origin) { @@ -330,14 +318,8 @@ impl AuthHelper { .and_then(|v| v.clone()) .unwrap_or_default(); if username != "x-token-auth" { - let mut bitbucket_util = Bitbucket::new( - // TODO(phase-b): clone or borrow io/config rather than moving - todo!("io clone"), - todo!("config clone"), - None, - None, - None, - )?; + let mut bitbucket_util = + Bitbucket::new(self.io.clone(), self.config.clone(), None, None, None)?; let password = auth .get("password") .and_then(|v| v.clone()) @@ -377,14 +359,8 @@ impl AuthHelper { "go over the API rate limit" }, ); - let mut bit_bucket_util = Bitbucket::new( - // TODO(phase-b): clone or borrow io/config rather than moving - todo!("io clone"), - todo!("config clone"), - None, - None, - None, - )?; + let mut bit_bucket_util = + Bitbucket::new(self.io.clone(), self.config.clone(), None, None, None)?; if !bit_bucket_util.authorize_oauth(&origin) && (!self.io.is_interactive() || !bit_bucket_util @@ -506,7 +482,7 @@ impl AuthHelper { } // PHP: $headers = &$options['http']['header']; - // TODO(phase-b): captured by reference; pushes below modify the same list + // The reference is emulated by writing `headers` back into options below. let mut headers: Vec<PhpMixed> = match options .get("http") .and_then(|v| v.as_array()) diff --git a/crates/shirabe/src/util/forgejo.rs b/crates/shirabe/src/util/forgejo.rs index 232c8f3..974fb4a 100644 --- a/crates/shirabe/src/util/forgejo.rs +++ b/crates/shirabe/src/util/forgejo.rs @@ -143,8 +143,6 @@ impl Forgejo { } // store value in local/user config - // TODO(phase-b): Config getters return references; cross-borrows of self.config.borrow() - // cannot live across method calls. Needs Rc<RefCell<dyn ConfigSourceInterface>> shape. let setting_key = format!("forgejo-token.{}", origin_url); { let mut cfg = self.config.borrow_mut(); diff --git a/crates/shirabe/src/util/github.rs b/crates/shirabe/src/util/github.rs index 18958ca..26544cb 100644 --- a/crates/shirabe/src/util/github.rs +++ b/crates/shirabe/src/util/github.rs @@ -261,8 +261,6 @@ impl GitHub { } } - // TODO(phase-b): Config getters return references; cross-borrow chains require - // Rc<RefCell<dyn ConfigSourceInterface>> shape. For now use _mut variants. let use_local = store_in_local_auth_config && self .config diff --git a/crates/shirabe/src/util/http/curl_downloader.rs b/crates/shirabe/src/util/http/curl_downloader.rs index 9d572fa..c9eb2f8 100644 --- a/crates/shirabe/src/util/http/curl_downloader.rs +++ b/crates/shirabe/src/util/http/curl_downloader.rs @@ -197,9 +197,7 @@ impl CurlDownloader { share_handle = Some(sh); } - // TODO(phase-b): clone io/config for AuthHelper construction without consuming. - let auth_helper = - AuthHelper::new(unsafe { std::mem::zeroed() }, unsafe { std::mem::zeroed() }); + let auth_helper = AuthHelper::new(io.clone(), config.clone()); let mut multi_errors: IndexMap<i64, Vec<String>> = IndexMap::new(); multi_errors.insert( |
