devel_wizard-2.x-dev/src/Spell/SpellTraitPackageManager.php
src/Spell/SpellTraitPackageManager.php
<?php
declare(strict_types=1);
namespace Drupal\devel_wizard\Spell;
use Composer\InstalledVersions as ComposerInstalledVersions;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\Exception\UnknownExtensionException;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\devel_wizard\ShellProcessFactoryInterface;
use Symfony\Component\Filesystem\Path;
trait SpellTraitPackageManager {
protected ConfigFactoryInterface $configFactory;
protected ShellProcessFactoryInterface $shellProcessFactory;
protected ModuleInstallerInterface $moduleInstaller;
protected ModuleExtensionList $moduleList;
/**
* @return \Drupal\devel_wizard\Spell\SpellDefinition
*/
abstract public function getPluginDefinition();
/**
* @param string $mode
* Allowed values: "prod", "dev".
* @param array<string, string> $packages
* Key: package name.
* Value: version constraint.
*/
protected function installComposerPackages(string $mode, array $packages): array {
$packages = array_filter(
$packages,
function ($versionConstraint): bool {
return $versionConstraint !== NULL;
},
);
$missingPackages = array_diff_key(
$packages,
array_flip(ComposerInstalledVersions::getInstalledPackages()),
);
if (!$missingPackages) {
return [];
}
$settings = $this->configFactory->get('devel_wizard.settings');
$phpBinDir = $settings->get('php.bin_dir');
$phpExecutable = $settings->get('php.php_executable') ?: 'php';
if ($phpBinDir) {
$phpExecutable = Path::join($phpBinDir, $phpExecutable);
}
$command = [
$phpExecutable,
$settings->get('composer.composer_executable'),
'require',
];
if ($mode === 'dev') {
$command[] = '--dev';
}
foreach ($missingPackages as $package => $version) {
$command[] = rtrim("$package:$version", ':');
}
// @todo Detect project root.
$projectRoot = Path::makeAbsolute('..', getcwd());
$envVars = getenv();
// @todo Better ${PATH} handling.
// Without proper PATH Composer is not able to find the Git executable.
// Without Git executable composer is not able to manage packages correctly.
$envVars['PATH'] = explode(\PATH_SEPARATOR, $envVars['PATH'] ?? '');
$envVars['PATH'] = implode(
\PATH_SEPARATOR,
array_unique(array_filter(array_merge(
[
"{$envVars['HOME']}/bin",
$settings->get('php.bin_dir'),
'/usr/bin',
'/bin',
],
$envVars['PATH'],
))),
);
$process = $this->shellProcessFactory->createInstance($command, $projectRoot, $envVars);
$process->run();
$args = [
'@spell' => $this->getPluginDefinition()->id(),
'@projectRoot' => $projectRoot,
'@cwd' => getcwd(),
'@wd' => $process->getWorkingDirectory(),
'@command' => $process->getCommandLine(),
'@exitCode' => $process->getExitCode(),
'@stdOutput' => $process->getOutput(),
'@stdError' => $process->getErrorOutput(),
];
if ($process->getExitCode()) {
throw new \RuntimeException(
strtr(
'@spell - shell command failed:<hr />project root: @projectRoot<hr />cwd: @cwd<hr />wd: @wd<hr />command: @command<hr />exit code: @exitCode<hr />stdOutput: <pre>@stdOutput</pre><hr />stdError: <pre>@stdError</pre>',
$args,
),
);
}
return $missingPackages;
}
protected function ensureModuleInstalled(string $moduleName): static {
$installedModules = $this->moduleList->getAllInstalledInfo();
if (isset($installedModules[$moduleName])) {
$args = [
'@spell' => $this->getPluginDefinition()->id(),
'%module' => $moduleName,
];
$this->batchContext['sandbox']['messages'][] = [
'type' => MessengerInterface::TYPE_STATUS,
'message' => $this->t('@spell - module %module already installed', $args),
];
return $this;
}
$this->installModules([$moduleName => TRUE]);
return $this;
}
protected function installModules(array $moduleNames): bool {
$moduleNames = array_keys($moduleNames, TRUE, FALSE);
if (!$moduleNames) {
return TRUE;
}
try {
$this->moduleInstaller->install($moduleNames, TRUE);
return TRUE;
}
catch (\Exception $e) {
$this->messenger()->addError($e->getMessage());
}
return FALSE;
}
/**
* Use this function only for newly created modules.
*/
protected function getModulePath(string $moduleName): string {
try {
return $this->moduleList->getPath($moduleName);
}
catch (UnknownExtensionException $e) {
// Nothing to do.
}
$candidates = [
Path::join('modules', 'custom', $moduleName),
Path::join('modules', 'contrib', $moduleName),
];
foreach ($candidates as $candidate) {
if ($this->fs->exists("$candidate/$moduleName.info.yml")) {
return $candidate;
}
}
throw new UnknownExtensionException("The module '$moduleName' does not exist.");
}
protected function doItComposerRequire(): static {
$this->installComposerPackages('prod', $this->getRequiredPackagesProd());
$this->installComposerPackages('dev', $this->getRequiredPackagesDev());
return $this;
}
protected function doItModuleInstall(): static {
$moduleNames = array_keys($this->getRequiredModules(), TRUE, TRUE);
foreach ($moduleNames as $moduleName) {
$this->ensureModuleInstalled($moduleName);
}
return $this;
}
}
