//! ref: composer/src/Composer/Command/CheckPlatformReqsCommand.php
use anyhow::Result;
use indexmap::IndexMap;
use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface;
use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface;
use shirabe_php_shim::{PhpMixed, strip_tags};
use shirabe_semver::constraint::constraint::Constraint;
use shirabe_semver::constraint::constraint_interface::ConstraintInterface;
use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData};
use crate::composer::Composer;
use crate::console::input::input_option::InputOption;
use crate::io::io_interface::IOInterface;
use crate::json::json_file::JsonFile;
use crate::package::link::Link;
use crate::repository::installed_repository::InstalledRepository;
use crate::repository::platform_repository::PlatformRepository;
use crate::repository::repository_interface::RepositoryInterface;
use crate::repository::root_package_repository::RootPackageRepository;
struct CheckResult {
platform_package: String,
version: String,
link: Option,
status: String,
provider: String,
}
#[derive(Debug)]
pub struct CheckPlatformReqsCommand {
base_command_data: BaseCommandData,
}
impl CheckPlatformReqsCommand {
pub fn configure(&mut self) {
self
.set_name("check-platform-reqs")
.set_description("Check that platform requirements are satisfied")
.set_definition(&[
InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables checking of require-dev packages requirements.", None).unwrap().into(),
InputOption::new("lock", None, Some(InputOption::VALUE_NONE), "Checks requirements only from the lock file, not from installed packages.", None).unwrap().into(),
InputOption::new("format", Some(shirabe_php_shim::PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(shirabe_php_shim::PhpMixed::String("text".to_string()))).unwrap().into(),
])
.set_help(
"Checks that your PHP and extensions versions match the platform requirements of the installed packages.\n\n\
Unlike update/install, this command will ignore config.platform settings and check the real platform packages so you can be certain you have the required platform dependencies.\n\n\
php composer.phar check-platform-reqs\n\n"
);
}
pub fn execute(
&mut self,
input: &dyn InputInterface,
_output: &dyn OutputInterface,
) -> Result {
let mut composer = self.require_composer(None, None)?;
let io = self.get_io();
let no_dev = input.get_option("no-dev").as_bool().unwrap_or(false);
let mut requires: IndexMap> = IndexMap::new();
let mut remove_packages: Vec = vec![];
let installed_repo_base: Box<
dyn crate::repository::repository_interface::RepositoryInterface,
> = if input.get_option("lock").as_bool().unwrap_or(false) {
io.write_error(&format!(
"Checking {}platform requirements using the lock file",
if no_dev { "non-dev " } else { "" }
));
Box::new(composer.get_locker_mut().get_locked_repository(!no_dev)?)
} else {
let local_repo = composer.get_repository_manager().get_local_repository();
if local_repo.get_packages().is_empty() {
io.write_error(&format!(
"No vendor dir present, checking {}platform requirements from the lock file",
if no_dev { "non-dev " } else { "" }
));
Box::new(composer.get_locker_mut().get_locked_repository(!no_dev)?)
as Box
} else {
if no_dev {
remove_packages = local_repo.get_dev_package_names().clone();
}
io.write_error(&format!(
"Checking {}platform requirements for packages in the vendor dir",
if no_dev { "non-dev " } else { "" }
));
local_repo.clone_box()
}
};
if !no_dev {
for (require, link) in composer.get_package().get_dev_requires() {
requires
.entry(require.to_string())
.or_insert_with(Vec::new)
.push(link.clone());
}
}
let root_pkg_repo = RootPackageRepository::new(composer.get_package().clone_box());
let installed_repo =
InstalledRepository::new(vec![installed_repo_base, Box::new(root_pkg_repo)]);
for package in installed_repo.get_packages() {
if remove_packages.contains(&package.get_name().to_string()) {
continue;
}
for (require, link) in package.get_requires() {
requires
.entry(require.to_string())
.or_insert_with(Vec::new)
.push(link.clone());
}
}
let mut requires_sorted: Vec<(String, Vec)> = requires.into_iter().collect();
requires_sorted.sort_by(|a, b| a.0.cmp(&b.0));
let installed_repo_with_platform = InstalledRepository::new(vec![
Box::new(installed_repo),
Box::new(PlatformRepository::new(vec![], indexmap::IndexMap::new())?),
]);
let mut results: Vec = vec![];
let mut exit_code = 0;
'requirements: for (require, links) in &requires_sorted {
if PlatformRepository::is_platform_package(require) {
let candidates = installed_repo_with_platform
.find_packages_with_replacers_and_providers(require, None);
if !candidates.is_empty() {
let mut req_results: Vec = vec![];
'candidates: for candidate in &candidates {
let candidate_constraint: Option> =
if candidate.get_name() == require {
let mut c = Constraint::new("=", candidate.get_version());
c.set_pretty_string(Some(
candidate.get_pretty_version().to_string(),
));
Some(Box::new(c))
} else {
let mut found: Option> = None;
for (_, link) in candidate
.get_provides()
.iter()
.chain(candidate.get_replaces().iter())
{
if link.get_target() == require {
found = Some(link.get_constraint().clone_box());
break;
}
}
found
};
let candidate_constraint = match candidate_constraint {
Some(c) => c,
None => continue,
};
for link in links {
if !link.get_constraint().matches(&*candidate_constraint) {
req_results.push(CheckResult {
platform_package: if candidate.get_name() == require {
candidate.get_pretty_name().to_string()
} else {
require.clone()
},
version: candidate_constraint.get_pretty_string().to_string(),
link: Some(link.clone()),
status: "failed".to_string(),
provider: if candidate.get_name() == require {
String::new()
} else {
format!(
"provided by {}",
candidate.get_pretty_name()
)
},
});
continue 'candidates;
}
}
results.push(CheckResult {
platform_package: if candidate.get_name() == require {
candidate.get_pretty_name().to_string()
} else {
require.clone()
},
version: candidate_constraint.get_pretty_string().to_string(),
link: None,
status: "success".to_string(),
provider: if candidate.get_name() == require {
String::new()
} else {
format!(
"provided by {}",
candidate.get_pretty_name()
)
},
});
continue 'requirements;
}
results.extend(req_results);
exit_code = exit_code.max(1);
continue;
}
results.push(CheckResult {
platform_package: require.clone(),
version: "n/a".to_string(),
link: links.first().cloned(),
status: "missing".to_string(),
provider: String::new(),
});
exit_code = exit_code.max(2);
}
}
let format = input
.get_option("format")
.as_string()
.unwrap_or("text")
.to_string();
self.print_table(_output, &results, &format);
Ok(exit_code)
}
fn print_table(&mut self, output: &dyn OutputInterface, results: &[CheckResult], format: &str) {
let io = self.get_io();
if format == "json" {
let rows: Vec = results
.iter()
.map(|result| {
let mut row = IndexMap::new();
row.insert(
"name".to_string(),
Box::new(PhpMixed::String(result.platform_package.clone())),
);
row.insert(
"version".to_string(),
Box::new(PhpMixed::String(result.version.clone())),
);
row.insert(
"status".to_string(),
Box::new(PhpMixed::String(strip_tags(&result.status))),
);
if let Some(link) = &result.link {
let mut failed_req = IndexMap::new();
failed_req.insert(
"source".to_string(),
Box::new(PhpMixed::String(link.get_source().to_string())),
);
failed_req.insert(
"type".to_string(),
Box::new(PhpMixed::String(link.get_description().to_string())),
);
failed_req.insert(
"target".to_string(),
Box::new(PhpMixed::String(link.get_target().to_string())),
);
failed_req.insert(
"constraint".to_string(),
Box::new(PhpMixed::String(
link.get_pretty_constraint().unwrap_or("").to_string(),
)),
);
row.insert(
"failed_requirement".to_string(),
Box::new(PhpMixed::Array(failed_req)),
);
} else {
row.insert("failed_requirement".to_string(), Box::new(PhpMixed::Null));
}
let provider_str = strip_tags(&result.provider);
row.insert(
"provider".to_string(),
Box::new(if provider_str.is_empty() {
PhpMixed::Null
} else {
PhpMixed::String(provider_str)
}),
);
PhpMixed::Array(row)
})
.collect();
io.write(&JsonFile::encode(
&PhpMixed::List(rows.into_iter().map(Box::new).collect()),
448,
));
} else {
let rows: Vec = results
.iter()
.map(|result| {
PhpMixed::List(vec![
Box::new(PhpMixed::String(result.platform_package.clone())),
Box::new(PhpMixed::String(result.version.clone())),
Box::new(if let Some(link) = &result.link {
PhpMixed::String(format!(
"{} {} {} ({})",
link.get_source(),
link.get_description(),
link.get_target(),
link.get_pretty_constraint().unwrap_or(""),
))
} else {
PhpMixed::String(String::new())
}),
Box::new(PhpMixed::String(
format!("{} {}", result.status, result.provider)
.trim_end()
.to_string(),
)),
])
})
.collect();
self.render_table(rows, output);
}
}
}
impl HasBaseCommandData for CheckPlatformReqsCommand {
fn base_command_data(&self) -> &BaseCommandData {
&self.base_command_data
}
fn base_command_data_mut(&mut self) -> &mut BaseCommandData {
&mut self.base_command_data
}
}