//! 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 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);
}