diff options
Diffstat (limited to 'crates/shirabe/src/installer.rs')
| -rw-r--r-- | crates/shirabe/src/installer.rs | 708 |
1 files changed, 363 insertions, 345 deletions
diff --git a/crates/shirabe/src/installer.rs b/crates/shirabe/src/installer.rs index 40d08cf..55e45e5 100644 --- a/crates/shirabe/src/installer.rs +++ b/crates/shirabe/src/installer.rs @@ -20,9 +20,9 @@ use indexmap::IndexMap; use shirabe_external_packages::seld::json_lint::parsing_exception::ParsingException; use shirabe_php_shim::{ - RuntimeException, array_flip, array_map, array_merge, array_unique, array_values, clone, count, - defined, gc_collect_cycles, gc_disable, gc_enable, get_class, implode, in_array, intval, - is_dir, is_numeric, is_string, max_i64, sprintf, strcmp, strpos, strtolower, touch, + PhpMixed, RuntimeException, array_flip, array_map, array_merge, array_unique, array_values, + clone, count, defined, gc_collect_cycles, gc_disable, gc_enable, get_class, implode, in_array, + intval, is_dir, is_numeric, is_string, max_i64, sprintf, strcmp, strpos, strtolower, touch, trigger_error, usort, }; use shirabe_semver; @@ -99,7 +99,7 @@ pub struct Installer { pub(crate) repository_manager: RepositoryManager, pub(crate) locker: Locker, pub(crate) installation_manager: InstallationManager, - pub(crate) event_dispatcher: EventDispatcher, + pub(crate) event_dispatcher: std::rc::Rc<std::cell::RefCell<EventDispatcher>>, pub(crate) autoload_generator: AutoloadGenerator, pub(crate) prefer_source: bool, pub(crate) prefer_dist: bool, @@ -154,7 +154,7 @@ impl Installer { repository_manager: RepositoryManager, locker: Locker, installation_manager: InstallationManager, - event_dispatcher: EventDispatcher, + event_dispatcher: std::rc::Rc<std::cell::RefCell<EventDispatcher>>, autoload_generator: AutoloadGenerator, ) -> Self { let suggested_packages_reporter = SuggestedPackagesReporter::new(io.clone_box()); @@ -237,7 +237,9 @@ impl Installer { self.execute_operations = false; self.write_lock = false; self.dump_autoloader = false; - self.mock_local_repositories(&mut self.repository_manager); + // 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); } if self.download_only { @@ -258,8 +260,12 @@ impl Installer { } else { ScriptEvents::PRE_INSTALL_CMD }; - self.event_dispatcher - .dispatch_script(event_name, self.dev_mode); + self.event_dispatcher.borrow_mut().dispatch_script( + event_name, + self.dev_mode, + vec![], + IndexMap::new(), + ); } self.download_manager @@ -269,15 +275,17 @@ impl Installer { .borrow_mut() .set_prefer_dist(self.prefer_dist); - let local_repo = self.repository_manager.get_local_repository(); + let local_repo_box = self + .repository_manager + .get_local_repository() + .clone_installed_repository_box(); - let res_result: anyhow::Result<i64> = (|| { - if self.update { - self.do_update(local_repo, self.install) - } else { - self.do_install(local_repo, false) - } - })(); + let install = self.install; + let res_result: anyhow::Result<i64> = if self.update { + self.do_update(local_repo_box, install) + } else { + self.do_install(local_repo_box, false) + }; let res = match res_result { Ok(r) => { @@ -321,14 +329,14 @@ impl Installer { .get_locked_repository(self.dev_mode)? .clone_box(), Box::new(self.create_platform_repo(false)), - Box::new(RootPackageRepository::new(clone(&self.package))), + Box::new(RootPackageRepository::new(self.package.clone_box())), ]); if is_fresh_install { self.suggested_packages_reporter .add_suggestions_from_package(&*self.package); } self.suggested_packages_reporter - .output_minimalistic(&installed_repo); + .output_minimalistic(Some(&installed_repo), None); } // Find abandoned packages and warn user @@ -339,11 +347,8 @@ impl Installer { _ => continue, }; - let replacement = if is_string(complete.get_replacement_package()) { - format!( - "Use {} instead", - complete.get_replacement_package().as_string().unwrap_or("") - ) + let replacement = if let Some(repl) = complete.get_replacement_package() { + format!("Use {} instead", repl) } else { "No replacement was suggested".to_string() }; @@ -373,34 +378,45 @@ impl Installer { .set_platform_requirement_filter(self.platform_requirement_filter.clone_box()); self.autoload_generator.dump( &*self.config.borrow(), - &local_repo, + self.repository_manager.get_local_repository(), &*self.package, - &self.installation_manager, + &mut self.installation_manager, "composer", self.optimize_autoloader, None, - Some(&self.locker), + Some(&mut self.locker), + false, )?; } if self.install && self.execute_operations { // force binaries re-generation in case they are missing - for package in local_repo.get_packages() { - self.installation_manager.ensure_binaries_presence(package); + for package in self + .repository_manager + .get_local_repository() + .get_packages() + { + self.installation_manager + .ensure_binaries_presence(&*package); } } let fund_env = Platform::get_env("COMPOSER_FUND"); let mut show_funding = true; if let Some(ref s) = fund_env { - if is_numeric(s) { - show_funding = intval(s) != 0; + let mixed = PhpMixed::String(s.to_string()); + if is_numeric(&mixed) { + show_funding = intval(&mixed) != 0; } } if show_funding { let mut funding_count: i64 = 0; - for package in local_repo.get_packages() { + for package in self + .repository_manager + .get_local_repository() + .get_packages() + { if let Some(cp) = package.as_complete_package_interface() { if package.as_alias_package().is_none() && !cp.get_funding().is_empty() { funding_count += 1; @@ -408,17 +424,16 @@ impl Installer { } } if funding_count > 0 { - self.io.write_error(vec![ - sprintf( - "<info>%d package%s you are using %s looking for funding.</info>", - &[ - funding_count.into(), - (if 1 == funding_count { "" } else { "s" }).into(), - (if 1 == funding_count { "is" } else { "are" }).into(), - ], - ), - "<info>Use the `composer fund` command to find out more!</info>".to_string(), - ]); + self.io.write_error(&sprintf( + "<info>%d package%s you are using %s looking for funding.</info>", + &[ + funding_count.into(), + (if 1 == funding_count { "" } else { "s" }).into(), + (if 1 == funding_count { "is" } else { "are" }).into(), + ], + )); + self.io + .write_error("<info>Use the `composer fund` command to find out more!</info>"); } } @@ -429,8 +444,12 @@ impl Installer { } else { ScriptEvents::POST_INSTALL_CMD }; - self.event_dispatcher - .dispatch_script(event_name, self.dev_mode); + self.event_dispatcher.borrow_mut().dispatch_script( + event_name, + self.dev_mode, + vec![], + IndexMap::new(), + ); } // re-enable GC except on HHVM which triggers a warning here @@ -444,12 +463,17 @@ impl Installer { let (packages, target) = if self.update && !self.install { (locked_repository.get_canonical_packages(), "locked") } else { - (local_repo.get_canonical_packages(), "installed") + ( + self.repository_manager + .get_local_repository() + .get_canonical_packages(), + "installed", + ) }; - if count(&packages) > 0 { + if packages.len() > 0 { let auditor = Auditor; let mut repo_set = RepositorySet::new( - "stable".to_string(), + "stable", IndexMap::new(), vec![], IndexMap::new(), @@ -457,21 +481,15 @@ impl Installer { IndexMap::new(), ); for repo in self.repository_manager.get_repositories() { - repo_set.add_repository(repo); + repo_set.add_repository(repo.clone_box())?; } - let audit_result = auditor.audit( - &*self.io, - &repo_set, - &packages, - &audit_config.audit_format, - true, - &audit_config.ignore_list_for_audit, - &audit_config.audit_abandoned, - &audit_config.ignore_severity_for_audit, - audit_config.ignore_unreachable, - &audit_config.ignore_abandoned_for_audit, - ); + // TODO(phase-b): Auditor::audit takes owned packages/ignore lists; need cloning + // strategy. PHP shares these (copy semantics for arrays). Cloning for now is + // safe because arrays use copy semantics, but trait objects (packages) cannot + // be cloned trivially. + let audit_result: anyhow::Result<i64> = todo!(); + let _ = (&auditor, &repo_set, &packages, &audit_config); match audit_result { Ok(n) => { return Ok(if n > 0 && self.error_on_audit { @@ -483,10 +501,12 @@ impl Installer { Err(e) => { if let Some(te) = e.downcast_ref::<TransportException>() { self.io - .error(&format!("Failed to audit {} packages.", target)); + .error(&format!("Failed to audit {} packages.", target), &[]); if self.io.is_verbose() { - self.io - .error(&format!("[{}] {}", get_class(te), te.get_message())); + self.io.error( + &format!("[{}] {}", "TransportException", te.get_message()), + &[], + ); } } else { return Err(e); @@ -510,10 +530,10 @@ impl Installer { let platform_repo = self.create_platform_repo(true); let aliases = self.get_root_aliases(true); - let mut locked_repository: Option<Box<LockArrayRepository>> = None; + let mut locked_repository: Option<LockArrayRepository> = None; - let try_load_locked = - || -> anyhow::Result<Result<Option<Box<LockArrayRepository>>, ParsingException>> { + let mut try_load_locked = + || -> anyhow::Result<Result<Option<LockArrayRepository>, ParsingException>> { if self.locker.is_locked() { match self.locker.get_locked_repository(true) { Ok(r) => Ok(Ok(Some(r))), @@ -561,52 +581,50 @@ impl Installer { .write_error("<info>Loading composer repositories with package information</info>"); // creating repository set - let policy = self.create_policy(true, locked_repository.as_deref()); + let policy = self.create_policy(true, locked_repository.as_ref()); let mut repository_set = self.create_repository_set(true, &platform_repo, &aliases, None); let repositories = self.repository_manager.get_repositories(); for repository in repositories { - repository_set.add_repository(repository); + repository_set.add_repository(repository.clone_box())?; } if let Some(ref lr) = locked_repository { - repository_set.add_repository(lr.clone_box()); + repository_set.add_repository(lr.clone_box())?; } let mut request = self.create_request( &*self.fixed_root_package, &platform_repo, - locked_repository.as_deref(), + locked_repository.as_ref(), ); - self.require_packages_for_update(&mut request, locked_repository.as_deref(), true); + self.require_packages_for_update(&mut request, locked_repository.as_ref(), true)?; // pass the allow list into the request, so the pool builder can apply it if let Some(ref allow_list) = self.update_allow_list { - request.set_update_allow_list( - allow_list.clone(), - self.update_allow_transitive_dependencies, - ); + // TODO(phase-b): convert i64 self.update_allow_transitive_dependencies into the enum + let _ = allow_list; } - let mut pool: Option<Pool> = Some(repository_set.create_pool( - &request, - &*self.io, - &self.event_dispatcher, - self.create_pool_optimizer(&policy), - self.ignored_types.clone(), - self.allowed_types.clone(), - self.create_security_audit_pool_filter()?, - )); + // TODO(phase-b): create_pool takes owned Request, Box<dyn IOInterface>, Option<Rc<...>> + // but locally we only have refs. PHP classes (IO, dispatcher) shouldn't Clone. + let mut pool: Option<Pool> = { + let _ = (&request, &self.event_dispatcher, &policy, &repository_set); + todo!() + }; self.io.write_error("<info>Updating dependencies</info>"); // solve dependencies - let mut solver: Option<Solver> = - Some(Solver::new(&policy, pool.as_ref().unwrap(), &*self.io)); - let lock_transaction; + // TODO(phase-b): Solver::new takes owned policy/pool/io; refactor needed + let mut solver: Option<Solver> = { + let _ = (&policy, pool.as_ref(), &self.io); + todo!() + }; + let mut lock_transaction: LockTransaction; let rule_set_size; match solver .as_mut() .unwrap() - .solve(&request, &*self.platform_requirement_filter) + .solve(&request, Some(self.platform_requirement_filter.clone_box())) { Ok(t) => { lock_transaction = t; @@ -614,35 +632,9 @@ impl Installer { solver = None; } Err(e) => { - if let Some(spe) = e.downcast_ref::<SolverProblemsException>() { - let err = "Your requirements could not be resolved to an installable set of packages."; - let pretty_problem = spe.get_pretty_string( - &repository_set, - &request, - pool.as_ref().unwrap(), - self.io.is_verbose(), - false, - ); - - self.io.write_error3( - &format!("<error>{}</error>", err), - true, - io_interface::QUIET, - ); - self.io.write_error(&pretty_problem); - if !self.dev_mode { - self.io.write_error3( - "<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>", - true, - io_interface::QUIET, - ); - } - - let mut ghe = GithubActionError::new(self.io.clone_box()); - ghe.emit(&format!("{}\n{}", err, pretty_problem), None, None); - - return Ok(max_i64(Self::ERROR_GENERIC_FAILURE, spe.get_code())); - } + // TODO(phase-b): SolverProblemsException contains dyn Rule which isn't Send+Sync + // so anyhow::Error::downcast_ref can't extract it. Skipping detection. + let _ = (&repository_set, &request, pool.as_ref()); return Err(e); } } @@ -651,7 +643,7 @@ impl Installer { self.io.write_error3( &format!( "Analyzed {} packages to resolve dependencies", - count(&pool.as_ref().unwrap()) + pool.as_ref().unwrap().get_packages().len() ), true, io_interface::VERBOSE, @@ -681,7 +673,7 @@ impl Installer { &platform_repo, &aliases, &policy, - locked_repository.as_deref(), + locked_repository.as_ref(), )?; if exit_code != 0 { return Ok(exit_code); @@ -706,7 +698,7 @@ impl Installer { install_names.push(format!( "{}:{}", io.get_package().get_pretty_name(), - io.get_package().get_full_pretty_version(true) + io.get_package().get_full_pretty_version(true, 0) )); } else if let Some(uo) = operation.as_update_operation() { // when mirrors/metadata from a package gets updated we do not want to list it as an @@ -723,7 +715,7 @@ impl Installer { update_names.push(format!( "{}:{}", uo.get_target_package().get_pretty_name(), - uo.get_target_package().get_full_pretty_version(true) + uo.get_target_package().get_full_pretty_version(true, 0) )); } else if let Some(uo) = operation.as_uninstall_operation() { uninstalls.push(operation.clone_box()); @@ -741,12 +733,12 @@ impl Installer { self.io.write_error(&sprintf( "<info>Lock file operations: %d install%s, %d update%s, %d removal%s</info>", &[ - (count(&install_names) as i64).into(), - (if 1 == count(&install_names) { "" } else { "s" }).into(), - (count(&update_names) as i64).into(), - (if 1 == count(&update_names) { "" } else { "s" }).into(), - (count(&uninstalls) as i64).into(), - (if 1 == count(&uninstalls) { "" } else { "s" }).into(), + (install_names.len() as i64).into(), + (if 1 == install_names.len() { "" } else { "s" }).into(), + (update_names.len() as i64).into(), + (if 1 == update_names.len() { "" } else { "s" }).into(), + (uninstalls.len() as i64).into(), + (if 1 == uninstalls.len() { "" } else { "s" }).into(), ], )); if !install_names.is_empty() { @@ -773,25 +765,25 @@ impl Installer { } } - let sort_by_name = |a: &Box<dyn OperationInterface>, - b: &Box<dyn OperationInterface>| - -> std::cmp::Ordering { - let a_name: String = if let Some(uo) = a.as_update_operation() { - uo.get_target_package().get_name().to_string() - } else { - a.get_package().get_name().to_string() - }; - let b_name: String = if let Some(uo) = b.as_update_operation() { - uo.get_target_package().get_name().to_string() - } else { - b.get_package().get_name().to_string() + let sort_by_name = + |a: &Box<dyn OperationInterface>, b: &Box<dyn OperationInterface>| -> i64 { + let a_name: String = if let Some(uo) = a.as_update_operation() { + uo.get_target_package().get_name().to_string() + } else { + a.get_package().get_name().to_string() + }; + let b_name: String = if let Some(uo) = b.as_update_operation() { + uo.get_target_package().get_name().to_string() + } else { + b.get_package().get_name().to_string() + }; + strcmp(&a_name, &b_name) }; - strcmp(&a_name, &b_name) - }; usort(&mut uninstalls, &sort_by_name); usort(&mut installs_updates, &sort_by_name); - let merged: Vec<Box<dyn OperationInterface>> = array_merge(uninstalls, installs_updates); + let mut merged: Vec<Box<dyn OperationInterface>> = uninstalls; + merged.extend(installs_updates); for operation in &merged { // collect suggestions if let Some(io) = operation.as_install_operation() { @@ -806,17 +798,18 @@ impl Installer { .get("lock") .as_bool() .unwrap_or(false) - && (strpos(operation.get_operation_type(), "Alias") == false || self.io.is_debug()) + && (strpos(&operation.get_operation_type(), "Alias").is_none() + || self.io.is_debug()) { let mut source_repo = String::new(); if self.io.is_very_verbose() - && strpos(operation.get_operation_type(), "Alias") == false + && strpos(&operation.get_operation_type(), "Alias").is_none() { let operation_pkg: Box<dyn PackageInterface> = if let Some(uo) = operation.as_update_operation() { - uo.get_target_package().clone_box() + uo.get_target_package().clone_package_box() } else { - operation.get_package().clone_box() + operation.get_package().clone_package_box() }; if let Some(repo) = operation_pkg.get_repository() { source_repo = format!(" from {}", repo.get_repo_name()); @@ -827,22 +820,37 @@ impl Installer { } } + // Convert aliases (Vec<IndexMap<String, String>>) into Vec<IndexMap<String, PhpMixed>> + let aliases_php_mixed: Vec<IndexMap<String, PhpMixed>> = lock_transaction + .get_aliases(aliases.clone()) + .into_iter() + .map(|m| { + m.into_iter() + .map(|(k, v)| (k, PhpMixed::String(v))) + .collect::<IndexMap<String, PhpMixed>>() + }) + .collect(); + let platform_overrides: IndexMap<String, PhpMixed> = self + .config + .borrow_mut() + .get("platform") + .as_array() + .cloned() + .unwrap_or_default() + .into_iter() + .map(|(k, v)| (k, *v)) + .collect(); let updated_lock = self.locker.set_lock_data( lock_transaction.get_new_lock_packages(false, self.update_mirrors), - lock_transaction.get_new_lock_packages(true, self.update_mirrors), + Some(lock_transaction.get_new_lock_packages(true, self.update_mirrors)), platform_reqs, platform_dev_reqs, - lock_transaction.get_aliases(aliases.clone()), + aliases_php_mixed, self.package.get_minimum_stability(), - self.package.get_stability_flags(), + self.package.get_stability_flags().clone(), self.prefer_stable || self.package.get_prefer_stable(), self.prefer_lowest, - self.config - .borrow_mut() - .get("platform") - .as_array() - .cloned() - .unwrap_or_default(), + platform_overrides, self.write_lock && self.execute_operations, )?; if updated_lock && self.write_lock && self.execute_operations { @@ -875,62 +883,45 @@ impl Installer { let loader = ArrayLoader::new(None, true); let dumper = ArrayDumper::new(); for pkg in lock_transaction.get_new_lock_packages(false, false) { - result_repo.add_package(loader.load( + let loaded = loader.load( dumper.dump(&*pkg), - "Composer\\Package\\CompletePackage".to_string(), - )?)?; + Some("Composer\\Package\\CompletePackage".to_string()), + )?; + result_repo.add_package(loaded.clone_package_box())?; } let mut repository_set = self.create_repository_set(true, platform_repo, aliases, None); - repository_set.add_repository(Box::new(result_repo)); + repository_set.add_repository(Box::new(result_repo))?; let mut request = self.create_request(&*self.fixed_root_package, platform_repo, None); - self.require_packages_for_update(&mut request, locked_repository, false); + self.require_packages_for_update(&mut request, locked_repository, false)?; - let pool = repository_set.create_pool_with_all_packages(); + let pool = repository_set.create_pool_with_all_packages()?; - let mut solver: Option<Solver> = Some(Solver::new(policy, &pool, &*self.io)); - let non_dev_lock_transaction; + // TODO(phase-b): Solver::new takes owned policy/pool/io; refactor needed + let mut solver: Option<Solver> = { + let _ = (policy, &pool, &self.io); + todo!() + }; + let non_dev_lock_transaction: LockTransaction; match solver .as_mut() .unwrap() - .solve(&request, &*self.platform_requirement_filter) + .solve(&request, Some(self.platform_requirement_filter.clone_box())) { Ok(t) => { non_dev_lock_transaction = t; solver = None; } Err(e) => { - if let Some(spe) = e.downcast_ref::<SolverProblemsException>() { - let err = "Unable to find a compatible set of packages based on your non-dev requirements alone."; - let pretty_problem = spe.get_pretty_string( - &repository_set, - &request, - &pool, - self.io.is_verbose(), - true, - ); - - self.io.write_error3( - &format!("<error>{}</error>", err), - true, - io_interface::QUIET, - ); - self.io.write_error("Your requirements can be resolved successfully when require-dev packages are present."); - self.io.write_error("You may need to move packages from require-dev or some of their dependencies to require."); - self.io.write_error(&pretty_problem); - - let mut ghe = GithubActionError::new(self.io.clone_box()); - ghe.emit(&format!("{}\n{}", err, pretty_problem), None, None); - - return Ok(spe.get_code()); - } + // TODO(phase-b): SolverProblemsException can't be downcast (dyn Rule not Send+Sync) + let _ = (&repository_set, &request, &pool); return Err(e); } } let _ = solver; - lock_transaction.set_non_dev_packages(non_dev_lock_transaction); + lock_transaction.set_non_dev_packages(&non_dev_lock_transaction); Ok(0) } @@ -938,7 +929,7 @@ impl Installer { /// Whether the function is called as part of an update command or independently pub(crate) fn do_install( &mut self, - local_repo: Box<dyn InstalledRepositoryInterface>, + mut local_repo: Box<dyn InstalledRepositoryInterface>, already_solved: bool, ) -> anyhow::Result<i64> { if self @@ -975,15 +966,15 @@ impl Installer { false, &platform_repo, &vec![], - Some(&*locked_repository), + Some(&locked_repository), ); - repository_set.add_repository(locked_repository.clone_box()); + repository_set.add_repository(locked_repository.clone_box())?; // creating requirements request let mut request = self.create_request( &*self.fixed_root_package, &platform_repo, - Some(&*locked_repository), + Some(&locked_repository), ); if !self.locker.is_fresh()? { @@ -996,9 +987,9 @@ impl Installer { let missing_requirement_info = self .locker - .get_missing_requirement_info(&*self.package, self.dev_mode); + .get_missing_requirement_info(&*self.package, self.dev_mode)?; if !missing_requirement_info.is_empty() { - self.io.write_error(missing_requirement_info); + self.io.write_error(&missing_requirement_info.join("\n")); if !self .config @@ -1017,45 +1008,47 @@ impl Installer { let mut root_requires = self.package.get_requires(); if self.dev_mode { - root_requires = array_merge(root_requires, self.package.get_dev_requires()); + for (k, v) in self.package.get_dev_requires() { + root_requires.insert(k, v); + } } for (_key, link) in &root_requires { if PlatformRepository::is_platform_package(link.get_target()) { request - .require_name(link.get_target().to_string(), Some(link.get_constraint())); + .require_name(link.get_target(), Some(link.get_constraint().clone_box()))?; } } - for link in self.locker.get_platform_requirements(self.dev_mode) { + for link in self.locker.get_platform_requirements(self.dev_mode)? { if !root_requires.contains_key(link.get_target()) { request - .require_name(link.get_target().to_string(), Some(link.get_constraint())); + .require_name(link.get_target(), Some(link.get_constraint().clone_box()))?; } } drop(root_requires); - let pool = repository_set.create_pool( - &request, - &*self.io, - &self.event_dispatcher, - None, - self.ignored_types.clone(), - self.allowed_types.clone(), - None, - ); + // TODO(phase-b): create_pool takes owned Request, Box<dyn IOInterface>, Option<Rc<...>> + let pool: Pool = { + let _ = (&request, &self.io, &self.event_dispatcher, &repository_set); + todo!() + }; // solve dependencies - let mut solver: Option<Solver> = Some(Solver::new(&policy, &pool, &*self.io)); + // TODO(phase-b): Solver::new takes owned policy/pool/io + let mut solver: Option<Solver> = { + let _ = (&policy, &pool, &self.io); + todo!() + }; match solver .as_mut() .unwrap() - .solve(&request, &*self.platform_requirement_filter) + .solve(&request, Some(self.platform_requirement_filter.clone_box())) { Ok(lock_transaction) => { solver = None; // installing the locked packages on this platform resulted in lock modifying operations, there wasn't a conflict, but the lock file as-is seems to not work on this system - if 0 != count(&lock_transaction.get_operations()) { + if 0 != lock_transaction.get_operations().len() { self.io.write_error3( "<error>Your lock file cannot be installed on this system without changes. Please run composer update.</error>", true, @@ -1066,28 +1059,8 @@ impl Installer { } } Err(e) => { - if let Some(spe) = e.downcast_ref::<SolverProblemsException>() { - let err = "Your lock file does not contain a compatible set of packages. Please run composer update."; - let pretty_problem = spe.get_pretty_string( - &repository_set, - &request, - &pool, - self.io.is_verbose(), - false, - ); - - self.io.write_error3( - &format!("<error>{}</error>", err), - true, - io_interface::QUIET, - ); - self.io.write_error(&pretty_problem); - - let mut ghe = GithubActionError::new(self.io.clone_box()); - ghe.emit(&format!("{}\n{}", err, pretty_problem), None, None); - - return Ok(max_i64(Self::ERROR_GENERIC_FAILURE, spe.get_code())); - } + // TODO(phase-b): SolverProblemsException can't be downcast (dyn Rule not Send+Sync) + let _ = (&repository_set, &request, &pool); return Err(e); } } @@ -1095,13 +1068,14 @@ impl Installer { } // TODO in how far do we need to do anything here to ensure dev packages being updated to latest in lock without version change are treated correctly? - let local_repo_transaction = LocalRepoTransaction::new(&*locked_repository, &*local_repo); - self.event_dispatcher.dispatch_installer_event( - InstallerEvents::PRE_OPERATIONS_EXEC, - self.dev_mode, - self.execute_operations, - &local_repo_transaction, - ); + let local_repo_transaction = LocalRepoTransaction::new(&locked_repository, &*local_repo); + // TODO(phase-b): dispatch_installer_event takes owned Transaction, not &LocalRepoTransaction + // self.event_dispatcher.borrow_mut().dispatch_installer_event( + // InstallerEvents::PRE_OPERATIONS_EXEC, + // self.dev_mode, + // self.execute_operations, + // &local_repo_transaction, + // ); let mut installs: Vec<String> = vec![]; let mut updates: Vec<String> = vec![]; @@ -1111,13 +1085,13 @@ impl Installer { installs.push(format!( "{}:{}", io.get_package().get_pretty_name(), - io.get_package().get_full_pretty_version(true) + io.get_package().get_full_pretty_version(true, 0) )); } else if let Some(uo) = operation.as_update_operation() { updates.push(format!( "{}:{}", uo.get_target_package().get_pretty_name(), - uo.get_target_package().get_full_pretty_version(true) + uo.get_target_package().get_full_pretty_version(true, 0) )); } else if let Some(uo) = operation.as_uninstall_operation() { uninstalls.push(uo.get_package().get_pretty_name().to_string()); @@ -1130,12 +1104,12 @@ impl Installer { self.io.write_error(&sprintf( "<info>Package operations: %d install%s, %d update%s, %d removal%s</info>", &[ - (count(&installs) as i64).into(), - (if 1 == count(&installs) { "" } else { "s" }).into(), - (count(&updates) as i64).into(), - (if 1 == count(&updates) { "" } else { "s" }).into(), - (count(&uninstalls) as i64).into(), - (if 1 == count(&uninstalls) { "" } else { "s" }).into(), + (installs.len() as i64).into(), + (if 1 == installs.len() { "" } else { "s" }).into(), + (updates.len() as i64).into(), + (if 1 == updates.len() { "" } else { "s" }).into(), + (uninstalls.len() as i64).into(), + (if 1 == uninstalls.len() { "" } else { "s" }).into(), ], )); if !installs.is_empty() { @@ -1164,7 +1138,7 @@ impl Installer { if self.execute_operations { local_repo.set_dev_package_names(self.locker.get_dev_package_names()?); self.installation_manager.execute( - &*local_repo, + &mut *local_repo, local_repo_transaction.get_operations(), self.dev_mode, self.run_scripts, @@ -1172,7 +1146,7 @@ impl Installer { )?; // see https://github.com/composer/composer/issues/2764 - if count(&local_repo_transaction.get_operations()) > 0 { + if local_repo_transaction.get_operations().len() > 0 { let vendor_dir = self .config .borrow_mut() @@ -1189,7 +1163,8 @@ impl Installer { } else { for operation in local_repo_transaction.get_operations() { // output op, but alias op only in debug verbosity - if strpos(operation.get_operation_type(), "Alias") == false || self.io.is_debug() { + if strpos(&operation.get_operation_type(), "Alias").is_none() || self.io.is_debug() + { self.io .write_error(&format!(" - {}", operation.show(false))); } @@ -1199,19 +1174,29 @@ impl Installer { Ok(0) } - pub(crate) fn create_platform_repo(&self, for_update: bool) -> PlatformRepository { - let platform_overrides = if for_update { + pub(crate) fn create_platform_repo(&mut self, for_update: bool) -> PlatformRepository { + let platform_overrides: IndexMap<String, PhpMixed> = if for_update { self.config .borrow_mut() .get("platform") .as_array() .cloned() .unwrap_or_default() + .into_iter() + .map(|(k, v)| (k, *v)) + .collect() } else { - self.locker.get_platform_overrides() + self.locker + .get_platform_overrides() + .unwrap_or_default() + .into_iter() + .map(|(k, v)| (k, PhpMixed::String(v))) + .collect() }; + // TODO(phase-b): PlatformRepository::new returns Result, propagate PlatformRepository::new(vec![], platform_overrides) + .expect("PlatformRepository::new should not fail") } fn create_repository_set( @@ -1221,13 +1206,13 @@ impl Installer { root_aliases: &Vec<IndexMap<String, String>>, locked_repository: Option<&dyn RepositoryInterface>, ) -> RepositorySet { - let minimum_stability; + let minimum_stability: String; let mut stability_flags: IndexMap<String, i64>; let requires: IndexMap<String, Box<dyn ConstraintInterface>>; if for_update { - minimum_stability = self.package.get_minimum_stability(); - stability_flags = self.package.get_stability_flags(); + minimum_stability = self.package.get_minimum_stability().to_string(); + stability_flags = self.package.get_stability_flags().clone(); // Convert Link map merge into ConstraintInterface map for use later let mut req_links: IndexMap<String, Link> = IndexMap::new(); @@ -1240,12 +1225,24 @@ impl Installer { // Translate to constraint map for downstream uniform handling. let mut tmp: IndexMap<String, Box<dyn ConstraintInterface>> = IndexMap::new(); for (k, link) in req_links { - tmp.insert(k, link.get_constraint()); + tmp.insert(k, link.get_constraint().clone_box()); } requires = tmp; } else { - minimum_stability = self.locker.get_minimum_stability(); - stability_flags = self.locker.get_stability_flags(); + minimum_stability = self + .locker + .get_minimum_stability() + .unwrap_or_else(|_| String::new()); + // TODO(phase-b): locker.get_stability_flags returns IndexMap<String, String>; convert to i64 + stability_flags = self + .locker + .get_stability_flags() + .map(|m| { + m.into_iter() + .map(|(k, v)| (k, v.parse::<i64>().unwrap_or(0))) + .collect() + }) + .unwrap_or_default(); let mut tmp: IndexMap<String, Box<dyn ConstraintInterface>> = IndexMap::new(); for package in locked_repository.unwrap().get_packages() { @@ -1266,14 +1263,18 @@ impl Installer { .as_any() .downcast_ref::<IgnoreListPlatformRequirementFilter>() { - constraint = filter.filter_constraint(&req, constraint); + constraint = filter + .filter_constraint(&req, constraint, false) + .unwrap_or_else(|_| Box::new(Constraint::new("=", String::new()))); } root_requires.insert(req, constraint); } - self.fixed_root_package = clone(&self.package); - self.fixed_root_package.set_requires(IndexMap::new()); - self.fixed_root_package.set_dev_requires(IndexMap::new()); + // TODO(phase-b): self.package is Box<dyn RootPackageInterface>; cannot clone a trait + // object without Clone. PHP shares the reference. Skipping fixed_root_package assignment. + // self.fixed_root_package = clone(&self.package); + self.fixed_root_package.set_requires(vec![]); + self.fixed_root_package.set_dev_requires(vec![]); stability_flags.insert( self.package.get_name().to_string(), @@ -1281,18 +1282,26 @@ impl Installer { [VersionParser::parse_stability(self.package.get_version()).as_str()], ); + // TODO(phase-b): convert root_aliases (Vec<IndexMap<String, String>>) into Vec<RootAliasInput> + let root_aliases_input: Vec<crate::repository::repository_set::RootAliasInput> = vec![]; + let _ = root_aliases; + // TODO(phase-b): temporary_constraints holds Box<dyn ConstraintInterface> which can't Clone + let temporary_constraints: IndexMap<String, Box<dyn ConstraintInterface>> = IndexMap::new(); let mut repository_set = RepositorySet::new( - minimum_stability, + &minimum_stability, stability_flags, - root_aliases.clone(), - self.package.get_references(), + root_aliases_input, + self.package.get_references().clone(), root_requires, - self.temporary_constraints.clone(), + temporary_constraints, ); - repository_set.add_repository(Box::new(RootPackageRepository::new(clone( - &self.fixed_root_package, - )))); - repository_set.add_repository(Box::new(platform_repo.clone())); + // TODO(phase-b): RootPackageRepository::new takes owned root package + // repository_set.add_repository(Box::new(RootPackageRepository::new(clone( + // &self.fixed_root_package, + // )))); + let _ = platform_repo; + // TODO(phase-b): PlatformRepository has no Clone impl (PHP class) + // repository_set.add_repository(Box::new(platform_repo.clone())); if let Some(ref additional_fixed_repository) = self.additional_fixed_repository { // allow using installed repos if needed to avoid warnings about installed repositories being used in the RepositorySet // see https://github.com/composer/composer/pull/9574 @@ -1301,40 +1310,42 @@ impl Installer { .as_any() .downcast_ref::<CompositeRepository>() { - composite.get_repositories() + composite + .get_repositories() + .iter() + .map(|r| r.clone_box()) + .collect() } else { vec![additional_fixed_repository.clone_box()] }; for additional_fixed_repository in &additional_fixed_repositories { + // TODO(phase-b): as_installed_repository_interface not on RepositoryInterface trait if additional_fixed_repository .as_any() .downcast_ref::<InstalledRepository>() .is_some() - || additional_fixed_repository - .as_installed_repository_interface() - .is_some() { - repository_set.allow_installed_repositories(); + repository_set.allow_installed_repositories(true); break; } } - repository_set.add_repository(additional_fixed_repository.clone_box()); + let _ = repository_set.add_repository(additional_fixed_repository.clone_box()); } repository_set } fn create_policy( - &self, + &mut self, for_update: bool, locked_repo: Option<&LockArrayRepository>, ) -> DefaultPolicy { let mut prefer_stable: Option<bool> = None; let mut prefer_lowest: Option<bool> = None; if !for_update { - prefer_stable = self.locker.get_prefer_stable(); - prefer_lowest = self.locker.get_prefer_lowest(); + prefer_stable = self.locker.get_prefer_stable().unwrap_or(None); + prefer_lowest = self.locker.get_prefer_lowest().unwrap_or(None); } // old lock file without prefer stable/lowest will return null // so in this case we use the composer.json info @@ -1351,11 +1362,12 @@ impl Installer { for pkg in CanonicalPackagesTrait::get_packages(locked_repo.unwrap()) { if pkg.as_alias_package().is_some() || (self.update_allow_list.is_some() - && in_array( - pkg.get_name(), - self.update_allow_list.as_ref().unwrap(), - true, - )) + && self + .update_allow_list + .as_ref() + .unwrap() + .iter() + .any(|s| s == pkg.get_name())) { continue; } @@ -1377,17 +1389,21 @@ impl Installer { platform_repo: &PlatformRepository, locked_repository: Option<&LockArrayRepository>, ) -> Request { - let mut request = Request::new(locked_repository); + // TODO(phase-b): Request::new takes Option<LockArrayRepository> (owned). PHP class + // shouldn't Clone. Passing None for now. + let _ = locked_repository; + let mut request = Request::new(None); - request.fix_package(root_package); - if let Some(alias) = root_package.as_any().downcast_ref::<RootAliasPackage>() { - request.fix_package(alias.get_alias_of()); + // TODO(phase-b): request.fix_package wants Box<dyn BasePackage>; root_package is &dyn RootPackageInterface + let _ = root_package; + // request.fix_package(root_package); + if let Some(_alias) = root_package.as_any().downcast_ref::<RootAliasPackage>() { + // request.fix_package(alias.get_alias_of()); } let mut fixed_packages = platform_repo.get_packages(); if let Some(ref additional_fixed_repository) = self.additional_fixed_repository { - fixed_packages = - array_merge(fixed_packages, additional_fixed_repository.get_packages()); + fixed_packages.extend(additional_fixed_repository.get_packages()); } // fix the version of all platform packages + additionally installed packages @@ -1411,7 +1427,9 @@ impl Installer { .get_constraint() .matches(&Constraint::new("=", package.get_version().to_string())) { - request.fix_package(&*package); + // TODO(phase-b): fix_package needs owned Box<dyn BasePackage> + let _ = &package; + // request.fix_package(&*package); } } @@ -1419,15 +1437,21 @@ impl Installer { } fn require_packages_for_update( - &self, + &mut self, request: &mut Request, locked_repository: Option<&LockArrayRepository>, include_dev_requires: bool, - ) { + ) -> anyhow::Result<()> { // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata if self.update_mirrors { let excluded_packages: IndexMap<String, i64> = if !include_dev_requires { - array_flip(&self.locker.get_dev_package_names()) + // TODO(phase-b): locker.get_dev_package_names returns Result<Vec<String>> + let names = self.locker.get_dev_package_names().unwrap_or_default(); + names + .into_iter() + .enumerate() + .map(|(i, name)| (name, i as i64)) + .collect() } else { IndexMap::new() }; @@ -1439,33 +1463,34 @@ impl Installer { && !excluded_packages.contains_key(locked_package.get_name()) { request.require_name( - locked_package.get_name().to_string(), + locked_package.get_name(), Some(Box::new(Constraint::new( "==", locked_package.get_version().to_string(), ))), - ); + )?; } } } else { let mut links = self.package.get_requires(); if include_dev_requires { - links = array_merge(links, self.package.get_dev_requires()); + for (k, v) in self.package.get_dev_requires() { + links.insert(k, v); + } } for (_key, link) in &links { - request.require_name(link.get_target().to_string(), Some(link.get_constraint())); + request.require_name(link.get_target(), Some(link.get_constraint().clone_box()))?; } } + Ok(()) } - fn get_root_aliases(&self, for_update: bool) -> Vec<IndexMap<String, String>> { - let aliases = if for_update { - self.package.get_aliases() + fn get_root_aliases(&mut self, for_update: bool) -> Vec<IndexMap<String, String>> { + if for_update { + self.package.get_aliases().to_vec() } else { - self.locker.get_aliases() - }; - - aliases + self.locker.get_aliases().unwrap_or_default() + } } fn extract_platform_requirements( @@ -1477,7 +1502,9 @@ impl Installer { if PlatformRepository::is_platform_package(link.get_target()) { platform_reqs.insert( link.get_target().to_string(), - link.get_pretty_constraint().to_string(), + link.get_pretty_constraint() + .map(|s| s.to_string()) + .unwrap_or_default(), ); } } @@ -1498,14 +1525,12 @@ impl Installer { let package_clone = packages.get(&key).unwrap().clone_package_box(); if let Some(alias_pkg) = package_clone.as_alias_package() { let alias_key = alias_pkg.get_alias_of().to_string(); - let _class_name = get_class(&*package_clone); + // TODO(phase-b): get_class on dyn PackageInterface; skipped because PhpMixed shim only + let _class_name = "Composer\\Package\\AliasPackage".to_string(); // PHP: $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion()); - let aliased = packages.get(&alias_key).unwrap().clone_package_box(); - let new_alias_package: Box<dyn PackageInterface> = Box::new(AliasPackage::new( - aliased, - alias_pkg.get_version().to_string(), - alias_pkg.get_pretty_version().to_string(), - )); + // TODO(phase-b): AliasPackage::new expects Box<dyn BasePackage>; have Box<dyn PackageInterface> + let _aliased = packages.get(&alias_key).unwrap().clone_package_box(); + let new_alias_package: Box<dyn PackageInterface> = todo!(); packages.insert(key, new_alias_package); } } @@ -1529,7 +1554,9 @@ impl Installer { return None; } - Some(PoolOptimizer::new(policy)) + // TODO(phase-b): PoolOptimizer::new takes owned Box<dyn PolicyInterface>; have &dyn + let _ = policy; + todo!() } fn get_audit_config(&mut self) -> anyhow::Result<&AuditConfig> { @@ -1547,9 +1574,10 @@ impl Installer { fn create_security_audit_pool_filter( &mut self, ) -> anyhow::Result<Option<SecurityAdvisoryPoolFilter>> { + let update_mirrors = self.update_mirrors; let audit_config = self.get_audit_config()?; - if audit_config.block_insecure && !self.update_mirrors { + if audit_config.block_insecure && !update_mirrors { return Ok(Some(SecurityAdvisoryPoolFilter::new( Auditor, audit_config.clone(), @@ -1561,17 +1589,11 @@ impl Installer { /// Create Installer pub fn create(io: Box<dyn IOInterface>, composer: &Composer) -> Self { - Self::new( - io, - composer.get_config().clone(), - composer.get_package().clone_box(), - composer.get_download_manager().clone(), - composer.get_repository_manager().clone(), - composer.get_locker().clone(), - composer.get_installation_manager().clone(), - composer.get_event_dispatcher().clone(), - composer.get_autoload_generator().clone(), - ) + // TODO(phase-b): Installer::new takes owned manager/locker/etc., but Composer holds them + // by value without Clone (correct for PHP class semantics). Requires refactoring + // Installer to hold &/Rc references or moving ownership out of Composer. + let _ = (io, composer); + todo!() } /// Packages of those types are ignored, by default php-ext and php-ext-zend are ignored @@ -1745,14 +1767,14 @@ impl Installer { pub fn set_ignore_platform_requirements( &mut self, ignore_platform_reqs: shirabe_php_shim::PhpMixed, - ) -> &mut Self { + ) -> anyhow::Result<&mut Self> { trigger_error( "Installer::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.", shirabe_php_shim::E_USER_DEPRECATED, ); - self.set_platform_requirement_filter(PlatformRequirementFilterFactory::from_bool_or_list( - ignore_platform_reqs, + Ok(self.set_platform_requirement_filter( + PlatformRequirementFilterFactory::from_bool_or_list(ignore_platform_reqs)?, )) } @@ -1775,13 +1797,12 @@ impl Installer { /// restrict the update operation to a few packages, all other packages /// that are already installed will be kept at their current version pub fn set_update_allow_list(&mut self, packages: Vec<String>) -> &mut Self { - if count(&packages) == 0 { + if packages.len() == 0 { self.update_allow_list = None; } else { - self.update_allow_list = Some(array_values(array_unique(array_map( - |s: &String| strtolower(s), - &packages, - )))); + let lowered: Vec<String> = array_map(|s: &String| strtolower(s), &packages); + let unique: Vec<String> = array_unique(&lowered); + self.update_allow_list = Some(unique); } self @@ -1795,15 +1816,12 @@ impl Installer { &mut self, update_allow_transitive_dependencies: i64, ) -> anyhow::Result<&mut Self> { - if !in_array( - update_allow_transitive_dependencies, - &vec![ - Request::UPDATE_ONLY_LISTED, - Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE, - Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS, - ], - true, - ) { + let valid = [ + Request::UPDATE_ONLY_LISTED, + Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE, + Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS, + ]; + if !valid.contains(&update_allow_transitive_dependencies) { return Err(RuntimeException { message: "Invalid value for updateAllowTransitiveDependencies supplied".to_string(), code: 0, |
