mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-19 13:24:51 +08:00
Compare commits
2 Commits
c38f174a6b
...
71d803d36f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71d803d36f | ||
|
|
daa87e1350 |
@ -3,7 +3,7 @@
|
||||
set -e
|
||||
|
||||
# This file is using docker to run commands
|
||||
SPC_DOCKER_VERSION=v6
|
||||
SPC_DOCKER_VERSION=v7
|
||||
|
||||
# Detect docker can run
|
||||
if ! which docker >/dev/null; then
|
||||
@ -123,6 +123,7 @@ COPY ./composer.* /app/
|
||||
ADD ./bin /app/bin
|
||||
RUN composer install --no-dev
|
||||
ADD ./config /app/config
|
||||
ADD ./spc.registry.json /app/spc.registry.json
|
||||
RUN bin/spc doctor --auto-fix
|
||||
RUN bin/spc install-pkg upx
|
||||
|
||||
|
||||
34
src/Package/Extension/readline.php
Normal file
34
src/Package/Extension/readline.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Package\Extension;
|
||||
|
||||
use StaticPHP\Attribute\Package\AfterStage;
|
||||
use StaticPHP\Attribute\Package\BeforeStage;
|
||||
use StaticPHP\Attribute\Package\Extension;
|
||||
use StaticPHP\Package\PackageInstaller;
|
||||
use StaticPHP\Toolchain\Interface\ToolchainInterface;
|
||||
use StaticPHP\Util\SourcePatcher;
|
||||
|
||||
#[Extension('readline')]
|
||||
class readline
|
||||
{
|
||||
#[BeforeStage('php', 'unix-make-cli')]
|
||||
public function beforeMakeLinuxCli(PackageInstaller $installer, ToolchainInterface $toolchain): void
|
||||
{
|
||||
if ($toolchain->isStatic()) {
|
||||
$php_src = $installer->getBuildPackage('php')->getSourceDir();
|
||||
SourcePatcher::patchFile('musl_static_readline.patch', $php_src);
|
||||
}
|
||||
}
|
||||
|
||||
#[AfterStage('php', 'unix-make-cli')]
|
||||
public function afterMakeLinuxCli(PackageInstaller $installer, ToolchainInterface $toolchain): void
|
||||
{
|
||||
if ($toolchain->isStatic()) {
|
||||
$php_src = $installer->getBuildPackage('php')->getSourceDir();
|
||||
SourcePatcher::patchFile('musl_static_readline.patch', $php_src, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/Package/Library/imap.php
Normal file
24
src/Package/Library/imap.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Package\Library;
|
||||
|
||||
use StaticPHP\Attribute\Package\AfterStage;
|
||||
use StaticPHP\Attribute\Package\Library;
|
||||
use StaticPHP\Attribute\PatchDescription;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
|
||||
#[Library('imap')]
|
||||
class imap
|
||||
{
|
||||
#[AfterStage('php', 'patch-embed-scripts')]
|
||||
#[PatchDescription('Fix missing -lcrypt in php-config libs on glibc systems')]
|
||||
public function afterPatchScripts(): void
|
||||
{
|
||||
if (SystemTarget::getLibc() === 'glibc') {
|
||||
FileSystem::replaceFileRegex(BUILD_BIN_PATH . '/php-config', '/^libs="(.*)"$/m', 'libs="$1 -lcrypt"');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,10 +26,12 @@ use StaticPHP\Package\TargetPackage;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Toolchain\Interface\ToolchainInterface;
|
||||
use StaticPHP\Toolchain\ToolchainManager;
|
||||
use StaticPHP\Util\DirDiff;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
use StaticPHP\Util\InteractiveTerm;
|
||||
use StaticPHP\Util\SourcePatcher;
|
||||
use StaticPHP\Util\SPCConfigUtil;
|
||||
use StaticPHP\Util\System\UnixUtil;
|
||||
use StaticPHP\Util\V2CompatLayer;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@ -312,11 +314,17 @@ class php
|
||||
if ($installer->isBuildPackage('php-cli')) {
|
||||
$package->runStage('unix-make-cli');
|
||||
}
|
||||
if ($installer->isBuildPackage('php-cgi')) {
|
||||
$package->runStage('unix-make-cgi');
|
||||
}
|
||||
if ($installer->isBuildPackage('php-fpm')) {
|
||||
$package->runStage('unix-make-fpm');
|
||||
}
|
||||
if ($installer->isBuildPackage('php-cgi')) {
|
||||
$package->runStage('unix-make-cgi');
|
||||
if ($installer->isBuildPackage('php-micro')) {
|
||||
$package->runStage('unix-make-micro');
|
||||
}
|
||||
if ($installer->isBuildPackage('php-embed')) {
|
||||
$package->runStage('unix-make-embed');
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,9 +338,103 @@ class php
|
||||
->exec("make -j{$concurrency} cli");
|
||||
}
|
||||
|
||||
#[Stage('unix-make-cgi')]
|
||||
public function makeCgiForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cgi'));
|
||||
$concurrency = $builder->concurrency;
|
||||
shell()->cd($package->getSourceDir())
|
||||
->setEnv($this->makeVars($installer))
|
||||
->exec("make -j{$concurrency} cgi");
|
||||
}
|
||||
|
||||
#[Stage('unix-make-fpm')]
|
||||
public function makeFpmForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make fpm'));
|
||||
$concurrency = $builder->concurrency;
|
||||
shell()->cd($package->getSourceDir())
|
||||
->setEnv($this->makeVars($installer))
|
||||
->exec("make -j{$concurrency} fpm");
|
||||
}
|
||||
|
||||
#[Stage('unix-make-micro')]
|
||||
public function makeMicroForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
$phar_patched = false;
|
||||
try {
|
||||
if ($installer->isPackageResolved('ext-phar')) {
|
||||
$phar_patched = true;
|
||||
SourcePatcher::patchMicroPhar(self::getPHPVersionID());
|
||||
}
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make micro'));
|
||||
// apply --with-micro-fake-cli option
|
||||
$vars = $this->makeVars($installer);
|
||||
$vars['EXTRA_CFLAGS'] .= $package->getBuildOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : '';
|
||||
// build
|
||||
shell()->cd($package->getSourceDir())
|
||||
->setEnv($vars)
|
||||
->exec("make -j{$builder->concurrency} micro");
|
||||
} finally {
|
||||
if ($phar_patched) {
|
||||
SourcePatcher::unpatchMicroPhar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Stage('unix-make-embed')]
|
||||
public function makeEmbedForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
$shared_exts = array_filter(
|
||||
$installer->getResolvedPackages(),
|
||||
static fn ($x) => $x instanceof PhpExtensionPackage && $x->isBuildShared() && $x->isBuildWithPhp()
|
||||
);
|
||||
$install_modules = $shared_exts ? 'install-modules' : '';
|
||||
|
||||
// detect changes in module path
|
||||
$diff = new DirDiff(BUILD_MODULES_PATH, true);
|
||||
|
||||
$root = BUILD_ROOT_PATH;
|
||||
shell()->cd($package->getSourceDir())
|
||||
->setEnv($this->makeVars($installer))
|
||||
->exec('sed -i "s|^EXTENSION_DIR = .*|EXTENSION_DIR = /' . basename(BUILD_MODULES_PATH) . '|" Makefile')
|
||||
->exec("make -j{$builder->concurrency} INSTALL_ROOT={$root} install-sapi {$install_modules} install-build install-headers install-programs");
|
||||
|
||||
// ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=shared -------------
|
||||
|
||||
// process libphp.so for shared embed
|
||||
$suffix = SystemTarget::getTargetOS() === 'Darwin' ? 'dylib' : 'so';
|
||||
$libphp_so = "{$package->getLibDir()}/libphp.{$suffix}";
|
||||
if (file_exists($libphp_so)) {
|
||||
// rename libphp.so if -release is set
|
||||
if (SystemTarget::getTargetOS() === 'Linux') {
|
||||
$this->processLibphpSoFile($libphp_so, $installer);
|
||||
}
|
||||
// deploy
|
||||
$builder->deployBinary($libphp_so, $libphp_so, false);
|
||||
}
|
||||
|
||||
// process shared extensions that built-with-php
|
||||
$increment_files = $diff->getChangedFiles();
|
||||
foreach ($increment_files as $increment_file) {
|
||||
$builder->deployBinary($increment_file, $libphp_so, false);
|
||||
}
|
||||
|
||||
// ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=static -------------
|
||||
|
||||
// process libphp.a for static embed
|
||||
$ar = getenv('AR') ?: 'ar';
|
||||
$libphp_a = "{$package->getLibDir()}/libphp.a";
|
||||
shell()->exec("{$ar} -t {$libphp_a} | grep '\\.a$' | xargs -n1 {$ar} d {$libphp_a}");
|
||||
UnixUtil::exportDynamicSymbols($libphp_a);
|
||||
|
||||
// deploy embed php scripts
|
||||
$package->runStage('patch-embed-scripts');
|
||||
}
|
||||
|
||||
#[BuildFor('Darwin')]
|
||||
#[BuildFor('Linux')]
|
||||
public function build(TargetPackage $package): void
|
||||
public function build(TargetPackage $package, PackageInstaller $installer, ToolchainInterface $toolchain): void
|
||||
{
|
||||
// virtual target, do nothing
|
||||
if ($package->getName() !== 'php') {
|
||||
@ -342,6 +444,68 @@ class php
|
||||
$package->runStage('unix-buildconf');
|
||||
$package->runStage('unix-configure');
|
||||
$package->runStage('unix-make');
|
||||
|
||||
// collect shared extensions
|
||||
/** @var PhpExtensionPackage[] $shared_extensions */
|
||||
$shared_extensions = array_filter(
|
||||
$installer->getResolvedPackages(PhpExtensionPackage::class),
|
||||
fn ($x) => $x->isBuildShared() && !$x->isBuildWithPhp()
|
||||
);
|
||||
if (!empty($shared_extensions)) {
|
||||
if ($toolchain->isStatic()) {
|
||||
throw new WrongUsageException(
|
||||
"You're building against musl libc statically (the default on Linux), but you're trying to build shared extensions.\n" .
|
||||
'Static musl libc does not implement `dlopen`, so your php binary is not able to load shared extensions.' . "\n" .
|
||||
'Either use SPC_LIBC=glibc to link against glibc on a glibc OS, or use SPC_TARGET="native-native-musl -dynamic" to link against musl libc dynamically using `zig cc`.'
|
||||
);
|
||||
}
|
||||
FileSystem::createDir(BUILD_MODULES_PATH);
|
||||
|
||||
// backup
|
||||
FileSystem::backupFile(BUILD_BIN_PATH . '/php-config');
|
||||
FileSystem::backupFile(BUILD_LIB_PATH . '/php/build/phpize.m4');
|
||||
|
||||
FileSystem::replaceFileLineContainsString(BUILD_BIN_PATH . '/php-config', 'extension_dir=', 'extension_dir="' . BUILD_MODULES_PATH . '"');
|
||||
FileSystem::replaceFileStr(BUILD_LIB_PATH . '/php/build/phpize.m4', 'test "[$]$1" = "no" && $1=yes', '# test "[$]$1" = "no" && $1=yes');
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($shared_extensions as $extension) {
|
||||
logger()->info('Building shared extensions...');
|
||||
$extension->buildSharedExtension();
|
||||
}
|
||||
} finally {
|
||||
// restore php-config
|
||||
if (!empty($shared_extensions)) {
|
||||
FileSystem::restoreBackupFile(BUILD_BIN_PATH . '/php-config');
|
||||
FileSystem::restoreBackupFile(BUILD_LIB_PATH . '/php/build/phpize.m4');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch phpize and php-config if needed
|
||||
*/
|
||||
#[Stage('patch-embed-scripts')]
|
||||
public function patchPhpScripts(): void
|
||||
{
|
||||
// patch phpize
|
||||
if (file_exists(BUILD_BIN_PATH . '/phpize')) {
|
||||
logger()->debug('Patching phpize prefix');
|
||||
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'");
|
||||
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#');
|
||||
}
|
||||
// patch php-config
|
||||
if (file_exists(BUILD_BIN_PATH . '/php-config')) {
|
||||
logger()->debug('Patching php-config prefix and libs order');
|
||||
$php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config');
|
||||
$php_config_str = str_replace('prefix=""', 'prefix="' . BUILD_ROOT_PATH . '"', $php_config_str);
|
||||
// move mimalloc to the beginning of libs
|
||||
$php_config_str = preg_replace('/(libs=")(.*?)\s*(' . preg_quote(BUILD_LIB_PATH, '/') . '\/mimalloc\.o)\s*(.*?)"/', '$1$3 $2 $4"', $php_config_str);
|
||||
// move lstdc++ to the end of libs
|
||||
$php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str);
|
||||
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -381,6 +545,10 @@ class php
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make environment variables for php make.
|
||||
* This will call SPCConfigUtil to generate proper LDFLAGS and LIBS for static linking.
|
||||
*/
|
||||
private function makeVars(PackageInstaller $installer): array
|
||||
{
|
||||
$config = (new SPCConfigUtil(['libs_only_deps' => true]))->config(array_map(fn ($x) => $x->getName(), $installer->getResolvedPackages()));
|
||||
@ -394,4 +562,75 @@ class php
|
||||
'EXTRA_LIBS' => $config['libs'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename libphp.so to libphp-<release>.so if -release is set in LDFLAGS.
|
||||
*/
|
||||
private function processLibphpSoFile(string $libphpSo, PackageInstaller $installer): void
|
||||
{
|
||||
$ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: '';
|
||||
$libDir = BUILD_LIB_PATH;
|
||||
$modulesDir = BUILD_MODULES_PATH;
|
||||
$realLibName = 'libphp.so';
|
||||
$cwd = getcwd();
|
||||
|
||||
if (preg_match('/-release\s+(\S+)/', $ldflags, $matches)) {
|
||||
$release = $matches[1];
|
||||
$realLibName = "libphp-{$release}.so";
|
||||
$libphpRelease = "{$libDir}/{$realLibName}";
|
||||
if (!file_exists($libphpRelease) && file_exists($libphpSo)) {
|
||||
rename($libphpSo, $libphpRelease);
|
||||
}
|
||||
if (file_exists($libphpRelease)) {
|
||||
chdir($libDir);
|
||||
if (file_exists($libphpSo)) {
|
||||
unlink($libphpSo);
|
||||
}
|
||||
symlink($realLibName, 'libphp.so');
|
||||
shell()->exec(sprintf(
|
||||
'patchelf --set-soname %s %s',
|
||||
escapeshellarg($realLibName),
|
||||
escapeshellarg($libphpRelease)
|
||||
));
|
||||
}
|
||||
if (is_dir($modulesDir)) {
|
||||
chdir($modulesDir);
|
||||
foreach ($installer->getResolvedPackages(PhpExtensionPackage::class) as $ext) {
|
||||
if (!$ext->isBuildShared()) {
|
||||
continue;
|
||||
}
|
||||
$name = $ext->getName();
|
||||
$versioned = "{$name}-{$release}.so";
|
||||
$unversioned = "{$name}.so";
|
||||
$src = "{$modulesDir}/{$versioned}";
|
||||
$dst = "{$modulesDir}/{$unversioned}";
|
||||
if (is_file($src)) {
|
||||
rename($src, $dst);
|
||||
shell()->exec(sprintf(
|
||||
'patchelf --set-soname %s %s',
|
||||
escapeshellarg($unversioned),
|
||||
escapeshellarg($dst)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
chdir($cwd);
|
||||
}
|
||||
|
||||
$target = "{$libDir}/{$realLibName}";
|
||||
if (file_exists($target)) {
|
||||
[, $output] = shell()->execWithResult('readelf -d ' . escapeshellarg($target));
|
||||
$output = implode("\n", $output);
|
||||
if (preg_match('/SONAME.*\[(.+)]/', $output, $sonameMatch)) {
|
||||
$currentSoname = $sonameMatch[1];
|
||||
if ($currentSoname !== basename($target)) {
|
||||
shell()->exec(sprintf(
|
||||
'patchelf --set-soname %s %s',
|
||||
escapeshellarg(basename($target)),
|
||||
escapeshellarg($target)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,7 +493,7 @@ class Artifact
|
||||
* Emit all after binary extract callbacks for the specified platform.
|
||||
*
|
||||
* @param null|string $target_path The directory where binary was extracted
|
||||
* @param string $platform The platform string (e.g., 'linux-x86_64')
|
||||
* @param string $platform The platform string (e.g., 'linux-x86_64')
|
||||
*/
|
||||
public function emitAfterBinaryExtract(?string $target_path, string $platform): void
|
||||
{
|
||||
|
||||
@ -570,6 +570,21 @@ class ArtifactExtractor
|
||||
FileSystem::removeDir($temp_dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace path variables.
|
||||
*/
|
||||
protected function replacePathVariables(string $path): string
|
||||
{
|
||||
$replacement = [
|
||||
'{pkg_root_path}' => PKG_ROOT_PATH,
|
||||
'{build_root_path}' => BUILD_ROOT_PATH,
|
||||
'{source_path}' => SOURCE_PATH,
|
||||
'{download_path}' => DOWNLOAD_PATH,
|
||||
'{working_dir}' => WORKING_DIR,
|
||||
];
|
||||
return str_replace(array_keys($replacement), array_values($replacement), $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move file or directory, handling cross-device scenarios
|
||||
* Uses rename() if possible, falls back to copy+delete for cross-device moves
|
||||
@ -608,21 +623,6 @@ class ArtifactExtractor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace path variables.
|
||||
*/
|
||||
protected function replacePathVariables(string $path): string
|
||||
{
|
||||
$replacement = [
|
||||
'{pkg_root_path}' => PKG_ROOT_PATH,
|
||||
'{build_root_path}' => BUILD_ROOT_PATH,
|
||||
'{source_path}' => SOURCE_PATH,
|
||||
'{download_path}' => DOWNLOAD_PATH,
|
||||
'{working_dir}' => WORKING_DIR,
|
||||
];
|
||||
return str_replace(array_keys($replacement), array_values($replacement), $path);
|
||||
}
|
||||
|
||||
private function copyFile(string $source_file, string $target_path): void
|
||||
{
|
||||
FileSystem::createDir(dirname($target_path));
|
||||
|
||||
@ -35,10 +35,11 @@ class ConsoleApplication extends Application
|
||||
// only add target that contains artifact.source
|
||||
if ($package->hasStage('build')) {
|
||||
logger()->debug("Registering build target command for package: {$name}");
|
||||
$this->add(new BuildTargetCommand($name));
|
||||
$this->addCommand(new BuildTargetCommand($name));
|
||||
}
|
||||
}
|
||||
|
||||
// add core commands
|
||||
$this->addCommands([
|
||||
new DownloadCommand(),
|
||||
new DoctorCommand(),
|
||||
|
||||
@ -9,9 +9,11 @@ use StaticPHP\DI\ApplicationContext;
|
||||
use StaticPHP\Exception\SPCInternalException;
|
||||
use StaticPHP\Exception\WrongUsageException;
|
||||
use StaticPHP\Runtime\Shell\Shell;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
use StaticPHP\Util\GlobalEnvManager;
|
||||
use StaticPHP\Util\InteractiveTerm;
|
||||
use StaticPHP\Util\System\LinuxUtil;
|
||||
|
||||
class PackageBuilder
|
||||
{
|
||||
@ -85,6 +87,92 @@ class PackageBuilder
|
||||
return $this->options[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy the binary file from src to dst.
|
||||
*/
|
||||
public function deployBinary(string $src, string $dst, bool $executable = true): string
|
||||
{
|
||||
logger()->debug("Deploying binary from {$src} to {$dst}");
|
||||
|
||||
// file must exists
|
||||
if (!file_exists($src)) {
|
||||
throw new SPCInternalException("Deploy failed. Cannot find file: {$src}");
|
||||
}
|
||||
// dst dir must exists
|
||||
FileSystem::createDir(dirname($dst));
|
||||
|
||||
// ignore copy to self
|
||||
if (realpath($src) !== realpath($dst)) {
|
||||
shell()->exec('cp ' . escapeshellarg($src) . ' ' . escapeshellarg($dst));
|
||||
}
|
||||
|
||||
// file exist
|
||||
if (!file_exists($dst)) {
|
||||
throw new SPCInternalException("Deploy failed. Cannot find file after copy: {$dst}");
|
||||
}
|
||||
|
||||
// extract debug info
|
||||
$this->extractDebugInfo($dst);
|
||||
|
||||
// strip
|
||||
if (!$this->getOption('no-strip')) {
|
||||
$this->stripBinary($dst);
|
||||
}
|
||||
|
||||
// UPX for linux
|
||||
$upx_option = $this->getOption('with-upx-pack');
|
||||
if ($upx_option && SystemTarget::getTargetOS() === 'Linux' && $executable) {
|
||||
if ($this->getOption('no-strip')) {
|
||||
logger()->warning('UPX compression is not recommended when --no-strip is enabled.');
|
||||
}
|
||||
logger()->info("Compressing {$dst} with UPX");
|
||||
shell()->exec(getenv('UPX_EXEC') . " --best {$dst}");
|
||||
}
|
||||
|
||||
return $dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract debug information from binary file.
|
||||
*
|
||||
* @param string $binary_path the path to the binary file, including executables, shared libraries, etc
|
||||
*/
|
||||
public function extractDebugInfo(string $binary_path): string
|
||||
{
|
||||
$target_dir = BUILD_ROOT_PATH . '/debug';
|
||||
FileSystem::createDir($target_dir);
|
||||
$basename = basename($binary_path);
|
||||
$debug_file = "{$target_dir}/{$basename}" . (SystemTarget::getTargetOS() === 'Darwin' ? '.dwarf' : '.debug');
|
||||
if (SystemTarget::getTargetOS() === 'Darwin') {
|
||||
shell()->exec("dsymutil -f {$binary_path} -o {$debug_file}");
|
||||
} elseif (SystemTarget::getTargetOS() === 'Linux') {
|
||||
if ($eu_strip = LinuxUtil::findCommand('eu-strip')) {
|
||||
shell()
|
||||
->exec("{$eu_strip} -f {$debug_file} {$binary_path}")
|
||||
->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}");
|
||||
} else {
|
||||
shell()
|
||||
->exec("objcopy --only-keep-debug {$binary_path} {$debug_file}")
|
||||
->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}");
|
||||
}
|
||||
} else {
|
||||
throw new SPCInternalException('extractDebugInfo is only supported on Linux and macOS');
|
||||
}
|
||||
return $debug_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip unneeded symbols from binary file.
|
||||
*/
|
||||
public function stripBinary(string $binary_path): void
|
||||
{
|
||||
shell()->exec(match (SystemTarget::getTargetOS()) {
|
||||
'Darwin' => "strip -S {$binary_path}",
|
||||
'Linux' => "strip --strip-unneeded {$binary_path}",
|
||||
default => throw new SPCInternalException('stripBinary is only supported on Linux and macOS'),
|
||||
});
|
||||
}
|
||||
|
||||
private function installLicense(Package $package, array $license): void
|
||||
{
|
||||
$dir = BUILD_ROOT_PATH . '/source-licenses/' . $package->getName();
|
||||
|
||||
@ -195,15 +195,20 @@ class PackageInstaller
|
||||
|
||||
/**
|
||||
* Get all resolved packages.
|
||||
* You can filter by package type class if needed.
|
||||
*
|
||||
* @return array<string, Package>
|
||||
* @template T
|
||||
* @param class-string<T> $package_type Filter by package type
|
||||
* @return array<T>
|
||||
*/
|
||||
public function getResolvedPackages(): array
|
||||
public function getResolvedPackages(mixed $package_type = Package::class): array
|
||||
{
|
||||
return $this->packages;
|
||||
return array_filter($this->packages, function (Package $pkg) use ($package_type): bool {
|
||||
return $pkg instanceof $package_type;
|
||||
});
|
||||
}
|
||||
|
||||
public function isPackageBeingResolved(string $package_name): bool
|
||||
public function isPackageResolved(string $package_name): bool
|
||||
{
|
||||
return isset($this->packages[$package_name]);
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ class PackageLoader
|
||||
$installer = ApplicationContext::get(PackageInstaller::class);
|
||||
$stages = self::$before_stages[$package_name][$stage] ?? [];
|
||||
foreach ($stages as [$callback, $only_when_package_resolved]) {
|
||||
if ($only_when_package_resolved !== null && !$installer->isPackageBeingResolved($only_when_package_resolved)) {
|
||||
if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) {
|
||||
continue;
|
||||
}
|
||||
yield $callback;
|
||||
@ -246,7 +246,7 @@ class PackageLoader
|
||||
$stages = self::$after_stage[$package_name][$stage] ?? [];
|
||||
$result = [];
|
||||
foreach ($stages as [$callback, $only_when_package_resolved]) {
|
||||
if ($only_when_package_resolved !== null && !$installer->isPackageBeingResolved($only_when_package_resolved)) {
|
||||
if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) {
|
||||
continue;
|
||||
}
|
||||
$result[] = $callback;
|
||||
|
||||
@ -107,4 +107,9 @@ class PhpExtensionPackage extends Package
|
||||
{
|
||||
return $this->build_with_php;
|
||||
}
|
||||
|
||||
public function buildSharedExtension(): void
|
||||
{
|
||||
// TODO: build common shared extensions code here...
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,6 +252,12 @@ class Registry
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return full path, resolving relative paths against a base path.
|
||||
*
|
||||
* @param string $path Input path (relative or absolute)
|
||||
* @param string $relative_path_base Base path for relative paths
|
||||
*/
|
||||
private static function fullpath(string $path, string $relative_path_base): string
|
||||
{
|
||||
if (FileSystem::isRelativePath($path)) {
|
||||
|
||||
95
src/StaticPHP/Util/DirDiff.php
Normal file
95
src/StaticPHP/Util/DirDiff.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Util;
|
||||
|
||||
/**
|
||||
* A util class to diff directory file increments.
|
||||
*/
|
||||
class DirDiff
|
||||
{
|
||||
protected array $before = [];
|
||||
|
||||
protected array $before_file_hashes = [];
|
||||
|
||||
public function __construct(protected string $dir, protected bool $track_content_changes = false)
|
||||
{
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the baseline to current state.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
$this->before = FileSystem::scanDirFiles($this->dir, relative: true) ?: [];
|
||||
|
||||
if ($this->track_content_changes) {
|
||||
$this->before_file_hashes = [];
|
||||
foreach ($this->before as $file) {
|
||||
$this->before_file_hashes[$file] = md5_file($this->dir . DIRECTORY_SEPARATOR . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of incremented files.
|
||||
*
|
||||
* @param bool $relative Return relative paths or absolute paths
|
||||
* @return array<string> List of incremented files
|
||||
*/
|
||||
public function getIncrementFiles(bool $relative = false): array
|
||||
{
|
||||
$after = FileSystem::scanDirFiles($this->dir, relative: true) ?: [];
|
||||
$diff = array_diff($after, $this->before);
|
||||
if ($relative) {
|
||||
return $diff;
|
||||
}
|
||||
return array_map(fn ($f) => $this->dir . DIRECTORY_SEPARATOR . $f, $diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of changed files (including new files).
|
||||
*
|
||||
* @param bool $relative Return relative paths or absolute paths
|
||||
* @param bool $include_new_files Include new files as changed files
|
||||
* @return array<string> List of changed files
|
||||
*/
|
||||
public function getChangedFiles(bool $relative = false, bool $include_new_files = true): array
|
||||
{
|
||||
$after = FileSystem::scanDirFiles($this->dir, relative: true) ?: [];
|
||||
$changed = [];
|
||||
foreach ($after as $file) {
|
||||
if (isset($this->before_file_hashes[$file])) {
|
||||
$after_hash = md5_file($this->dir . DIRECTORY_SEPARATOR . $file);
|
||||
if ($after_hash !== $this->before_file_hashes[$file]) {
|
||||
$changed[] = $file;
|
||||
}
|
||||
} elseif ($include_new_files) {
|
||||
// New file, consider as changed
|
||||
$changed[] = $file;
|
||||
}
|
||||
}
|
||||
if ($relative) {
|
||||
return $changed;
|
||||
}
|
||||
return array_map(fn ($f) => $this->dir . DIRECTORY_SEPARATOR . $f, $changed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of removed files.
|
||||
*
|
||||
* @param bool $relative Return relative paths or absolute paths
|
||||
* @return array<string> List of removed files
|
||||
*/
|
||||
public function getRemovedFiles(bool $relative = false): array
|
||||
{
|
||||
$after = FileSystem::scanDirFiles($this->dir, relative: true) ?: [];
|
||||
$removed = array_diff($this->before, $after);
|
||||
if ($relative) {
|
||||
return $removed;
|
||||
}
|
||||
return array_map(fn ($f) => $this->dir . DIRECTORY_SEPARATOR . $f, $removed);
|
||||
}
|
||||
}
|
||||
@ -159,4 +159,39 @@ class SourcePatcher
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch micro SAPI to support compressed phar loading from the current executable.
|
||||
*
|
||||
* @param int $version_id PHP version ID
|
||||
*/
|
||||
public static function patchMicroPhar(int $version_id): void
|
||||
{
|
||||
FileSystem::backupFile(SOURCE_PATH . '/php-src/ext/phar/phar.c');
|
||||
FileSystem::replaceFileStr(
|
||||
SOURCE_PATH . '/php-src/ext/phar/phar.c',
|
||||
'static zend_op_array *phar_compile_file',
|
||||
"char *micro_get_filename(void);\n\nstatic zend_op_array *phar_compile_file"
|
||||
);
|
||||
if ($version_id < 80100) {
|
||||
// PHP 8.0.x
|
||||
FileSystem::replaceFileStr(
|
||||
SOURCE_PATH . '/php-src/ext/phar/phar.c',
|
||||
'if (strstr(file_handle->filename, ".phar") && !strstr(file_handle->filename, "://")) {',
|
||||
'if ((strstr(file_handle->filename, micro_get_filename()) || strstr(file_handle->filename, ".phar")) && !strstr(file_handle->filename, "://")) {'
|
||||
);
|
||||
} else {
|
||||
// PHP >= 8.1
|
||||
FileSystem::replaceFileStr(
|
||||
SOURCE_PATH . '/php-src/ext/phar/phar.c',
|
||||
'if (strstr(ZSTR_VAL(file_handle->filename), ".phar") && !strstr(ZSTR_VAL(file_handle->filename), "://")) {',
|
||||
'if ((strstr(ZSTR_VAL(file_handle->filename), micro_get_filename()) || strstr(ZSTR_VAL(file_handle->filename), ".phar")) && !strstr(ZSTR_VAL(file_handle->filename), "://")) {'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function unpatchMicroPhar(): void
|
||||
{
|
||||
FileSystem::restoreBackupFile(SOURCE_PATH . '/php-src/ext/phar/phar.c');
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user