Merge pull request #853 from crazywhalecc/fix/exception-reflection

Enhance exception handling by binding builder and extra info to exception handler
This commit is contained in:
Jerry Ma
2025-08-16 19:33:13 +08:00
committed by GitHub
8 changed files with 163 additions and 118 deletions

View File

@@ -82,7 +82,7 @@ jobs:
- zlib - zlib
- zstd - zstd
php-version: php-version:
- "git" - "8.5"
operating-system: operating-system:
- "ubuntu-latest" - "ubuntu-latest"
#- "macos-13" #- "macos-13"

View File

@@ -229,6 +229,9 @@ abstract class BuilderBase
*/ */
abstract public function testPHP(int $build_target = BUILD_TARGET_NONE); abstract public function testPHP(int $build_target = BUILD_TARGET_NONE);
/**
* Build shared extensions.
*/
public function buildSharedExts(): void public function buildSharedExts(): void
{ {
$lines = file(BUILD_BIN_PATH . '/php-config'); $lines = file(BUILD_BIN_PATH . '/php-config');

View File

@@ -8,6 +8,7 @@ use SPC\builder\freebsd\BSDBuilder;
use SPC\builder\linux\LinuxBuilder; use SPC\builder\linux\LinuxBuilder;
use SPC\builder\macos\MacOSBuilder; use SPC\builder\macos\MacOSBuilder;
use SPC\builder\windows\WindowsBuilder; use SPC\builder\windows\WindowsBuilder;
use SPC\exception\ExceptionHandler;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
@@ -29,6 +30,10 @@ class BuilderProvider
'BSD' => new BSDBuilder($input->getOptions()), 'BSD' => new BSDBuilder($input->getOptions()),
default => throw new WrongUsageException('Current OS "' . PHP_OS_FAMILY . '" is not supported yet'), default => throw new WrongUsageException('Current OS "' . PHP_OS_FAMILY . '" is not supported yet'),
}; };
// bind the builder to ExceptionHandler
ExceptionHandler::bindBuilder(self::$builder);
return self::$builder; return self::$builder;
} }

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace SPC\command; namespace SPC\command;
use SPC\builder\BuilderProvider; use SPC\builder\BuilderProvider;
use SPC\exception\SPCException; use SPC\exception\ExceptionHandler;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
@@ -165,8 +165,9 @@ class BuildPHPCommand extends BuildCommand
} }
$this->printFormatInfo($this->getDefinedEnvs(), true); $this->printFormatInfo($this->getDefinedEnvs(), true);
$this->printFormatInfo($indent_texts); $this->printFormatInfo($indent_texts);
// bind extra info to SPCException
SPCException::bindBuildPHPExtraInfo($indent_texts); // bind extra info to exception handler
ExceptionHandler::bindBuildPhpExtraInfo($indent_texts);
logger()->notice('Build will start after 2s ...'); logger()->notice('Build will start after 2s ...');
sleep(2); sleep(2);

View File

@@ -4,6 +4,11 @@ declare(strict_types=1);
namespace SPC\exception; namespace SPC\exception;
use SPC\builder\BuilderBase;
use SPC\builder\freebsd\BSDBuilder;
use SPC\builder\linux\LinuxBuilder;
use SPC\builder\macos\MacOSBuilder;
use SPC\builder\windows\WindowsBuilder;
use ZM\Logger\ConsoleColor; use ZM\Logger\ConsoleColor;
class ExceptionHandler class ExceptionHandler
@@ -26,21 +31,27 @@ class ExceptionHandler
WrongUsageException::class, WrongUsageException::class,
]; ];
/** @var null|BuilderBase Builder binding */
private static ?BuilderBase $builder = null;
/** @var array<string, mixed> Build PHP extra info binding */
private static array $build_php_extra_info = [];
public static function handleSPCException(SPCException $e): void public static function handleSPCException(SPCException $e): void
{ {
// XXX error: yyy // XXX error: yyy
$head_msg = match ($class = get_class($e)) { $head_msg = match ($class = get_class($e)) {
BuildFailureException::class => "Build failed: {$e->getMessage()}", BuildFailureException::class => "Build failed: {$e->getMessage()}",
DownloaderException::class => "Download failed: {$e->getMessage()}", DownloaderException::class => "Download failed: {$e->getMessage()}",
EnvironmentException::class => "Environment check failed: {$e->getMessage()}", EnvironmentException::class => "Environment check failed: {$e->getMessage()}",
ExecutionException::class => "Command execution failed: {$e->getMessage()}", ExecutionException::class => "Command execution failed: {$e->getMessage()}",
FileSystemException::class => "File system error: {$e->getMessage()}", FileSystemException::class => "File system error: {$e->getMessage()}",
InterruptException::class => "⚠ Build interrupted by user: {$e->getMessage()}", InterruptException::class => "⚠ Build interrupted by user: {$e->getMessage()}",
PatchException::class => "Patch apply failed: {$e->getMessage()}", PatchException::class => "Patch apply failed: {$e->getMessage()}",
SPCInternalException::class => "SPC internal error: {$e->getMessage()}", SPCInternalException::class => "SPC internal error: {$e->getMessage()}",
ValidationException::class => "Validation failed: {$e->getMessage()}", ValidationException::class => "Validation failed: {$e->getMessage()}",
WrongUsageException::class => $e->getMessage(), WrongUsageException::class => $e->getMessage(),
default => "Unknown SPC exception {$class}: {$e->getMessage()}", default => "Unknown SPC exception {$class}: {$e->getMessage()}",
}; };
self::logError($head_msg); self::logError($head_msg);
@@ -54,25 +65,32 @@ class ExceptionHandler
self::logError("----------------------------------------\n"); self::logError("----------------------------------------\n");
// get the SPCException module // get the SPCException module
if ($php_info = $e->getBuildPHPInfo()) { if ($lib_info = $e->getLibraryInfo()) {
self::logError('Failed module: ' . ConsoleColor::yellow("PHP builder {$php_info['builder_class']} for {$php_info['os']}")); self::logError('Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
} elseif ($lib_info = $e->getLibraryInfo()) {
self::logError('✗ Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
} elseif ($ext_info = $e->getExtensionInfo()) { } elseif ($ext_info = $e->getExtensionInfo()) {
self::logError('Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder")); self::logError('Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder"));
} elseif (self::$builder) {
$os = match (get_class(self::$builder)) {
WindowsBuilder::class => 'Windows',
MacOSBuilder::class => 'macOS',
LinuxBuilder::class => 'Linux',
BSDBuilder::class => 'FreeBSD',
default => 'Unknown OS',
};
self::logError('Failed module: ' . ConsoleColor::yellow("Builder for {$os}"));
} elseif (!in_array($class, self::KNOWN_EXCEPTIONS)) { } elseif (!in_array($class, self::KNOWN_EXCEPTIONS)) {
self::logError('Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class)); self::logError('Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class));
} }
self::logError('');
// get command execution info // get command execution info
if ($e instanceof ExecutionException) { if ($e instanceof ExecutionException) {
self::logError('✗ Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand())); self::logError('');
self::logError('Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand()));
if ($cd = $e->getCd()) { if ($cd = $e->getCd()) {
self::logError('Command executed in: ' . ConsoleColor::yellow($cd)); self::logError('Command executed in: ' . ConsoleColor::yellow($cd));
} }
if ($env = $e->getEnv()) { if ($env = $e->getEnv()) {
self::logError('Command inline env variables:'); self::logError('Command inline env variables:');
foreach ($env as $k => $v) { foreach ($env as $k => $v) {
self::logError(ConsoleColor::yellow("{$k}={$v}"), 4); self::logError(ConsoleColor::yellow("{$k}={$v}"), 4);
} }
@@ -81,46 +99,40 @@ class ExceptionHandler
// validation error // validation error
if ($e instanceof ValidationException) { if ($e instanceof ValidationException) {
self::logError('Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString())); self::logError('Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString()));
} }
// environment error // environment error
if ($e instanceof EnvironmentException) { if ($e instanceof EnvironmentException) {
self::logError('Failed environment check: ' . ConsoleColor::yellow($e->getMessage())); self::logError('Failed environment check: ' . ConsoleColor::yellow($e->getMessage()));
if (($solution = $e->getSolution()) !== null) { if (($solution = $e->getSolution()) !== null) {
self::logError('Solution: ' . ConsoleColor::yellow($solution)); self::logError('Solution: ' . ConsoleColor::yellow($solution));
} }
} }
// get patch info // get patch info
if ($e instanceof PatchException) { if ($e instanceof PatchException) {
self::logError("Failed patch module: {$e->getPatchModule()}"); self::logError("Failed patch module: {$e->getPatchModule()}");
} }
// get internal trace // get internal trace
if ($e instanceof SPCInternalException) { if ($e instanceof SPCInternalException) {
self::logError('Internal trace:'); self::logError('Internal trace:');
self::logError(ConsoleColor::gray("{$e->getTraceAsString()}\n"), 4); self::logError(ConsoleColor::gray("{$e->getTraceAsString()}\n"), 4);
} }
// get the full build info if possible // get the full build info if possible
if (($info = $e->getBuildPHPExtraInfo()) && defined('DEBUG_MODE')) { if ($info = ExceptionHandler::$build_php_extra_info) {
self::logError('✗ Build PHP extra info:'); self::logError('', output_log: defined('DEBUG_MODE'));
$maxlen = 0; self::logError('Build PHP extra info:', output_log: defined('DEBUG_MODE'));
foreach ($info as $k => $v) { self::printArrayInfo($info);
$maxlen = max(strlen($k), $maxlen); }
}
foreach ($info as $k => $v) { // get the full builder options if possible
if (is_string($v)) { if ($e->getBuildPHPInfo()) {
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($v), 4); $info = $e->getBuildPHPInfo();
} elseif (is_array($v) && !is_assoc_array($v)) { self::logError('', output_log: defined('DEBUG_MODE'));
$first = array_shift($v); self::logError('Builder function: ' . ConsoleColor::yellow($info['builder_function']), output_log: defined('DEBUG_MODE'));
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($first), 4);
foreach ($v as $vs) {
self::logError(str_pad('', $maxlen + 2) . ConsoleColor::yellow($vs), 4);
}
}
}
} }
self::logError("\n----------------------------------------\n"); self::logError("\n----------------------------------------\n");
@@ -142,20 +154,67 @@ class ExceptionHandler
public static function handleDefaultException(\Throwable $e): void public static function handleDefaultException(\Throwable $e): void
{ {
$class = get_class($e); $class = get_class($e);
self::logError("Unhandled exception {$class}: {$e->getMessage()}\n\t{$e->getMessage()}\n"); self::logError("Unhandled exception {$class}:\n\t{$e->getMessage()}\n");
self::logError('Stack trace:'); self::logError('Stack trace:');
self::logError(ConsoleColor::gray($e->getTraceAsString()), 4); self::logError(ConsoleColor::gray($e->getTraceAsString()) . PHP_EOL, 4);
self::logError('Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues'); self::logError('Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues');
} }
private static function logError($message, int $indent_space = 0): void public static function bindBuilder(?BuilderBase $bind_builder): void
{
self::$builder = $bind_builder;
}
public static function bindBuildPhpExtraInfo(array $build_php_extra_info): void
{
self::$build_php_extra_info = $build_php_extra_info;
}
private static function logError($message, int $indent_space = 0, bool $output_log = true): void
{ {
$spc_log = fopen(SPC_OUTPUT_LOG, 'a'); $spc_log = fopen(SPC_OUTPUT_LOG, 'a');
$msg = explode("\n", (string) $message); $msg = explode("\n", (string) $message);
foreach ($msg as $v) { foreach ($msg as $v) {
$line = str_pad($v, strlen($v) + $indent_space, ' ', STR_PAD_LEFT); $line = str_pad($v, strlen($v) + $indent_space, ' ', STR_PAD_LEFT);
fwrite($spc_log, strip_ansi_colors($line) . PHP_EOL); fwrite($spc_log, strip_ansi_colors($line) . PHP_EOL);
echo ConsoleColor::red($line) . PHP_EOL; if ($output_log) {
echo ConsoleColor::red($line) . PHP_EOL;
}
}
}
/**
* Print array info to console and log.
*/
private static function printArrayInfo(array $info): void
{
$log_output = defined('DEBUG_MODE');
$maxlen = 0;
foreach ($info as $k => $v) {
$maxlen = max(strlen($k), $maxlen);
}
foreach ($info as $k => $v) {
if (is_string($v)) {
if ($v === '') {
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow('""'), 4, $log_output);
} else {
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($v), 4, $log_output);
}
} elseif (is_array($v) && !is_assoc_array($v)) {
if ($v === []) {
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow('[]'), 4, $log_output);
continue;
}
$first = array_shift($v);
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($first), 4, $log_output);
foreach ($v as $vs) {
self::logError(str_pad('', $maxlen + 2) . ConsoleColor::yellow($vs), 4, $log_output);
}
} elseif (is_bool($v) || is_null($v)) {
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::cyan($v === true ? 'true' : ($v === false ? 'false' : 'null')), 4, $log_output);
} else {
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow(json_encode($v, JSON_PRETTY_PRINT)), 4, $log_output);
}
} }
} }
} }

View File

@@ -5,15 +5,11 @@ declare(strict_types=1);
namespace SPC\exception; namespace SPC\exception;
use SPC\builder\BuilderBase; use SPC\builder\BuilderBase;
use SPC\builder\freebsd\BSDBuilder;
use SPC\builder\freebsd\library\BSDLibraryBase; use SPC\builder\freebsd\library\BSDLibraryBase;
use SPC\builder\LibraryBase; use SPC\builder\LibraryBase;
use SPC\builder\linux\library\LinuxLibraryBase; use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\builder\linux\LinuxBuilder;
use SPC\builder\macos\library\MacOSLibraryBase; use SPC\builder\macos\library\MacOSLibraryBase;
use SPC\builder\macos\MacOSBuilder;
use SPC\builder\windows\library\WindowsLibraryBase; use SPC\builder\windows\library\WindowsLibraryBase;
use SPC\builder\windows\WindowsBuilder;
/** /**
* Base class for SPC exceptions. * Base class for SPC exceptions.
@@ -24,8 +20,6 @@ use SPC\builder\windows\WindowsBuilder;
*/ */
abstract class SPCException extends \Exception abstract class SPCException extends \Exception
{ {
private static ?array $build_php_extra_info = null;
private ?array $library_info = null; private ?array $library_info = null;
private ?array $extension_info = null; private ?array $extension_info = null;
@@ -40,11 +34,6 @@ abstract class SPCException extends \Exception
$this->loadStackTraceInfo(); $this->loadStackTraceInfo();
} }
public static function bindBuildPHPExtraInfo(array $indent_texts): void
{
self::$build_php_extra_info = $indent_texts;
}
public function bindExtensionInfo(array $extension_info): void public function bindExtensionInfo(array $extension_info): void
{ {
$this->extension_info = $extension_info; $this->extension_info = $extension_info;
@@ -55,11 +44,6 @@ abstract class SPCException extends \Exception
$this->extra_log_files[$key] = $filename; $this->extra_log_files[$key] = $filename;
} }
public function getBuildPHPExtraInfo(): ?array
{
return self::$build_php_extra_info;
}
/** /**
* Returns an array containing information about the SPC module. * Returns an array containing information about the SPC module.
* *
@@ -82,8 +66,7 @@ abstract class SPCException extends \Exception
* Returns an array containing information about the PHP build process. * Returns an array containing information about the PHP build process.
* *
* @return null|array{ * @return null|array{
* builder_class: string, * builder_function: string,
* os: string,
* file: null|string, * file: null|string,
* line: null|int, * line: null|int,
* } an array containing PHP build information * } an array containing PHP build information
@@ -124,7 +107,7 @@ abstract class SPCException extends \Exception
} }
// Check if the class is a subclass of LibraryBase // Check if the class is a subclass of LibraryBase
if (!$this->library_info && is_subclass_of($frame['class'], LibraryBase::class)) { if (!$this->library_info && is_a($frame['class'], LibraryBase::class, true)) {
try { try {
$reflection = new \ReflectionClass($frame['class']); $reflection = new \ReflectionClass($frame['class']);
if ($reflection->hasConstant('NAME')) { if ($reflection->hasConstant('NAME')) {
@@ -152,21 +135,9 @@ abstract class SPCException extends \Exception
} }
// Check if the class is a subclass of BuilderBase and the method is buildPHP // Check if the class is a subclass of BuilderBase and the method is buildPHP
if (!$this->build_php_info && is_subclass_of($frame['class'], BuilderBase::class) && $frame['function'] === 'buildPHP') { if (!$this->build_php_info && is_a($frame['class'], BuilderBase::class, true)) {
$reflection = new \ReflectionClass($frame['class']);
if ($reflection->hasProperty('options')) {
$options = $reflection->getProperty('options')->getValue();
}
$this->build_php_info = [ $this->build_php_info = [
'builder_class' => $frame['class'], 'builder_function' => $frame['function'],
'builder_options' => $options ?? [],
'os' => match (true) {
is_a($frame['class'], BSDBuilder::class, true) => 'BSD',
is_a($frame['class'], LinuxBuilder::class, true) => 'Linux',
is_a($frame['class'], MacOSBuilder::class, true) => 'macOS',
is_a($frame['class'], WindowsBuilder::class, true) => 'Windows',
default => 'Unknown',
},
'file' => $frame['file'] ?? null, 'file' => $frame['file'] ?? null,
'line' => $frame['line'] ?? null, 'line' => $frame['line'] ?? null,
]; ];

View File

@@ -8,6 +8,7 @@ use SPC\builder\BuilderBase;
use SPC\builder\linux\SystemUtil; use SPC\builder\linux\SystemUtil;
use SPC\builder\unix\UnixBuilderBase; use SPC\builder\unix\UnixBuilderBase;
use SPC\builder\windows\WindowsBuilder; use SPC\builder\windows\WindowsBuilder;
use SPC\exception\ExecutionException;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\PatchException; use SPC\exception\PatchException;
use SPC\util\SPCTarget; use SPC\util\SPCTarget;
@@ -190,46 +191,51 @@ class SourcePatcher
*/ */
public static function patchFile(string $patch_name, string $cwd, bool $reverse = false): bool public static function patchFile(string $patch_name, string $cwd, bool $reverse = false): bool
{ {
if (FileSystem::isRelativePath($patch_name)) { try {
$patch_file = ROOT_DIR . "/src/globals/patch/{$patch_name}"; if (FileSystem::isRelativePath($patch_name)) {
} else { $patch_file = ROOT_DIR . "/src/globals/patch/{$patch_name}";
$patch_file = $patch_name; } else {
} $patch_file = $patch_name;
if (!file_exists($patch_file)) { }
return false; if (!file_exists($patch_file)) {
} return false;
}
$patch_str = FileSystem::convertPath($patch_file); $patch_str = FileSystem::convertPath($patch_file);
if (!file_exists($patch_str)) { if (!file_exists($patch_str)) {
throw new PatchException($patch_name, "Patch file [{$patch_str}] does not exist"); throw new PatchException($patch_name, "Patch file [{$patch_str}] does not exist");
} }
// Copy patch from phar // Copy patch from phar
if (str_starts_with($patch_str, 'phar://')) { if (str_starts_with($patch_str, 'phar://')) {
$filename = pathinfo($patch_file, PATHINFO_BASENAME); $filename = pathinfo($patch_file, PATHINFO_BASENAME);
file_put_contents(SOURCE_PATH . "/{$filename}", file_get_contents($patch_file)); file_put_contents(SOURCE_PATH . "/{$filename}", file_get_contents($patch_file));
$patch_str = FileSystem::convertPath(SOURCE_PATH . "/{$filename}"); $patch_str = FileSystem::convertPath(SOURCE_PATH . "/{$filename}");
} }
// detect // detect
$detect_reverse = !$reverse; $detect_reverse = !$reverse;
$detect_cmd = 'cd ' . escapeshellarg($cwd) . ' && ' $detect_cmd = 'cd ' . escapeshellarg($cwd) . ' && '
. (PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . escapeshellarg($patch_str) . (PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . escapeshellarg($patch_str)
. ' | patch --dry-run -p1 -s -f ' . ($detect_reverse ? '-R' : '') . ' | patch --dry-run -p1 -s -f ' . ($detect_reverse ? '-R' : '')
. ' > ' . (PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null') . ' 2>&1'; . ' > ' . (PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null') . ' 2>&1';
exec($detect_cmd, $output, $detect_status); exec($detect_cmd, $output, $detect_status);
if ($detect_status === 0) { if ($detect_status === 0) {
return true;
}
// apply patch
$apply_cmd = 'cd ' . escapeshellarg($cwd) . ' && '
. (PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . escapeshellarg($patch_str)
. ' | patch -p1 ' . ($reverse ? '-R' : '');
f_passthru($apply_cmd);
return true; return true;
} catch (ExecutionException $e) {
// If patch failed, throw exception
throw new PatchException($patch_name, "Patch file [{$patch_name}] failed to apply", previous: $e);
} }
// apply patch
$apply_cmd = 'cd ' . escapeshellarg($cwd) . ' && '
. (PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . escapeshellarg($patch_str)
. ' | patch -p1 ' . ($reverse ? '-R' : '');
f_passthru($apply_cmd);
return true;
} }
public static function patchOpenssl11Darwin(): bool public static function patchOpenssl11Darwin(): bool

View File

@@ -16,7 +16,7 @@ class PhpSource extends CustomSourceBase
{ {
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4'; $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4';
if ($major === '8.5') { if ($major === '8.5') {
Downloader::downloadSource('php-src', ['type' => 'url', 'url' => 'https://downloads.php.net/~daniels/php-8.5.0alpha4.tar.xz'], $force); Downloader::downloadSource('php-src', ['type' => 'url', 'url' => 'https://downloads.php.net/~edorian/php-8.5.0beta1.tar.xz'], $force);
} elseif ($major === 'git') { } elseif ($major === 'git') {
Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force); Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force);
} else { } else {