addArgument('craft', null, 'Path to craft.yml file', WORKING_DIR . '/craft.yml'); $this->addOption('pgi', null, null, 'Forward --pgi to the inner build (instrumented binaries).'); $this->addOption('cs-pgi', null, null, 'Forward --cs-pgi to the inner build (cs-instrumented binaries).'); $this->addOption('pgo', null, null, 'Forward --pgo to the inner build (use collected profile data).'); $this->addOption('libs-only', null, null, 'Build only the libraries needed by the configured exts (skip PHP and extension build).'); } public function handle(): int { $craft_file = $this->getArgument('craft'); // Check if the craft.yml file exists if (!file_exists($craft_file)) { $this->output->writeln('craft.yml not found, please create one!'); return static::FAILURE; } // Check if the craft.yml file is valid try { $craft = ConfigValidator::validateAndParseCraftFile($craft_file, $this); if ($craft['debug']) { $this->input->setOption('debug', true); } } catch (ValidationException $e) { $this->output->writeln('craft.yml parse error: ' . $e->getMessage() . ''); return static::FAILURE; } // Craft!!! $this->output->writeln('Crafting...'); // apply env if (isset($craft['extra-env'])) { $env = $craft['extra-env']; foreach ($env as $key => $val) { f_putenv("{$key}={$val}"); } } $static_extensions = implode(',', $craft['extensions']); $shared_extensions = implode(',', $craft['shared-extensions'] ?? []); $libs = implode(',', $craft['libs']); // craft doctor if ($craft['craft-options']['doctor']) { $retcode = $this->runCommand('doctor', '--auto-fix'); if ($retcode !== 0) { $this->output->writeln('craft doctor failed'); return static::FAILURE; } } // install go and xcaddy for frankenphp if (in_array('frankenphp', $craft['sapi']) && !GoXcaddy::isInstalled()) { $retcode = $this->runCommand('install-pkg', 'go-xcaddy'); if ($retcode !== 0) { $this->output->writeln('craft go-xcaddy failed'); return static::FAILURE; } } // install zig if requested if (ToolchainManager::getToolchainClass() === ZigToolchain::class && !Zig::isInstalled()) { $retcode = $this->runCommand('install-pkg', 'zig'); if ($retcode !== 0) { $this->output->writeln('craft zig failed'); return static::FAILURE; } } // craft download if ($craft['craft-options']['download']) { $sharedAppend = $shared_extensions ? ',' . $shared_extensions : ''; $args = ["--for-extensions={$static_extensions}{$sharedAppend}"]; if ($craft['libs'] !== []) { $args[] = "--for-libs={$libs}"; } if (isset($craft['php-version'])) { $args[] = '--with-php=' . $craft['php-version']; if (!array_key_exists('ignore-cache-sources', $craft['download-options'])) { $craft['download-options']['ignore-cache-sources'] = 'php-src'; } } $this->optionsToArguments($craft['download-options'], $args); $retcode = $this->runCommand('download', ...$args); if ($retcode !== 0) { $this->output->writeln('craft download failed'); return static::FAILURE; } } // craft build if ($craft['craft-options']['build']) { if ($this->getOption('libs-only')) { $exts = array_merge($craft['extensions'], $craft['shared-extensions'] ?? []); $include_suggested_exts = (bool) ($craft['build-options']['with-suggested-exts'] ?? false); $include_suggested_libs = (bool) ($craft['build-options']['with-suggested-libs'] ?? false); [, $needed_libs] = DependencyUtil::getExtsAndLibs( $exts, $craft['libs'], $include_suggested_exts, $include_suggested_libs, ); if ($needed_libs === []) { $this->output->writeln('No libs needed for the configured extensions; skipping build:libs.'); } else { $retcode = $this->runCommand('build:libs', implode(',', $needed_libs)); if ($retcode !== 0) { $this->output->writeln('craft build:libs failed'); return static::FAILURE; } } } else { $args = [$static_extensions, "--with-libs={$libs}", "--build-shared={$shared_extensions}", ...array_map(fn ($x) => "--build-{$x}", $craft['sapi'])]; $this->optionsToArguments($craft['build-options'], $args); foreach (['pgi', 'cs-pgi', 'pgo'] as $pgoFlag) { if ($this->getOption($pgoFlag)) { $args[] = "--{$pgoFlag}"; } } $retcode = $this->runCommand('build', ...$args); if ($retcode !== 0) { $this->output->writeln('craft build failed'); return static::FAILURE; } } } return 0; } public function processLogCallback($type, $buffer): void { if ($type === Process::ERR) { fwrite(STDERR, $buffer); } else { fwrite(STDOUT, $buffer); } } private function runCommand(string $cmd, ...$args): int { global $argv; if ($this->getOption('debug')) { array_unshift($args, '--debug'); } array_unshift($args, '--preserve-log'); $prefix = PHP_SAPI === 'cli' ? [PHP_BINARY, $argv[0]] : [$argv[0]]; $env = getenv(); $process = new Process([...$prefix, $cmd, '--no-motd', ...$args], env: $env, timeout: null); if (PHP_OS_FAMILY === 'Windows') { sapi_windows_set_ctrl_handler(function () use ($process) { if ($process->isRunning()) { $process->signal(-1073741510); } }); } elseif (extension_loaded('pcntl')) { pcntl_signal(SIGINT, function () use ($process) { /* @noinspection PhpComposerExtensionStubsInspection */ $process->signal(SIGINT); }); } else { logger()->debug('You have not enabled `pcntl` extension, cannot prevent download file corruption when Ctrl+C'); } // $process->setTty(true); $process->run([$this, 'processLogCallback']); return $process->getExitCode(); } private function optionsToArguments(array $options, array &$args): void { foreach ($options as $option => $val) { if ((is_bool($val) && $val) || $val === null) { $args[] = "--{$option}"; continue; } if (is_string($val)) { $args[] = "--{$option}={$val}"; continue; } if (is_array($val)) { foreach ($val as $v) { if (is_string($v)) { $args[] = "--{$option}={$v}"; } } } } } }