//! ref: composer/src/Composer/Command/ReinstallCommand.php use crate::command::BaseCommand; use crate::command::BaseCommandData; use crate::command::base_command::base_command_initialize; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::dependency_resolver::Transaction; use crate::dependency_resolver::operation::InstallOperation; use crate::dependency_resolver::operation::UninstallOperation; use crate::io::IOInterfaceImmutable; use crate::package::base_package; use crate::plugin::CommandEvent; use crate::plugin::PluginEvents; use crate::script::ScriptEvents; use crate::util::Platform; use anyhow::Result; use shirabe_external_packages::composer::pcre::Preg; use shirabe_external_packages::symfony::console::command::command::Command; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::output::OutputInterface; use shirabe_php_shim::InvalidArgumentException; use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] pub struct ReinstallCommand { base_command_data: BaseCommandData, } impl Default for ReinstallCommand { fn default() -> Self { Self::new() } } impl ReinstallCommand { pub fn new() -> Self { let command = ReinstallCommand { base_command_data: BaseCommandData::new(None), }; command .configure() .expect("ReinstallCommand::configure uses static, valid metadata"); command } } impl Command for ReinstallCommand { fn configure(&self) -> anyhow::Result<()> { // TODO(cli-completion): suggest_prefer_install / suggest_installed_package_types / suggest_installed_package self.set_name("reinstall")?; self.set_description("Uninstalls and reinstalls the given package names"); self.set_definition(&[ InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None).unwrap().into(), InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None).unwrap().into(), InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None).unwrap().into(), InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None).unwrap().into(), InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None).unwrap().into(), InputOption::new("optimize-autoloader", Some(shirabe_php_shim::PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None).unwrap().into(), InputOption::new("classmap-authoritative", Some(shirabe_php_shim::PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None).unwrap().into(), InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None).unwrap().into(), InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None).unwrap().into(), InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None).unwrap().into(), InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None).unwrap().into(), InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Filter packages to reinstall by type(s)", None).unwrap().into(), InputArgument::new("packages", Some(InputArgument::IS_ARRAY), "List of package names to reinstall, can include a wildcard (*) to match any substring.", None).unwrap().into(), ]); self.set_help( "The reinstall command looks up installed packages by name,\n\ uninstalls them and reinstalls them. This lets you do a clean install\n\ of a package if you messed with its files, or if you wish to change\n\ the installation type using --prefer-install.\n\n\ shirabe reinstall acme/foo \"acme/bar-*\"\n\n\ Read more at https://getcomposer.org/doc/03-cli.md#reinstall", ); Ok(()) } fn execute( &self, input: Rc>, output: Rc>, ) -> anyhow::Result { let composer = self.require_composer(None, None)?; let composer = crate::composer::composer_full(&composer); let io = self.get_io(); let repository_manager = composer.get_repository_manager().clone(); let repository_manager = repository_manager.borrow(); let local_repo = repository_manager.get_local_repository(); let mut packages_to_reinstall: Vec = vec![]; let mut package_names_to_reinstall: Vec = vec![]; let type_option = input.borrow().get_option("type")?; let type_count = type_option.as_list().map_or(0, |l| l.len()); let packages_arg = input.borrow().get_argument("packages")?; let packages_count = packages_arg.as_list().map_or(0, |l| l.len()); if type_count > 0 { if packages_count > 0 { return Err(InvalidArgumentException { message: "You cannot specify package names and filter by type at the same time." .to_string(), code: 0, } .into()); } let filter_types: Vec = type_option .as_list() .map(|l| { l.iter() .filter_map(|v| v.as_string().map(|s| s.to_string())) .collect() }) .unwrap_or_default(); for package in local_repo.get_canonical_packages()? { if filter_types.contains(&package.get_type()) { package_names_to_reinstall.push(package.get_name()); packages_to_reinstall.push(package); } } } else { if packages_count == 0 { return Err(InvalidArgumentException { message: "You must pass one or more package names to be reinstalled." .to_string(), code: 0, } .into()); } let patterns: Vec = packages_arg .as_list() .map(|l| { l.iter() .filter_map(|v| v.as_string().map(|s| s.to_string())) .collect() }) .unwrap_or_default(); for pattern in &patterns { let pattern_regexp = base_package::package_name_to_regexp(pattern); let mut matched = false; for package in local_repo.get_canonical_packages()? { if Preg::is_match(&pattern_regexp, &package.get_name()) { matched = true; package_names_to_reinstall.push(package.get_name()); packages_to_reinstall.push(package); } } if !matched { io.write_error(&format!("Pattern \"{}\" does not match any currently installed packages.", pattern)); } } } if packages_to_reinstall.is_empty() { io.write_error("Found no packages to reinstall, aborting."); return Ok(1); } let mut uninstall_operations: Vec = vec![]; for package in packages_to_reinstall { uninstall_operations.push(UninstallOperation::new(package)); } let present_packages = local_repo.get_packages()?; let result_packages: Vec = present_packages.to_vec(); let present_packages: Vec = present_packages .into_iter() .filter(|package| !package_names_to_reinstall.contains(&package.get_name())) .collect(); let transaction = Transaction::new(present_packages, result_packages); let install_operations = transaction.get_operations(); let mut install_order = indexmap::IndexMap::new(); for (index, op) in install_operations.iter().enumerate() { if let Some(install_op) = op.as_any().downcast_ref::() && install_op.get_package().as_alias().is_none() { install_order.insert(install_op.get_package().get_name(), index); } } uninstall_operations.sort_by(|a, b| { let a_order = install_order .get(&a.get_package().get_name()) .copied() .unwrap_or(0); let b_order = install_order .get(&b.get_package().get_name()) .copied() .unwrap_or(0); b_order.cmp(&a_order) }); // TODO(plugin): dispatch CommandEvent let command_event = CommandEvent::new(PluginEvents::COMMAND, "reinstall", input.clone(), output); let event_dispatcher = composer.get_event_dispatcher(); event_dispatcher .borrow_mut() .dispatch(Some(command_event.get_name()), None); let config = composer.get_config(); let (prefer_source, prefer_dist) = self.get_preferred_install_options(&config.borrow(), input.clone(), false)?; let installation_manager = composer.get_installation_manager().clone(); let download_manager = composer.get_download_manager(); let package = composer.get_package(); installation_manager.borrow_mut().set_output_progress( !input .borrow() .get_option("no-progress")? .as_bool() .unwrap_or(false), ); if input .borrow() .get_option("no-plugins")? .as_bool() .unwrap_or(false) { installation_manager.borrow_mut().disable_plugins(); } download_manager .borrow_mut() .set_prefer_source(prefer_source); download_manager.borrow_mut().set_prefer_dist(prefer_dist); let dev_mode = local_repo.get_dev_mode().unwrap_or(true); Platform::put_env("COMPOSER_DEV_MODE", if dev_mode { "1" } else { "0" }); event_dispatcher.borrow_mut().dispatch_script( ScriptEvents::PRE_INSTALL_CMD, dev_mode, vec![], indexmap::IndexMap::new(), ); // PHP: $installationManager->execute($localRepo, $uninstallOperations, $devMode); // $installationManager->execute($localRepo, $installOperations, $devMode); // TODO(phase-c): two blockers. (1) execute() wants `&mut dyn InstalledRepositoryInterface`, // but local_repo is a RepositoryInterfaceHandle that exposes no raw &mut // InstalledRepositoryInterface view (only per-method helpers). (2) InstallationManager:: // execute is itself deferred (its operation/promise machinery stays todo!() — see // installation_manager.rs). let _ = ( uninstall_operations, install_operations, dev_mode, local_repo.clone(), &installation_manager, ); // installation_manager.execute(local_repo_mut, uninstall_ops_boxed, dev_mode, true, false); // installation_manager.execute(local_repo_mut, install_ops_boxed, dev_mode, true, false); if !input .borrow() .get_option("no-autoloader")? .as_bool() .unwrap_or(false) { let optimize = input .borrow() .get_option("optimize-autoloader")? .as_bool() .unwrap_or(false) || config .borrow_mut() .get("optimize-autoloader") .as_bool() .unwrap_or(false); let authoritative = input .borrow() .get_option("classmap-authoritative")? .as_bool() .unwrap_or(false) || config .borrow_mut() .get("classmap-authoritative") .as_bool() .unwrap_or(false); let apcu_prefix = input .borrow() .get_option("apcu-autoloader-prefix")? .as_string() .map(|s| s.to_string()); let apcu = apcu_prefix.is_some() || input .borrow() .get_option("apcu-autoloader")? .as_bool() .unwrap_or(false) || config .borrow_mut() .get("apcu-autoloader") .as_bool() .unwrap_or(false); // PHP: $generator = $composer->getAutoloadGenerator(); $generator->setClassMapAuthoritative(...); // $generator->setApcu(...); $generator->setPlatformRequirementFilter(...); // $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); // TODO(phase-c): AutoloadGenerator::dump (and the setters) take &mut self and dump wants // `local_repo: &mut dyn InstalledRepositoryInterface`, which the RepositoryInterfaceHandle // does not expose as a raw &mut view (the same handle blocker as execute above). Wiring // this needs that accessor plus completing the dump call's remaining arguments. let _ = ( authoritative, apcu, apcu_prefix.clone(), self.get_platform_requirement_filter(input)?, optimize, &*config.borrow(), local_repo, package, installation_manager, ); // composer.get_autoload_generator_mut().set_class_map_authoritative(authoritative); // composer.get_autoload_generator_mut().set_apcu(apcu, apcu_prefix.clone()); // composer.get_autoload_generator_mut().set_platform_requirement_filter(...); // composer.get_autoload_generator_mut().dump(...); } event_dispatcher.borrow_mut().dispatch_script( ScriptEvents::POST_INSTALL_CMD, dev_mode, vec![], indexmap::IndexMap::new(), ); Ok(0) } fn initialize( &self, input: Rc>, output: Rc>, ) -> anyhow::Result<()> { base_command_initialize(self, input, output) } shirabe_external_packages::delegate_command_trait_impls_to_inner!(base_command_data); } impl BaseCommand for ReinstallCommand { fn command_data( &self, ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { self.base_command_data.command_data() } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); }