aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/installer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/src/installer.rs')
-rw-r--r--crates/shirabe/src/installer.rs1791
1 files changed, 1791 insertions, 0 deletions
diff --git a/crates/shirabe/src/installer.rs b/crates/shirabe/src/installer.rs
index 63e0df3..bcba5e9 100644
--- a/crates/shirabe/src/installer.rs
+++ b/crates/shirabe/src/installer.rs
@@ -1 +1,1792 @@
//! ref: composer/src/Composer/Installer.php
+
+use indexmap::IndexMap;
+
+use shirabe_external_packages::seld::json_lint::parsing_exception::ParsingException;
+use shirabe_php_shim::{
+ 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, RuntimeException,
+};
+use shirabe_semver;
+
+use crate::advisory::audit_config::AuditConfig;
+use crate::advisory::auditor::Auditor;
+use crate::autoload::autoload_generator::AutoloadGenerator;
+use crate::composer::Composer;
+use crate::config::Config;
+use crate::console::github_action_error::GithubActionError;
+use crate::dependency_resolver::default_policy::DefaultPolicy;
+use crate::dependency_resolver::local_repo_transaction::LocalRepoTransaction;
+use crate::dependency_resolver::lock_transaction::LockTransaction;
+use crate::dependency_resolver::operation::install_operation::InstallOperation;
+use crate::dependency_resolver::operation::operation_interface::OperationInterface;
+use crate::dependency_resolver::operation::uninstall_operation::UninstallOperation;
+use crate::dependency_resolver::operation::update_operation::UpdateOperation;
+use crate::dependency_resolver::policy_interface::PolicyInterface;
+use crate::dependency_resolver::pool::Pool;
+use crate::dependency_resolver::pool_optimizer::PoolOptimizer;
+use crate::dependency_resolver::request::Request;
+use crate::dependency_resolver::security_advisory_pool_filter::SecurityAdvisoryPoolFilter;
+use crate::dependency_resolver::solver::Solver;
+use crate::dependency_resolver::solver_problems_exception::SolverProblemsException;
+use crate::downloader::download_manager::DownloadManager;
+use crate::downloader::transport_exception::TransportException;
+use crate::event_dispatcher::event_dispatcher::EventDispatcher;
+use crate::filter::platform_requirement_filter::ignore_list_platform_requirement_filter::IgnoreListPlatformRequirementFilter;
+use crate::filter::platform_requirement_filter::platform_requirement_filter_factory::PlatformRequirementFilterFactory;
+use crate::filter::platform_requirement_filter::platform_requirement_filter_interface::PlatformRequirementFilterInterface;
+use crate::installer::installation_manager::InstallationManager;
+use crate::installer::installer_events::InstallerEvents;
+use crate::installer::suggested_packages_reporter::SuggestedPackagesReporter;
+use crate::io::io_interface::IOInterface;
+use crate::package::alias_package::AliasPackage;
+use crate::package::base_package::{self, BasePackage};
+use crate::package::complete_package::CompletePackage;
+use crate::package::complete_package_interface::CompletePackageInterface;
+use crate::package::dumper::array_dumper::ArrayDumper;
+use crate::package::link::Link;
+use crate::package::loader::array_loader::ArrayLoader;
+use crate::package::locker::Locker;
+use crate::package::package::Package;
+use crate::package::package_interface::PackageInterface;
+use crate::package::root_alias_package::RootAliasPackage;
+use crate::package::root_package_interface::RootPackageInterface;
+use crate::package::version::version_parser::VersionParser;
+use crate::repository::array_repository::ArrayRepository;
+use crate::repository::composite_repository::CompositeRepository;
+use crate::repository::installed_array_repository::InstalledArrayRepository;
+use crate::repository::installed_repository::InstalledRepository;
+use crate::repository::installed_repository_interface::InstalledRepositoryInterface;
+use crate::repository::lock_array_repository::LockArrayRepository;
+use crate::repository::platform_repository::PlatformRepository;
+use crate::repository::repository_interface::RepositoryInterface;
+use crate::repository::repository_manager::RepositoryManager;
+use crate::repository::repository_set::RepositorySet;
+use crate::repository::root_package_repository::RootPackageRepository;
+use crate::script::script_events::ScriptEvents;
+use crate::util::platform::Platform;
+use shirabe_semver::constraint::constraint::Constraint;
+use shirabe_semver::constraint::constraint_interface::ConstraintInterface;
+
+#[derive(Debug)]
+pub struct Installer {
+ pub(crate) io: Box<dyn IOInterface>,
+ pub(crate) config: Config,
+ pub(crate) package: Box<dyn RootPackageInterface>,
+ // TODO can we get rid of the below and just use the package itself?
+ pub(crate) fixed_root_package: Box<dyn RootPackageInterface>,
+ pub(crate) download_manager: DownloadManager,
+ pub(crate) repository_manager: RepositoryManager,
+ pub(crate) locker: Locker,
+ pub(crate) installation_manager: InstallationManager,
+ pub(crate) event_dispatcher: EventDispatcher,
+ pub(crate) autoload_generator: AutoloadGenerator,
+ pub(crate) prefer_source: bool,
+ pub(crate) prefer_dist: bool,
+ pub(crate) optimize_autoloader: bool,
+ pub(crate) class_map_authoritative: bool,
+ pub(crate) apcu_autoloader: bool,
+ pub(crate) apcu_autoloader_prefix: Option<String>,
+ pub(crate) dev_mode: bool,
+ pub(crate) dry_run: bool,
+ pub(crate) download_only: bool,
+ pub(crate) verbose: bool,
+ pub(crate) update: bool,
+ pub(crate) install: bool,
+ pub(crate) dump_autoloader: bool,
+ pub(crate) run_scripts: bool,
+ pub(crate) prefer_stable: bool,
+ pub(crate) prefer_lowest: bool,
+ pub(crate) minimal_update: bool,
+ pub(crate) write_lock: bool,
+ pub(crate) execute_operations: bool,
+ pub(crate) audit: bool,
+ pub(crate) error_on_audit: bool,
+ pub(crate) audit_format: String,
+ audit_config: Option<AuditConfig>,
+ ignored_types: Vec<String>,
+ allowed_types: Option<Vec<String>>,
+ pub(crate) update_mirrors: bool,
+ pub(crate) update_allow_list: Option<Vec<String>>,
+ pub(crate) update_allow_transitive_dependencies: i64,
+ pub(crate) suggested_packages_reporter: SuggestedPackagesReporter,
+ pub(crate) platform_requirement_filter: Box<dyn PlatformRequirementFilterInterface>,
+ pub(crate) additional_fixed_repository: Option<Box<dyn RepositoryInterface>>,
+ pub(crate) temporary_constraints: IndexMap<String, Box<dyn ConstraintInterface>>,
+}
+
+impl Installer {
+ pub const ERROR_NONE: i64 = 0; // no error/success state
+ pub const ERROR_GENERIC_FAILURE: i64 = 1;
+ pub const ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE: i64 = 3;
+ pub const ERROR_LOCK_FILE_INVALID: i64 = 4;
+ // used/declared in SolverProblemsException, carried over here for completeness
+ pub const ERROR_DEPENDENCY_RESOLUTION_FAILED: i64 = 2;
+ pub const ERROR_AUDIT_FAILED: i64 = 5;
+ // technically exceptions are thrown with various status codes >400, but the process exit code is normalized to 100
+ pub const ERROR_TRANSPORT_EXCEPTION: i64 = 100;
+
+ pub fn new(
+ io: Box<dyn IOInterface>,
+ config: Config,
+ package: Box<dyn RootPackageInterface>,
+ download_manager: DownloadManager,
+ repository_manager: RepositoryManager,
+ locker: Locker,
+ installation_manager: InstallationManager,
+ event_dispatcher: EventDispatcher,
+ autoload_generator: AutoloadGenerator,
+ ) -> Self {
+ let suggested_packages_reporter = SuggestedPackagesReporter::new(io.clone_box());
+ let platform_requirement_filter = PlatformRequirementFilterFactory::ignore_nothing();
+ let write_lock = config.get("lock").as_bool().unwrap_or(false);
+
+ Self {
+ io,
+ config,
+ package: package.clone_box(),
+ fixed_root_package: package.clone_box(),
+ download_manager,
+ repository_manager,
+ locker,
+ installation_manager,
+ event_dispatcher,
+ autoload_generator,
+ prefer_source: false,
+ prefer_dist: false,
+ optimize_autoloader: false,
+ class_map_authoritative: false,
+ apcu_autoloader: false,
+ apcu_autoloader_prefix: None,
+ dev_mode: false,
+ dry_run: false,
+ download_only: false,
+ verbose: false,
+ update: false,
+ install: true,
+ dump_autoloader: true,
+ run_scripts: true,
+ prefer_stable: false,
+ prefer_lowest: false,
+ minimal_update: false,
+ write_lock,
+ execute_operations: true,
+ audit: true,
+ error_on_audit: false,
+ audit_format: Auditor::FORMAT_SUMMARY.to_string(),
+ audit_config: None,
+ ignored_types: vec!["php-ext".to_string(), "php-ext-zend".to_string()],
+ allowed_types: None,
+ update_mirrors: false,
+ update_allow_list: None,
+ update_allow_transitive_dependencies: Request::UPDATE_ONLY_LISTED,
+ suggested_packages_reporter,
+ platform_requirement_filter,
+ additional_fixed_repository: None,
+ temporary_constraints: IndexMap::new(),
+ }
+ }
+
+ /// Run installation (or update)
+ pub fn run(&mut self) -> anyhow::Result<i64> {
+ // Disable GC to save CPU cycles, as the dependency solver can create hundreds of thousands
+ // of PHP objects, the GC can spend quite some time walking the tree of references looking
+ // for stuff to collect while there is nothing to collect. This slows things down dramatically
+ // and turning it off results in much better performance. Do not try this at home however.
+ gc_collect_cycles();
+ gc_disable();
+
+ if self.update_allow_list.is_some() && self.update_mirrors {
+ return Err(RuntimeException {
+ message: "The installer options updateMirrors and updateAllowList are mutually exclusive.".to_string(),
+ code: 0,
+ }.into());
+ }
+
+ let is_fresh_install = self.repository_manager.get_local_repository().is_fresh();
+
+ // Force update if there is no lock file present
+ if !self.update && !self.locker.is_locked() {
+ self.io.write_error("<warning>No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.</warning>");
+ self.update = true;
+ }
+
+ if self.dry_run {
+ self.verbose = true;
+ self.run_scripts = false;
+ self.execute_operations = false;
+ self.write_lock = false;
+ self.dump_autoloader = false;
+ self.mock_local_repositories(&mut self.repository_manager);
+ }
+
+ if self.download_only {
+ self.dump_autoloader = false;
+ }
+
+ if self.update && !self.install {
+ self.dump_autoloader = false;
+ }
+
+ if self.run_scripts {
+ Platform::put_env("COMPOSER_DEV_MODE", if self.dev_mode { "1" } else { "0" });
+
+ // dispatch pre event
+ // should we treat this more strictly as running an update and then running an install, triggering events multiple times?
+ let event_name = if self.update {
+ ScriptEvents::PRE_UPDATE_CMD
+ } else {
+ ScriptEvents::PRE_INSTALL_CMD
+ };
+ self.event_dispatcher.dispatch_script(event_name, self.dev_mode);
+ }
+
+ self.download_manager.set_prefer_source(self.prefer_source);
+ self.download_manager.set_prefer_dist(self.prefer_dist);
+
+ let local_repo = self.repository_manager.get_local_repository();
+
+ let res_result: anyhow::Result<i64> = (|| {
+ if self.update {
+ self.do_update(local_repo, self.install)
+ } else {
+ self.do_install(local_repo, false)
+ }
+ })();
+
+ let res = match res_result {
+ Ok(r) => {
+ if r != 0 {
+ return Ok(r);
+ }
+ r
+ }
+ Err(e) => {
+ if self.execute_operations
+ && self.install
+ && self.config.get("notify-on-install").as_bool().unwrap_or(false)
+ {
+ self.installation_manager.notify_installs(&*self.io);
+ }
+ return Err(e);
+ }
+ };
+ let _ = res;
+
+ if self.execute_operations
+ && self.install
+ && self.config.get("notify-on-install").as_bool().unwrap_or(false)
+ {
+ self.installation_manager.notify_installs(&*self.io);
+ }
+
+ if self.update {
+ let installed_repo = InstalledRepository::new(vec![
+ self.locker.get_locked_repository(self.dev_mode)?.clone_box(),
+ Box::new(self.create_platform_repo(false)),
+ Box::new(RootPackageRepository::new(clone(&self.package))),
+ ]);
+ if is_fresh_install {
+ self.suggested_packages_reporter
+ .add_suggestions_from_package(&*self.package);
+ }
+ self.suggested_packages_reporter
+ .output_minimalistic(&installed_repo);
+ }
+
+ // Find abandoned packages and warn user
+ let locked_repository = self.locker.get_locked_repository(true)?;
+ for package in locked_repository.get_packages() {
+ let complete = match package.as_complete_package() {
+ Some(p) if p.is_abandoned() => p,
+ _ => continue,
+ };
+
+ let replacement = if is_string(complete.get_replacement_package()) {
+ format!(
+ "Use {} instead",
+ complete.get_replacement_package().as_string().unwrap_or("")
+ )
+ } else {
+ "No replacement was suggested".to_string()
+ };
+
+ self.io.write_error(&sprintf(
+ "<warning>Package %s is abandoned, you should avoid using it. %s.</warning>",
+ &[
+ complete.get_pretty_name().into(),
+ replacement.into(),
+ ],
+ ));
+ }
+
+ if self.dump_autoloader {
+ // write autoloader
+ if self.optimize_autoloader {
+ self.io.write_error("<info>Generating optimized autoload files</info>");
+ } else {
+ self.io.write_error("<info>Generating autoload files</info>");
+ }
+
+ self.autoload_generator
+ .set_class_map_authoritative(self.class_map_authoritative);
+ self.autoload_generator
+ .set_apcu(self.apcu_autoloader, self.apcu_autoloader_prefix.clone());
+ self.autoload_generator.set_run_scripts(self.run_scripts);
+ self.autoload_generator
+ .set_platform_requirement_filter(self.platform_requirement_filter.clone_box());
+ self.autoload_generator.dump(
+ &self.config,
+ &local_repo,
+ &*self.package,
+ &self.installation_manager,
+ "composer",
+ self.optimize_autoloader,
+ None,
+ Some(&self.locker),
+ )?;
+ }
+
+ 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);
+ }
+ }
+
+ 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;
+ }
+ }
+
+ if show_funding {
+ let mut funding_count: i64 = 0;
+ for package in local_repo.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;
+ }
+ }
+ }
+ 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(),
+ ]);
+ }
+ }
+
+ if self.run_scripts {
+ // dispatch post event
+ let event_name = if self.update {
+ ScriptEvents::POST_UPDATE_CMD
+ } else {
+ ScriptEvents::POST_INSTALL_CMD
+ };
+ self.event_dispatcher.dispatch_script(event_name, self.dev_mode);
+ }
+
+ // re-enable GC except on HHVM which triggers a warning here
+ if !defined("HHVM_VERSION") {
+ gc_enable();
+ }
+
+ let audit_config = self.get_audit_config()?;
+
+ if audit_config.audit {
+ let (packages, target) = if self.update && !self.install {
+ (locked_repository.get_canonical_packages(), "locked")
+ } else {
+ (local_repo.get_canonical_packages(), "installed")
+ };
+ if count(&packages) > 0 {
+ let auditor = Auditor::new();
+ let mut repo_set = RepositorySet::new(
+ "stable".to_string(),
+ IndexMap::new(),
+ vec![],
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ );
+ for repo in self.repository_manager.get_repositories() {
+ repo_set.add_repository(repo);
+ }
+
+ 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,
+ );
+ match audit_result {
+ Ok(n) => {
+ return Ok(if n > 0 && self.error_on_audit {
+ Self::ERROR_AUDIT_FAILED
+ } else {
+ 0
+ });
+ }
+ Err(e) => {
+ if let Some(te) = e.downcast_ref::<TransportException>() {
+ self.io.error(&format!("Failed to audit {} packages.", target));
+ if self.io.is_verbose() {
+ self.io.error(&format!(
+ "[{}] {}",
+ get_class(te),
+ te.get_message()
+ ));
+ }
+ } else {
+ return Err(e);
+ }
+ }
+ }
+ } else {
+ self.io
+ .write_error(&format!("No {} packages - skipping audit.", target));
+ }
+ }
+
+ Ok(0)
+ }
+
+ pub(crate) fn do_update(
+ &mut self,
+ local_repo: Box<dyn InstalledRepositoryInterface>,
+ do_install: bool,
+ ) -> anyhow::Result<i64> {
+ let platform_repo = self.create_platform_repo(true);
+ let aliases = self.get_root_aliases(true);
+
+ let mut locked_repository: Option<Box<dyn LockArrayRepository>> = None;
+
+ let try_load_locked = || -> anyhow::Result<Result<Option<Box<dyn LockArrayRepository>>, ParsingException>> {
+ if self.locker.is_locked() {
+ match self.locker.get_locked_repository(true) {
+ Ok(r) => Ok(Ok(Some(r))),
+ Err(e) => match e.downcast::<ParsingException>() {
+ Ok(p) => Ok(Err(p)),
+ Err(other) => Err(other),
+ },
+ }
+ } else {
+ Ok(Ok(None))
+ }
+ };
+
+ match try_load_locked()? {
+ Ok(r) => locked_repository = r,
+ Err(e) => {
+ if self.update_allow_list.is_some() || self.update_mirrors {
+ // in case we are doing a partial update or updating mirrors, the lock file is needed so we error
+ return Err(e.into());
+ }
+ // otherwise, ignoring parse errors as the lock file will be regenerated from scratch when
+ // doing a full update
+ }
+ }
+
+ if (self.update_allow_list.is_some() || self.update_mirrors)
+ && locked_repository.is_none()
+ {
+ self.io.write_error(
+ &format!(
+ "<error>Cannot update {} without a lock file present. Run `composer update` to generate a lock file.</error>",
+ if self.update_mirrors {
+ "lock file information"
+ } else {
+ "only a partial set of packages"
+ }
+ ),
+ true,
+ IOInterface::QUIET,
+ );
+
+ return Ok(Self::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE);
+ }
+
+ self.io
+ .write_error("<info>Loading composer repositories with package information</info>");
+
+ // creating repository set
+ let policy = self.create_policy(true, locked_repository.as_deref());
+ 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);
+ }
+ if let Some(ref lr) = locked_repository {
+ repository_set.add_repository(lr.clone_box());
+ }
+
+ let mut request = self.create_request(
+ &*self.fixed_root_package,
+ &platform_repo,
+ locked_repository.as_deref(),
+ );
+ self.require_packages_for_update(&mut request, locked_repository.as_deref(), 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);
+ }
+
+ 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()?,
+ ));
+
+ 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;
+ let rule_set_size;
+ match solver
+ .as_mut()
+ .unwrap()
+ .solve(&request, &*self.platform_requirement_filter)
+ {
+ Ok(t) => {
+ lock_transaction = t;
+ rule_set_size = solver.as_ref().unwrap().get_rule_set_size();
+ 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_error(
+ &format!("<error>{}</error>", err),
+ true,
+ IOInterface::QUIET,
+ );
+ self.io.write_error(&pretty_problem);
+ if !self.dev_mode {
+ self.io.write_error(
+ "<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,
+ IOInterface::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()));
+ }
+ return Err(e);
+ }
+ }
+ let _ = solver;
+
+ self.io.write_error(
+ &format!("Analyzed {} packages to resolve dependencies", count(&pool.as_ref().unwrap())),
+ true,
+ IOInterface::VERBOSE,
+ );
+ self.io.write_error(
+ &format!("Analyzed {} rules to resolve dependencies", rule_set_size),
+ true,
+ IOInterface::VERBOSE,
+ );
+
+ pool = None;
+ let _ = pool;
+
+ if lock_transaction.get_operations().is_empty() {
+ self.io.write_error("Nothing to modify in lock file");
+
+ if self.minimal_update
+ && self.update_allow_list.is_none()
+ && self.locker.is_fresh()
+ {
+ self.io.write_error("<warning>The --minimal-changes option should be used with package arguments or after modifying composer.json requirements, otherwise it will likely not yield any dependency changes.</warning>");
+ }
+ }
+
+ let exit_code = self.extract_dev_packages(
+ &mut lock_transaction,
+ &platform_repo,
+ &aliases,
+ &policy,
+ locked_repository.as_deref(),
+ )?;
+ if exit_code != 0 {
+ return Ok(exit_code);
+ }
+
+ shirabe_semver::compiling_matcher::CompilingMatcher::clear();
+
+ // write lock
+ let platform_reqs = self.extract_platform_requirements(&self.package.get_requires());
+ let platform_dev_reqs = self.extract_platform_requirements(&self.package.get_dev_requires());
+
+ let mut installs_updates: Vec<Box<dyn OperationInterface>> = vec![];
+ let mut uninstalls: Vec<Box<dyn OperationInterface>> = vec![];
+ if !lock_transaction.get_operations().is_empty() {
+ let mut install_names: Vec<String> = vec![];
+ let mut update_names: Vec<String> = vec![];
+ let mut uninstall_names: Vec<String> = vec![];
+ for operation in lock_transaction.get_operations() {
+ if let Some(io) = operation.as_install_operation() {
+ installs_updates.push(operation.clone_box());
+ install_names.push(format!(
+ "{}:{}",
+ io.get_package().get_pretty_name(),
+ io.get_package().get_full_pretty_version(true)
+ ));
+ } 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
+ // update in the output as it is only an internal lock file metadata update
+ if self.update_mirrors
+ && uo.get_initial_package().get_name() == uo.get_target_package().get_name()
+ && uo.get_initial_package().get_version() == uo.get_target_package().get_version()
+ {
+ continue;
+ }
+
+ installs_updates.push(operation.clone_box());
+ update_names.push(format!(
+ "{}:{}",
+ uo.get_target_package().get_pretty_name(),
+ uo.get_target_package().get_full_pretty_version(true)
+ ));
+ } else if let Some(uo) = operation.as_uninstall_operation() {
+ uninstalls.push(operation.clone_box());
+ uninstall_names.push(uo.get_package().get_pretty_name().to_string());
+ }
+ }
+
+ if self.config.get("lock").as_bool().unwrap_or(false) {
+ 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(),
+ ],
+ ));
+ if !install_names.is_empty() {
+ self.io.write_error(
+ &format!("Installs: {}", implode(", ", &install_names)),
+ true,
+ IOInterface::VERBOSE,
+ );
+ }
+ if !update_names.is_empty() {
+ self.io.write_error(
+ &format!("Updates: {}", implode(", ", &update_names)),
+ true,
+ IOInterface::VERBOSE,
+ );
+ }
+ if !uninstalls.is_empty() {
+ self.io.write_error(
+ &format!("Removals: {}", implode(", ", &uninstall_names)),
+ true,
+ IOInterface::VERBOSE,
+ );
+ }
+ }
+ }
+
+ 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()
+ };
+ 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);
+ for operation in &merged {
+ // collect suggestions
+ if let Some(io) = operation.as_install_operation() {
+ self.suggested_packages_reporter
+ .add_suggestions_from_package(&*io.get_package());
+ }
+
+ // output op if lock file is enabled, but alias op only in debug verbosity
+ if self.config.get("lock").as_bool().unwrap_or(false)
+ && (strpos(operation.get_operation_type(), "Alias") == false || self.io.is_debug())
+ {
+ let mut source_repo = String::new();
+ if self.io.is_very_verbose()
+ && strpos(operation.get_operation_type(), "Alias") == false
+ {
+ let operation_pkg: Box<dyn PackageInterface> = if let Some(uo) = operation.as_update_operation() {
+ uo.get_target_package().clone_box()
+ } else {
+ operation.get_package().clone_box()
+ };
+ if let Some(repo) = operation_pkg.get_repository() {
+ source_repo = format!(" from {}", repo.get_repo_name());
+ }
+ }
+ self.io.write_error(&format!(" - {}{}", operation.show(true), source_repo));
+ }
+ }
+
+ 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),
+ platform_reqs,
+ platform_dev_reqs,
+ lock_transaction.get_aliases(aliases.clone()),
+ self.package.get_minimum_stability(),
+ self.package.get_stability_flags(),
+ self.prefer_stable || self.package.get_prefer_stable(),
+ self.prefer_lowest,
+ self.config.get("platform").as_array().cloned().unwrap_or_default(),
+ self.write_lock && self.execute_operations,
+ )?;
+ if updated_lock && self.write_lock && self.execute_operations {
+ self.io.write_error("<info>Writing lock file</info>");
+ }
+
+ if do_install {
+ // TODO ensure lock is used from locker as-is, since it may not have been written to disk in case of executeOperations == false
+ return self.do_install(local_repo, true);
+ }
+
+ Ok(0)
+ }
+
+ /// Run the solver a second time on top of the existing update result with only the current result set in the pool
+ /// and see what packages would get removed if we only had the non-dev packages in the solver request
+ pub(crate) fn extract_dev_packages(
+ &mut self,
+ lock_transaction: &mut LockTransaction,
+ platform_repo: &PlatformRepository,
+ aliases: &Vec<IndexMap<String, String>>,
+ policy: &dyn PolicyInterface,
+ locked_repository: Option<&dyn LockArrayRepository>,
+ ) -> anyhow::Result<i64> {
+ if self.package.get_dev_requires().is_empty() {
+ return Ok(0);
+ }
+
+ let mut result_repo = ArrayRepository::new(vec![]);
+ 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(dumper.dump(&*pkg), "Composer\\Package\\CompletePackage".to_string())?);
+ }
+
+ let mut repository_set = self.create_repository_set(true, platform_repo, aliases, None);
+ 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);
+
+ 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;
+ match solver
+ .as_mut()
+ .unwrap()
+ .solve(&request, &*self.platform_requirement_filter)
+ {
+ 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_error(
+ &format!("<error>{}</error>", err),
+ true,
+ IOInterface::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());
+ }
+ return Err(e);
+ }
+ }
+ let _ = solver;
+
+ lock_transaction.set_non_dev_packages(non_dev_lock_transaction);
+
+ Ok(0)
+ }
+
+ /// 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>,
+ already_solved: bool,
+ ) -> anyhow::Result<i64> {
+ if self.config.get("lock").as_bool().unwrap_or(false) {
+ self.io.write_error(&format!(
+ "<info>Installing dependencies from lock file{}</info>",
+ if self.dev_mode { " (including require-dev)" } else { "" }
+ ));
+ }
+
+ let locked_repository = self.locker.get_locked_repository(self.dev_mode)?;
+
+ // verify that the lock file works with the current platform repository
+ // we can skip this part if we're doing this as the second step after an update
+ if !already_solved {
+ self.io.write_error("<info>Verifying lock file contents can be installed on current platform.</info>");
+
+ let platform_repo = self.create_platform_repo(false);
+ // creating repository set
+ let policy = self.create_policy(false, None);
+ // use aliases from lock file only, so empty root aliases here
+ let mut repository_set = self.create_repository_set(
+ false,
+ &platform_repo,
+ &vec![],
+ Some(&*locked_repository),
+ );
+ 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),
+ );
+
+ if !self.locker.is_fresh() {
+ self.io.write_error(
+ "<warning>Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update <package name>`.</warning>",
+ true,
+ IOInterface::QUIET,
+ );
+ }
+
+ let missing_requirement_info = self.locker.get_missing_requirement_info(&*self.package, self.dev_mode);
+ if !missing_requirement_info.is_empty() {
+ self.io.write_error(missing_requirement_info);
+
+ if !self.config.get("allow-missing-requirements").as_bool().unwrap_or(false) {
+ return Ok(Self::ERROR_LOCK_FILE_INVALID);
+ }
+ }
+
+ for package in locked_repository.get_packages() {
+ request.fix_locked_package(package);
+ }
+
+ let mut root_requires = self.package.get_requires();
+ if self.dev_mode {
+ root_requires = array_merge(root_requires, self.package.get_dev_requires());
+ }
+ 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()));
+ }
+ }
+
+ 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()));
+ }
+ }
+ 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,
+ );
+
+ // solve dependencies
+ let mut solver: Option<Solver> = Some(Solver::new(&policy, &pool, &*self.io));
+ match solver
+ .as_mut()
+ .unwrap()
+ .solve(&request, &*self.platform_requirement_filter)
+ {
+ 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()) {
+ self.io.write_error(
+ "<error>Your lock file cannot be installed on this system without changes. Please run composer update.</error>",
+ true,
+ IOInterface::QUIET,
+ );
+
+ return Ok(Self::ERROR_LOCK_FILE_INVALID);
+ }
+ }
+ 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_error(
+ &format!("<error>{}</error>", err),
+ true,
+ IOInterface::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()));
+ }
+ return Err(e);
+ }
+ }
+ let _ = solver;
+ }
+
+ // 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 mut installs: Vec<String> = vec![];
+ let mut updates: Vec<String> = vec![];
+ let mut uninstalls: Vec<String> = vec![];
+ for operation in local_repo_transaction.get_operations() {
+ if let Some(io) = operation.as_install_operation() {
+ installs.push(format!(
+ "{}:{}",
+ io.get_package().get_pretty_name(),
+ io.get_package().get_full_pretty_version(true)
+ ));
+ } 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)
+ ));
+ } else if let Some(uo) = operation.as_uninstall_operation() {
+ uninstalls.push(uo.get_package().get_pretty_name().to_string());
+ }
+ }
+
+ if installs.is_empty() && updates.is_empty() && uninstalls.is_empty() {
+ self.io.write_error("Nothing to install, update or remove");
+ } else {
+ 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(),
+ ],
+ ));
+ if !installs.is_empty() {
+ self.io.write_error(
+ &format!("Installs: {}", implode(", ", &installs)),
+ true,
+ IOInterface::VERBOSE,
+ );
+ }
+ if !updates.is_empty() {
+ self.io.write_error(
+ &format!("Updates: {}", implode(", ", &updates)),
+ true,
+ IOInterface::VERBOSE,
+ );
+ }
+ if !uninstalls.is_empty() {
+ self.io.write_error(
+ &format!("Removals: {}", implode(", ", &uninstalls)),
+ true,
+ IOInterface::VERBOSE,
+ );
+ }
+ }
+
+ if self.execute_operations {
+ local_repo.set_dev_package_names(self.locker.get_dev_package_names());
+ self.installation_manager.execute(
+ &*local_repo,
+ local_repo_transaction.get_operations(),
+ self.dev_mode,
+ self.run_scripts,
+ self.download_only,
+ )?;
+
+ // see https://github.com/composer/composer/issues/2764
+ if count(&local_repo_transaction.get_operations()) > 0 {
+ let vendor_dir = self.config.get("vendor-dir").as_string().unwrap_or("").to_string();
+ if is_dir(&vendor_dir) {
+ // suppress errors as this fails sometimes on OSX for no apparent reason
+ // see https://github.com/composer/composer/issues/4070#issuecomment-129792748
+ let _ = touch(&vendor_dir);
+ }
+ }
+ } 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() {
+ self.io.write_error(&format!(" - {}", operation.show(false)));
+ }
+ }
+ }
+
+ Ok(0)
+ }
+
+ pub(crate) fn create_platform_repo(&self, for_update: bool) -> PlatformRepository {
+ let platform_overrides = if for_update {
+ self.config.get("platform").as_array().cloned().unwrap_or_default()
+ } else {
+ self.locker.get_platform_overrides()
+ };
+
+ PlatformRepository::new(vec![], platform_overrides)
+ }
+
+ fn create_repository_set(
+ &mut self,
+ for_update: bool,
+ platform_repo: &PlatformRepository,
+ root_aliases: &Vec<IndexMap<String, String>>,
+ locked_repository: Option<&dyn RepositoryInterface>,
+ ) -> RepositorySet {
+ let minimum_stability;
+ 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();
+
+ // Convert Link map merge into ConstraintInterface map for use later
+ let mut req_links: IndexMap<String, Link> = IndexMap::new();
+ for (k, v) in self.package.get_requires() {
+ req_links.insert(k, v);
+ }
+ for (k, v) in self.package.get_dev_requires() {
+ req_links.insert(k, v);
+ }
+ // 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());
+ }
+ requires = tmp;
+ } else {
+ minimum_stability = self.locker.get_minimum_stability();
+ stability_flags = self.locker.get_stability_flags();
+
+ let mut tmp: IndexMap<String, Box<dyn ConstraintInterface>> = IndexMap::new();
+ for package in locked_repository.unwrap().get_packages() {
+ let mut constraint = Constraint::new("=", package.get_version().to_string());
+ constraint.set_pretty_string(Some(package.get_pretty_version().to_string()));
+ tmp.insert(package.get_name().to_string(), Box::new(constraint));
+ }
+ requires = tmp;
+ }
+
+ let mut root_requires: IndexMap<String, Box<dyn ConstraintInterface>> = IndexMap::new();
+ for (req, mut constraint) in requires {
+ // skip platform requirements from the root package to avoid filtering out existing platform packages
+ if self.platform_requirement_filter.is_ignored(&req) {
+ continue;
+ } else if let Some(filter) = self
+ .platform_requirement_filter
+ .as_any()
+ .downcast_ref::<IgnoreListPlatformRequirementFilter>()
+ {
+ constraint = filter.filter_constraint(&req, constraint);
+ }
+ 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());
+
+ stability_flags.insert(
+ self.package.get_name().to_string(),
+ base_package::STABILITIES[VersionParser::parse_stability(self.package.get_version()).as_str()],
+ );
+
+ let mut repository_set = RepositorySet::new(
+ minimum_stability,
+ stability_flags,
+ root_aliases.clone(),
+ self.package.get_references(),
+ root_requires,
+ self.temporary_constraints.clone(),
+ );
+ repository_set.add_repository(Box::new(RootPackageRepository::new(clone(&self.fixed_root_package))));
+ 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
+ let additional_fixed_repositories: Vec<Box<dyn RepositoryInterface>> =
+ if let Some(composite) = additional_fixed_repository
+ .as_any()
+ .downcast_ref::<CompositeRepository>()
+ {
+ composite.get_repositories()
+ } else {
+ vec![additional_fixed_repository.clone_box()]
+ };
+ for additional_fixed_repository in &additional_fixed_repositories {
+ 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();
+ break;
+ }
+ }
+
+ repository_set.add_repository(additional_fixed_repository.clone_box());
+ }
+
+ repository_set
+ }
+
+ fn create_policy(
+ &self,
+ for_update: bool,
+ locked_repo: Option<&dyn 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();
+ }
+ // old lock file without prefer stable/lowest will return null
+ // so in this case we use the composer.json info
+ if prefer_stable.is_none() {
+ prefer_stable = Some(self.prefer_stable || self.package.get_prefer_stable());
+ }
+ if prefer_lowest.is_none() {
+ prefer_lowest = Some(self.prefer_lowest);
+ }
+
+ let mut preferred_versions: Option<IndexMap<String, String>> = None;
+ if for_update && self.minimal_update && locked_repo.is_some() {
+ let mut versions: IndexMap<String, String> = IndexMap::new();
+ for pkg in locked_repo.unwrap().get_packages() {
+ 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,
+ ))
+ {
+ continue;
+ }
+ versions.insert(pkg.get_name().to_string(), pkg.get_version().to_string());
+ }
+ preferred_versions = Some(versions);
+ }
+
+ DefaultPolicy::new(prefer_stable.unwrap(), prefer_lowest.unwrap(), preferred_versions)
+ }
+
+ fn create_request(
+ &self,
+ root_package: &dyn RootPackageInterface,
+ platform_repo: &PlatformRepository,
+ locked_repository: Option<&dyn LockArrayRepository>,
+ ) -> Request {
+ let mut request = Request::new(locked_repository);
+
+ 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());
+ }
+
+ // fix the version of all platform packages + additionally installed packages
+ // to prevent the solver trying to remove or update those
+ // TODO why not replaces?
+ let provided = root_package.get_provides();
+ for package in fixed_packages {
+ // skip platform packages that are provided by the root package
+ let pkg_repo_is_platform = match package.get_repository() {
+ Some(r) => std::ptr::eq(
+ r.as_any() as *const _ as *const u8,
+ platform_repo.as_any() as *const _ as *const u8,
+ ),
+ None => false,
+ };
+ if !pkg_repo_is_platform
+ || !provided.contains_key(package.get_name())
+ || !provided
+ .get(package.get_name())
+ .unwrap()
+ .get_constraint()
+ .matches(&Constraint::new("=", package.get_version().to_string()))
+ {
+ request.fix_package(&*package);
+ }
+ }
+
+ request
+ }
+
+ fn require_packages_for_update(
+ &self,
+ request: &mut Request,
+ locked_repository: Option<&dyn LockArrayRepository>,
+ include_dev_requires: bool,
+ ) {
+ // 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())
+ } else {
+ IndexMap::new()
+ };
+
+ for locked_package in locked_repository.unwrap().get_packages() {
+ // exclude alias packages here as for root aliases, both alias and aliased are
+ // present in the lock repo and we only want to require the aliased version
+ if locked_package.as_alias_package().is_none()
+ && !excluded_packages.contains_key(locked_package.get_name())
+ {
+ request.require_name(
+ locked_package.get_name().to_string(),
+ 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 (_key, link) in &links {
+ request.require_name(link.get_target().to_string(), Some(link.get_constraint()));
+ }
+ }
+ }
+
+ fn get_root_aliases(&self, for_update: bool) -> Vec<IndexMap<String, String>> {
+ let aliases = if for_update {
+ self.package.get_aliases()
+ } else {
+ self.locker.get_aliases()
+ };
+
+ aliases
+ }
+
+ fn extract_platform_requirements(&self, links: &IndexMap<String, Link>) -> IndexMap<String, String> {
+ let mut platform_reqs: IndexMap<String, String> = IndexMap::new();
+ for (_key, link) in links {
+ if PlatformRepository::is_platform_package(link.get_target()) {
+ platform_reqs.insert(link.get_target().to_string(), link.get_pretty_constraint().to_string());
+ }
+ }
+
+ platform_reqs
+ }
+
+ /// Replace local repositories with InstalledArrayRepository instances
+ ///
+ /// This is to prevent any accidental modification of the existing repos on disk
+ fn mock_local_repositories(&self, rm: &mut RepositoryManager) {
+ let mut packages: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new();
+ for package in rm.get_local_repository().get_packages() {
+ packages.insert(package.to_string(), package.clone_box());
+ }
+ let keys: Vec<String> = packages.keys().cloned().collect();
+ for key in keys {
+ let package_clone = packages.get(&key).unwrap().clone_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);
+ // PHP: $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion());
+ let aliased = packages.get(&alias_key).unwrap().clone_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(),
+ ));
+ packages.insert(key, new_alias_package);
+ }
+ }
+ rm.set_local_repository(Box::new(InstalledArrayRepository::new_with_packages(
+ packages.into_values().collect(),
+ )));
+ }
+
+ fn create_pool_optimizer(&self, policy: &dyn PolicyInterface) -> Option<PoolOptimizer> {
+ // Not the best architectural decision here, would need to be able
+ // to configure from the outside of Installer but this is only
+ // a debugging tool and should never be required in any other use case
+ if Platform::get_env("COMPOSER_POOL_OPTIMIZER").as_deref() == Some("0") {
+ self.io.write(
+ "Pool Optimizer was disabled for debugging purposes.",
+ true,
+ IOInterface::DEBUG,
+ );
+
+ return None;
+ }
+
+ Some(PoolOptimizer::new(policy))
+ }
+
+ fn get_audit_config(&mut self) -> anyhow::Result<&AuditConfig> {
+ if self.audit_config.is_none() {
+ self.audit_config = Some(AuditConfig::from_config(
+ &self.config,
+ self.audit,
+ &self.audit_format,
+ )?);
+ }
+
+ Ok(self.audit_config.as_ref().unwrap())
+ }
+
+ fn create_security_audit_pool_filter(&mut self) -> anyhow::Result<Option<SecurityAdvisoryPoolFilter>> {
+ let audit_config = self.get_audit_config()?;
+
+ if audit_config.block_insecure && !self.update_mirrors {
+ return Ok(Some(SecurityAdvisoryPoolFilter::new(
+ Auditor::new(),
+ audit_config.clone(),
+ )));
+ }
+
+ Ok(None)
+ }
+
+ /// 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(),
+ )
+ }
+
+ /// Packages of those types are ignored, by default php-ext and php-ext-zend are ignored
+ pub fn set_ignored_types(&mut self, types: Vec<String>) -> &mut Self {
+ self.ignored_types = types;
+
+ self
+ }
+
+ /// Only packages of those types are allowed if set to non-null
+ pub fn set_allowed_types(&mut self, types: Option<Vec<String>>) -> &mut Self {
+ self.allowed_types = types;
+
+ self
+ }
+
+ pub fn set_additional_fixed_repository(
+ &mut self,
+ additional_fixed_repository: Box<dyn RepositoryInterface>,
+ ) -> &mut Self {
+ self.additional_fixed_repository = Some(additional_fixed_repository);
+
+ self
+ }
+
+ pub fn set_temporary_constraints(
+ &mut self,
+ constraints: IndexMap<String, Box<dyn ConstraintInterface>>,
+ ) -> &mut Self {
+ self.temporary_constraints = constraints;
+
+ self
+ }
+
+ /// Whether to run in drymode or not
+ pub fn set_dry_run(&mut self, dry_run: bool) -> &mut Self {
+ self.dry_run = dry_run;
+
+ self
+ }
+
+ /// Checks, if this is a dry run (simulation mode).
+ pub fn is_dry_run(&self) -> bool {
+ self.dry_run
+ }
+
+ /// Whether to download only or not.
+ pub fn set_download_only(&mut self, download_only: bool) -> &mut Self {
+ self.download_only = download_only;
+
+ self
+ }
+
+ /// prefer source installation
+ pub fn set_prefer_source(&mut self, prefer_source: bool) -> &mut Self {
+ self.prefer_source = prefer_source;
+
+ self
+ }
+
+ /// prefer dist installation
+ pub fn set_prefer_dist(&mut self, prefer_dist: bool) -> &mut Self {
+ self.prefer_dist = prefer_dist;
+
+ self
+ }
+
+ /// Whether or not generated autoloader are optimized
+ pub fn set_optimize_autoloader(&mut self, optimize_autoloader: bool) -> &mut Self {
+ self.optimize_autoloader = optimize_autoloader;
+ if !self.optimize_autoloader {
+ // Force classMapAuthoritative off when not optimizing the
+ // autoloader
+ self.set_class_map_authoritative(false);
+ }
+
+ self
+ }
+
+ /// Whether or not generated autoloader considers the class map
+ /// authoritative.
+ pub fn set_class_map_authoritative(&mut self, class_map_authoritative: bool) -> &mut Self {
+ self.class_map_authoritative = class_map_authoritative;
+ if self.class_map_authoritative {
+ // Force optimizeAutoloader when classmap is authoritative
+ self.set_optimize_autoloader(true);
+ }
+
+ self
+ }
+
+ /// Whether or not generated autoloader considers APCu caching.
+ pub fn set_apcu_autoloader(
+ &mut self,
+ apcu_autoloader: bool,
+ apcu_autoloader_prefix: Option<String>,
+ ) -> &mut Self {
+ self.apcu_autoloader = apcu_autoloader;
+ self.apcu_autoloader_prefix = apcu_autoloader_prefix;
+
+ self
+ }
+
+ /// update packages
+ pub fn set_update(&mut self, update: bool) -> &mut Self {
+ self.update = update;
+
+ self
+ }
+
+ /// Allows disabling the install step after an update
+ pub fn set_install(&mut self, install: bool) -> &mut Self {
+ self.install = install;
+
+ self
+ }
+
+ /// enables dev packages
+ pub fn set_dev_mode(&mut self, dev_mode: bool) -> &mut Self {
+ self.dev_mode = dev_mode;
+
+ self
+ }
+
+ /// set whether to run autoloader or not
+ ///
+ /// This is disabled implicitly when enabling dryRun
+ pub fn set_dump_autoloader(&mut self, dump_autoloader: bool) -> &mut Self {
+ self.dump_autoloader = dump_autoloader;
+
+ self
+ }
+
+ /// set whether to run scripts or not
+ ///
+ /// This is disabled implicitly when enabling dryRun
+ ///
+ /// Deprecated: Use setRunScripts(false) on the EventDispatcher instance being injected instead
+ pub fn set_run_scripts(&mut self, run_scripts: bool) -> &mut Self {
+ self.run_scripts = run_scripts;
+
+ self
+ }
+
+ /// set the config instance
+ pub fn set_config(&mut self, config: Config) -> &mut Self {
+ self.config = config;
+
+ self
+ }
+
+ /// run in verbose mode
+ pub fn set_verbose(&mut self, verbose: bool) -> &mut Self {
+ self.verbose = verbose;
+
+ self
+ }
+
+ /// Checks, if running in verbose mode.
+ pub fn is_verbose(&self) -> bool {
+ self.verbose
+ }
+
+ /// set ignore Platform Package requirements
+ ///
+ /// If this is set to true, all platform requirements are ignored
+ /// If this is set to false, no platform requirements are ignored
+ /// If this is set to string[], those packages will be ignored
+ ///
+ /// Deprecated: use setPlatformRequirementFilter instead
+ pub fn set_ignore_platform_requirements(
+ &mut self,
+ ignore_platform_reqs: shirabe_php_shim::PhpMixed,
+ ) -> &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))
+ }
+
+ pub fn set_platform_requirement_filter(
+ &mut self,
+ platform_requirement_filter: Box<dyn PlatformRequirementFilterInterface>,
+ ) -> &mut Self {
+ self.platform_requirement_filter = platform_requirement_filter;
+
+ self
+ }
+
+ /// Update the lock file to the exact same versions and references but use current remote metadata like URLs and mirror info
+ pub fn set_update_mirrors(&mut self, update_mirrors: bool) -> &mut Self {
+ self.update_mirrors = update_mirrors;
+
+ self
+ }
+
+ /// 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 {
+ self.update_allow_list = None;
+ } else {
+ self.update_allow_list = Some(array_values(array_unique(array_map(
+ |s: &String| strtolower(s),
+ &packages,
+ ))));
+ }
+
+ self
+ }
+
+ /// Should dependencies of packages marked for update be updated?
+ ///
+ /// Depending on the chosen constant this will either only update the directly named packages, all transitive
+ /// dependencies which are not root requirement or all transitive dependencies including root requirements
+ pub fn set_update_allow_transitive_dependencies(
+ &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,
+ ) {
+ return Err(RuntimeException {
+ message: "Invalid value for updateAllowTransitiveDependencies supplied".to_string(),
+ code: 0,
+ }
+ .into());
+ }
+
+ self.update_allow_transitive_dependencies = update_allow_transitive_dependencies;
+
+ Ok(self)
+ }
+
+ /// Should packages be preferred in a stable version when updating?
+ pub fn set_prefer_stable(&mut self, prefer_stable: bool) -> &mut Self {
+ self.prefer_stable = prefer_stable;
+
+ self
+ }
+
+ /// Should packages be preferred in a lowest version when updating?
+ pub fn set_prefer_lowest(&mut self, prefer_lowest: bool) -> &mut Self {
+ self.prefer_lowest = prefer_lowest;
+
+ self
+ }
+
+ /// Only relevant for partial updates (with setUpdateAllowList), if this is enabled currently locked versions will be preferred for packages which are not in the allowlist
+ ///
+ /// This reduces the update to
+ pub fn set_minimal_update(&mut self, minimal_update: bool) -> &mut Self {
+ self.minimal_update = minimal_update;
+
+ self
+ }
+
+ /// Should the lock file be updated when updating?
+ ///
+ /// This is disabled implicitly when enabling dryRun
+ pub fn set_write_lock(&mut self, write_lock: bool) -> &mut Self {
+ self.write_lock = write_lock;
+
+ self
+ }
+
+ /// Should the operations (package install, update and removal) be executed on disk?
+ ///
+ /// This is disabled implicitly when enabling dryRun
+ pub fn set_execute_operations(&mut self, execute_operations: bool) -> &mut Self {
+ self.execute_operations = execute_operations;
+
+ self
+ }
+
+ /// Should an audit be run after installation is complete?
+ ///
+ /// Deprecated: Use setAuditConfig instead of calling this
+ pub fn set_audit(&mut self, audit: bool) -> &mut Self {
+ self.audit = audit;
+ self.audit_config = None; // Invalidate cached config
+
+ self
+ }
+
+ /// Should exit with status code 5 on audit error
+ pub fn set_error_on_audit(&mut self, error_on_audit: bool) -> &mut Self {
+ self.error_on_audit = error_on_audit;
+
+ self
+ }
+
+ /// What format should be used for audit output?
+ ///
+ /// Deprecated: Use setAuditConfig instead of calling this
+ pub fn set_audit_format(&mut self, audit_format: String) -> &mut Self {
+ self.audit_format = audit_format;
+ self.audit_config = None; // Invalidate cached config
+
+ self
+ }
+
+ /// Sets a custom AuditConfig to override the default configuration from Config
+ pub fn set_audit_config(&mut self, audit_config: AuditConfig) -> &mut Self {
+ self.audit_config = Some(audit_config);
+
+ self
+ }
+
+ /// Disables plugins.
+ ///
+ /// Call this if you want to ensure that third-party code never gets
+ /// executed. The default is to automatically install, and execute
+ /// custom third-party installers.
+ pub fn disable_plugins(&mut self) -> &mut Self {
+ // TODO(plugin): plugin disabling is part of the plugin API
+ self.installation_manager.disable_plugins();
+
+ self
+ }
+
+ pub fn set_suggested_packages_reporter(
+ &mut self,
+ suggested_packages_reporter: SuggestedPackagesReporter,
+ ) -> &mut Self {
+ self.suggested_packages_reporter = suggested_packages_reporter;
+
+ self
+ }
+}