Move all interactive input to construct

This commit is contained in:
crazywhalecc 2026-03-09 11:04:18 +08:00
parent 1f768ffc64
commit 77e129881a
No known key found for this signature in database
GPG Key ID: 1F4BDD59391F2680
11 changed files with 96 additions and 61 deletions

View File

@ -443,7 +443,27 @@ trait unix
$package->runStage([$this, 'makeForUnix']);
$package->runStage([$this, 'unixBuildSharedExt']);
$package->runStage([$this, 'smokeTestForUnix']);
}
#[Stage('postInstall')]
public function postInstall(TargetPackage $package, PackageInstaller $installer): void
{
if ($package->getName() === 'frankenphp') {
$package->runStage([$this, 'smokeTestFrankenphpForUnix']);
return;
}
if ($package->getName() !== 'php') {
return;
}
if (SystemTarget::isUnix()) {
if ($installer->interactive) {
InteractiveTerm::indicateProgress('Running PHP smoke tests');
}
$package->runStage([$this, 'smokeTestForUnix']);
if ($installer->interactive) {
InteractiveTerm::finish('PHP smoke tests passed');
}
}
}
/**

View File

@ -106,7 +106,7 @@ class ArtifactDownloader
* no-shallow-clone?: bool
* } $options Downloader options
*/
public function __construct(protected array $options = [])
public function __construct(protected array $options = [], public readonly bool $interactive = true)
{
// Allow setting concurrency via options
$this->parallel = max(1, (int) ($options['parallel'] ?? 1));
@ -273,12 +273,10 @@ class ArtifactDownloader
/**
* Download all artifacts, with optional parallel processing.
*
* @param bool $interactive Enable interactive mode with Ctrl+C handling
*/
public function download(bool $interactive = true): void
public function download(): void
{
if ($interactive) {
if ($this->interactive) {
Shell::passthruCallback(function () {
InteractiveTerm::advance();
});
@ -311,7 +309,7 @@ class ArtifactDownloader
$count = count($this->artifacts);
$artifacts_str = implode(',', array_map(fn ($x) => '' . ConsoleColor::yellow($x->getName()), $this->artifacts));
// mute the first line if not interactive
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::notice("Downloading {$count} artifacts: {$artifacts_str} ...");
}
try {
@ -329,19 +327,19 @@ class ArtifactDownloader
$skipped = [];
foreach ($this->artifacts as $artifact) {
++$current;
if ($this->downloadWithType($artifact, $current, $count, interactive: $interactive) === SPC_DOWNLOAD_STATUS_SKIPPED) {
if ($this->downloadWithType($artifact, $current, $count) === SPC_DOWNLOAD_STATUS_SKIPPED) {
$skipped[] = $artifact->getName();
continue;
}
$this->_before_files = FileSystem::scanDirFiles(DOWNLOAD_PATH, false, true, true) ?: [];
}
if ($interactive) {
if ($this->interactive) {
$skip_msg = !empty($skipped) ? ' (Skipped ' . count($skipped) . ' artifacts for being already downloaded)' : '';
InteractiveTerm::success("Downloaded all {$count} artifacts.{$skip_msg}\n", true);
}
}
} finally {
if ($interactive) {
if ($this->interactive) {
Shell::passthruCallback(null);
keyboard_interrupt_unregister();
}
@ -537,7 +535,7 @@ class ArtifactDownloader
return $dl->checkUpdate($artifact_name, $platform_config, null, $this);
}
private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false, bool $interactive = true): int
private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false): int
{
$queue = $this->generateQueue($artifact);
// already downloaded
@ -558,7 +556,7 @@ class ArtifactDownloader
};
$try_h = $try ? 'Try downloading' : 'Downloading';
logger()->info("{$try_h} artifact '{$artifact->getName()}' {$item['display']} ...");
if ($parallel === false && $interactive) {
if ($parallel === false && $this->interactive) {
InteractiveTerm::indicateProgress("[{$current}/{$total}] Downloading artifact " . ConsoleColor::green($artifact->getName()) . " {$item['display']} from {$type_display_name} ...");
}
// is valid download type
@ -597,13 +595,13 @@ class ArtifactDownloader
}
// process lock
ApplicationContext::get(ArtifactCache::class)->lock($artifact, $item['lock'], $lock, SystemTarget::getCurrentPlatformString());
if ($parallel === false && $interactive) {
if ($parallel === false && $this->interactive) {
$ver = $lock->hasVersion() ? (' (' . ConsoleColor::yellow($lock->version) . ')') : '';
InteractiveTerm::finish('Downloaded ' . ($verified ? 'and verified ' : '') . 'artifact ' . ConsoleColor::green($artifact->getName()) . $ver . " {$item['display']} .");
}
return SPC_DOWNLOAD_STATUS_SUCCESS;
} catch (DownloaderException|ExecutionException $e) {
if ($parallel === false && $interactive) {
if ($parallel === false && $this->interactive) {
InteractiveTerm::finish("Download artifact {$artifact->getName()} {$item['display']} failed !", false);
InteractiveTerm::error("Failed message: {$e->getMessage()}", true);
}

View File

@ -34,9 +34,9 @@ class InstallPackageCommand extends BaseCommand
public function handle(): int
{
ApplicationContext::set('elephant', true);
$installer = new PackageInstaller([...$this->input->getOptions(), 'dl-prefer-binary' => true]);
$installer = new PackageInstaller([...$this->input->getOptions(), 'dl-prefer-binary' => true], false);
$installer->addInstallPackage($this->input->getArgument('package'));
$installer->run(true, true);
$installer->run(true);
return static::SUCCESS;
}
}

View File

@ -16,6 +16,7 @@ class ConfigValidator
public const array PACKAGE_FIELD_TYPES = [
// package fields
'type' => ConfigType::STRING,
'description' => ConfigType::STRING,
'depends' => ConfigType::LIST_ARRAY, // @
'suggests' => ConfigType::LIST_ARRAY, // @
'artifact' => [self::class, 'validateArtifactField'], // STRING or OBJECT
@ -43,6 +44,7 @@ class ConfigValidator
public const array PACKAGE_FIELDS = [
'type' => true,
'description' => false,
'depends' => false, // @
'suggests' => false, // @
'artifact' => false,

View File

@ -18,7 +18,7 @@ use function Laravel\Prompts\confirm;
readonly class Doctor
{
public function __construct(private ?OutputInterface $output = null, private int $auto_fix = FIX_POLICY_PROMPT)
public function __construct(private ?OutputInterface $output = null, private int $auto_fix = FIX_POLICY_PROMPT, public readonly bool $interactive = true)
{
// debug shows all loaded doctor items
$items = DoctorLoader::getDoctorItems();
@ -53,13 +53,13 @@ readonly class Doctor
* Check all valid check items.
* @return bool true if all checks passed, false otherwise
*/
public function checkAll(bool $interactive = true): bool
public function checkAll(): bool
{
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::notice('Starting doctor checks ...');
}
foreach ($this->getValidCheckList() as $check) {
if (!$this->checkItem($check, $interactive)) {
if (!$this->checkItem($check)) {
return false;
}
}
@ -72,7 +72,7 @@ readonly class Doctor
* @param CheckItem|string $check The check item to be checked
* @return bool True if the check passed or was fixed, false otherwise
*/
public function checkItem(CheckItem|string $check, bool $interactive = true): bool
public function checkItem(CheckItem|string $check): bool
{
if (is_string($check)) {
$found = null;
@ -88,7 +88,7 @@ readonly class Doctor
}
$check = $found;
}
$prepend = $interactive ? ' - ' : '';
$prepend = $this->interactive ? ' - ' : '';
$this->output?->write("{$prepend}Checking <comment>{$check->item_name}</comment> ... ");
// call check

View File

@ -59,8 +59,8 @@ class LinuxMuslCheck
#[FixItem('fix-musl-wrapper')]
public function fixMusl(): bool
{
$downloader = new ArtifactDownloader();
$downloader->add('musl-wrapper')->download(false);
$downloader = new ArtifactDownloader(interactive: false);
$downloader->add('musl-wrapper')->download();
$extractor = new ArtifactExtractor(ApplicationContext::get(ArtifactCache::class));
$extractor->extract('musl-wrapper');
@ -96,9 +96,9 @@ class LinuxMuslCheck
Shell::passthruCallback(function () {
InteractiveTerm::advance();
});
$downloader = new ArtifactDownloader();
$downloader = new ArtifactDownloader(interactive: false);
$extractor = new ArtifactExtractor(ApplicationContext::get(ArtifactCache::class));
$downloader->add('musl-toolchain')->download(false);
$downloader->add('musl-toolchain')->download();
$extractor->extract('musl-toolchain');
$pkg_root = PKG_ROOT_PATH . '/musl-toolchain';
f_passthru("{$prefix}cp -rf {$pkg_root}/* /usr/local/musl");

View File

@ -45,9 +45,9 @@ class PkgConfigCheck
public function fix(): bool
{
ApplicationContext::set('elephant', true);
$installer = new PackageInstaller(['dl-binary-only' => true]);
$installer = new PackageInstaller(['dl-binary-only' => true], interactive: false);
$installer->addInstallPackage('pkg-config');
$installer->run(false, true);
$installer->run(true);
return true;
}
}

View File

@ -30,9 +30,9 @@ class Re2cVersionCheck
#[FixItem('build-re2c')]
public function buildRe2c(): bool
{
$installer = new PackageInstaller();
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('re2c');
$installer->run(false);
$installer->run(true);
return true;
}
}

View File

@ -107,7 +107,7 @@ class WindowsToolCheck
{
$installer = new PackageInstaller();
$installer->addInstallPackage('strawberry-perl');
$installer->run(false);
$installer->run(true);
GlobalEnvManager::addPathIfNotExists(PKG_ROOT_PATH . '\strawberry-perl');
return true;
}
@ -116,27 +116,27 @@ class WindowsToolCheck
public function installSDK(): bool
{
FileSystem::removeDir(getenv('PHP_SDK_PATH'));
$installer = new PackageInstaller();
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('php-sdk-binary-tools');
$installer->run(false);
$installer->run(true);
return true;
}
#[FixItem('install-nasm')]
public function installNasm(): bool
{
$installer = new PackageInstaller();
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('nasm');
$installer->run(false);
$installer->run(true);
return true;
}
#[FixItem('install-vswhere')]
public function installVSWhere(): bool
{
$installer = new PackageInstaller();
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('vswhere');
$installer->run(false);
$installer->run(true);
return true;
}
}

View File

@ -34,9 +34,9 @@ class ZigCheck
#[FixItem('install-zig')]
public function installZig(): bool
{
$installer = new PackageInstaller();
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('zig');
$installer->run(false);
$installer->run(true);
return $installer->isPackageInstalled('zig');
}
}

View File

@ -46,7 +46,7 @@ class PackageInstaller
/** @var null|BuildRootTracker buildroot file tracker for debugging purpose */
protected ?BuildRootTracker $tracker = null;
public function __construct(protected array $options = [])
public function __construct(protected array $options = [], public readonly bool $interactive = true)
{
ApplicationContext::set(PackageInstaller::class, $this);
$builder = new PackageBuilder($options);
@ -143,7 +143,7 @@ class PackageInstaller
/**
* Run the package installation process.
*/
public function run(bool $interactive = true, bool $disable_delay_msg = false): void
public function run(bool $disable_delay_msg = false): void
{
// apply build toolchain envs
GlobalEnvManager::afterInit();
@ -153,7 +153,7 @@ class PackageInstaller
$this->resolvePackages();
}
if ($interactive && !$disable_delay_msg) {
if ($this->interactive && !$disable_delay_msg) {
// show install or build options in terminal with beautiful output
$this->printInstallerInfo();
@ -167,14 +167,17 @@ class PackageInstaller
// check download
if ($this->download) {
$downloaderOptions = DownloaderOptions::extractFromConsoleOptions($this->options, 'dl');
$downloader = new ArtifactDownloader([...$downloaderOptions, 'source-only' => implode(',', array_map(fn ($x) => $x->getName(), $this->build_packages))]);
$downloader->addArtifacts($this->getArtifacts())->download($interactive);
$downloader = new ArtifactDownloader(
[...$downloaderOptions, 'source-only' => implode(',', array_map(fn ($x) => $x->getName(), $this->build_packages))],
$this->interactive
);
$downloader->addArtifacts($this->getArtifacts())->download();
} else {
logger()->notice('Skipping download (--no-download option enabled)');
}
// extract sources
$this->extractSourceArtifacts(interactive: $interactive);
$this->extractSourceArtifacts();
// validate packages
foreach ($this->packages as $package) {
@ -183,7 +186,7 @@ class PackageInstaller
}
// build/install packages
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::notice('Building/Installing packages ...');
keyboard_interrupt_register(function () {
InteractiveTerm::finish('Build/Install process interrupted by user!', false);
@ -198,7 +201,7 @@ class PackageInstaller
$has_source = $package->hasSource();
if (!$is_to_build && $should_use_binary) {
// install binary
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::indicateProgress('Installing package: ' . ConsoleColor::yellow($package->getName()));
}
try {
@ -210,17 +213,17 @@ class PackageInstaller
} catch (\Throwable $e) {
// Stop tracking on error
$this->tracker?->stopTracking();
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::finish('Installing binary package failed: ' . ConsoleColor::red($package->getName()), false);
echo PHP_EOL;
}
throw $e;
}
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::finish('Installed binary package: ' . ConsoleColor::green($package->getName()) . ($status === SPC_STATUS_ALREADY_INSTALLED ? ' (already installed, skipped)' : ''));
}
} elseif ($is_to_build && $has_build_stage || $has_source && $has_build_stage) {
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::indicateProgress('Building package: ' . ConsoleColor::yellow($package->getName()));
}
try {
@ -243,22 +246,20 @@ class PackageInstaller
} catch (\Throwable $e) {
// Stop tracking on error
$this->tracker?->stopTracking();
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::finish('Building package failed: ' . ConsoleColor::red($package->getName()), false);
echo PHP_EOL;
}
throw $e;
}
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::finish('Built package: ' . ConsoleColor::green($package->getName()) . ($status === SPC_STATUS_ALREADY_BUILT ? ' (already built, skipped)' : ''));
}
}
}
$this->dumpLicenseFiles($this->packages);
if ($interactive) {
InteractiveTerm::success('Exported package licenses', true);
}
// perform after-install actions and emit post-install events
$this->emitPostInstallEvents();
}
public function isBuildPackage(Package|string $package): bool
@ -311,6 +312,17 @@ class PackageInstaller
return false;
}
public function emitPostInstallEvents(): void
{
foreach ($this->packages as $package) {
if ($package->hasStage('postInstall')) {
$package->runStage('postInstall');
}
}
$this->dumpLicenseFiles($this->packages);
}
/**
* Returns the download status of all artifacts for the resolved packages.
*
@ -368,7 +380,7 @@ class PackageInstaller
/**
* Extract all artifacts for resolved packages.
*/
public function extractSourceArtifacts(bool $interactive = true): void
public function extractSourceArtifacts(): void
{
FileSystem::createDir(SOURCE_PATH);
$packages = array_values($this->packages);
@ -403,7 +415,7 @@ class PackageInstaller
}
// Extract each artifact
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::notice('Extracting source for ' . count($artifacts) . ' artifacts: ' . implode(',', array_map(fn ($x) => ConsoleColor::yellow($x->getName()), $artifacts)) . ' ...');
InteractiveTerm::indicateProgress('Extracting artifacts');
}
@ -411,7 +423,7 @@ class PackageInstaller
try {
V2CompatLayer::beforeExtsExtractHook();
foreach ($artifacts as $artifact) {
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::setMessage('Extracting source: ' . ConsoleColor::green($artifact->getName()));
}
if (($pkg = array_search($artifact->getName(), $pkg_artifact_map, true)) !== false) {
@ -423,12 +435,12 @@ class PackageInstaller
}
}
V2CompatLayer::afterExtsExtractHook();
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::finish('Extracted all sources successfully.');
echo PHP_EOL;
}
} catch (\Throwable $e) {
if ($interactive) {
if ($this->interactive) {
InteractiveTerm::finish('Artifact extraction failed!', false);
echo PHP_EOL;
}
@ -525,6 +537,9 @@ class PackageInstaller
}
}
$dumper->dump(BUILD_ROOT_PATH . '/license');
if ($this->interactive) {
InteractiveTerm::success('Exported package licenses', true);
}
}
/**