From 1865762f80e2d6c7ec13cf690c3d1edc4e34f3b0 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 22 Jan 2026 16:03:01 +0800 Subject: [PATCH 01/99] Fix config yaml support --- src/StaticPHP/Config/ArtifactConfig.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/StaticPHP/Config/ArtifactConfig.php b/src/StaticPHP/Config/ArtifactConfig.php index 49abae926..0ae9e2849 100644 --- a/src/StaticPHP/Config/ArtifactConfig.php +++ b/src/StaticPHP/Config/ArtifactConfig.php @@ -6,6 +6,7 @@ use StaticPHP\Exception\WrongUsageException; use StaticPHP\Registry\Registry; +use Symfony\Component\Yaml\Yaml; class ArtifactConfig { @@ -40,7 +41,11 @@ public static function loadFromFile(string $file, string $registry_name): string if ($content === false) { throw new WrongUsageException("Failed to read artifact config file: {$file}"); } - $data = json_decode($content, true); + $data = match (pathinfo($file, PATHINFO_EXTENSION)) { + 'json' => json_decode($content, true), + 'yml', 'yaml' => Yaml::parse($content), + default => throw new WrongUsageException("Unsupported artifact config file format: {$file}"), + }; if (!is_array($data)) { throw new WrongUsageException("Invalid JSON format in artifact config file: {$file}"); } From ae748757d1a9a420d55141b2cbe471d81c3c3682 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 22 Jan 2026 16:03:06 +0800 Subject: [PATCH 02/99] Fix config yaml support --- src/StaticPHP/Config/PackageConfig.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/StaticPHP/Config/PackageConfig.php b/src/StaticPHP/Config/PackageConfig.php index 56ef7ab1c..92c01f70f 100644 --- a/src/StaticPHP/Config/PackageConfig.php +++ b/src/StaticPHP/Config/PackageConfig.php @@ -7,6 +7,7 @@ use StaticPHP\Exception\WrongUsageException; use StaticPHP\Registry\Registry; use StaticPHP\Runtime\SystemTarget; +use Symfony\Component\Yaml\Yaml; class PackageConfig { @@ -47,10 +48,12 @@ public static function loadFromFile(string $file, string $registry_name): string if ($content === false) { throw new WrongUsageException("Failed to read package config file: {$file}"); } - $data = json_decode($content, true); - if (!is_array($data)) { - throw new WrongUsageException("Invalid JSON format in package config file: {$file}"); - } + // judge extension + $data = match (pathinfo($file, PATHINFO_EXTENSION)) { + 'json' => json_decode($content, true), + 'yml', 'yaml' => Yaml::parse($content), + default => throw new WrongUsageException("Unsupported package config file format: {$file}"), + }; ConfigValidator::validateAndLintPackages(basename($file), $data); foreach ($data as $pkg_name => $config) { self::$package_configs[$pkg_name] = $config; From 7b725bb4da0a605c8c63f0ebac50c58f5d07c21e Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 22 Jan 2026 16:04:48 +0800 Subject: [PATCH 03/99] Add LicenseDumper component --- src/StaticPHP/Command/DumpLicenseCommand.php | 147 +++++++++++ src/StaticPHP/ConsoleApplication.php | 2 + src/StaticPHP/Util/LicenseDumper.php | 262 +++++++++++++++++++ 3 files changed, 411 insertions(+) create mode 100644 src/StaticPHP/Command/DumpLicenseCommand.php create mode 100644 src/StaticPHP/Util/LicenseDumper.php diff --git a/src/StaticPHP/Command/DumpLicenseCommand.php b/src/StaticPHP/Command/DumpLicenseCommand.php new file mode 100644 index 000000000..d90ecbf95 --- /dev/null +++ b/src/StaticPHP/Command/DumpLicenseCommand.php @@ -0,0 +1,147 @@ +addArgument('artifacts', InputArgument::OPTIONAL, 'Specific artifacts to dump licenses, comma separated, e.g "php-src,openssl,curl"'); + + // v2 compatible options + $this->addOption('for-extensions', 'e', InputOption::VALUE_REQUIRED, 'Dump by extensions (automatically includes php-src), e.g "openssl,mbstring"'); + $this->addOption('for-libs', 'l', InputOption::VALUE_REQUIRED, 'Dump by libraries, e.g "openssl,zlib,curl"'); + + // v3 options + $this->addOption('for-packages', 'p', InputOption::VALUE_REQUIRED, 'Dump by packages, e.g "php,libssl,libcurl"'); + $this->addOption('dump-dir', 'd', InputOption::VALUE_REQUIRED, 'Target directory for dumped licenses', BUILD_ROOT_PATH . '/license'); + $this->addOption('without-suggests', null, null, 'Do not include suggested packages when using --for-extensions or --for-packages'); + } + + public function handle(): int + { + $dumper = new LicenseDumper(); + $dump_dir = $this->getOption('dump-dir'); + $artifacts_to_dump = []; + + // Handle direct artifact argument + if ($artifacts = $this->getArgument('artifacts')) { + $artifacts_to_dump = array_merge($artifacts_to_dump, parse_comma_list($artifacts)); + } + + // Handle --for-extensions option + if ($exts = $this->getOption('for-extensions')) { + $artifacts_to_dump = array_merge( + $artifacts_to_dump, + $this->resolveFromExtensions(parse_extension_list($exts)) + ); + } + + // Handle --for-libs option (v2 compat) + if ($libs = $this->getOption('for-libs')) { + $artifacts_to_dump = array_merge( + $artifacts_to_dump, + $this->resolveFromPackages(parse_comma_list($libs)) + ); + } + + // Handle --for-packages option + if ($packages = $this->getOption('for-packages')) { + $artifacts_to_dump = array_merge( + $artifacts_to_dump, + $this->resolveFromPackages(parse_comma_list($packages)) + ); + } + + // Check if any artifacts to dump + if (empty($artifacts_to_dump)) { + $this->output->writeln('No artifacts specified. Use one of:'); + $this->output->writeln(' - Direct argument: dump-license php-src,openssl,curl'); + $this->output->writeln(' - --for-extensions: dump-license --for-extensions=openssl,mbstring'); + $this->output->writeln(' - --for-libs: dump-license --for-libs=openssl,zlib'); + $this->output->writeln(' - --for-packages: dump-license --for-packages=php,libssl'); + return self::FAILURE; + } + + // Deduplicate artifacts + $artifacts_to_dump = array_values(array_unique($artifacts_to_dump)); + + logger()->info('Dumping licenses for ' . count($artifacts_to_dump) . ' artifact(s)'); + logger()->debug('Artifacts: ' . implode(', ', $artifacts_to_dump)); + + // Add artifacts to dumper + $dumper->addArtifacts($artifacts_to_dump); + + // Dump + $success = $dumper->dump($dump_dir); + + if ($success) { + InteractiveTerm::success('Licenses dumped successfully: ' . $dump_dir); + // $this->output->writeln("✓ Successfully dumped licenses to: {$dump_dir}"); + // $this->output->writeln(" Total artifacts: " . count($artifacts_to_dump) . ''); + return self::SUCCESS; + } + + $this->output->writeln('Failed to dump licenses'); + return self::FAILURE; + } + + /** + * Resolve artifacts from extension names. + * + * @param array $extensions Extension names + * @return array Artifact names + */ + private function resolveFromExtensions(array $extensions): array + { + // Convert extension names to package names + $packages = array_map(fn ($ext) => "ext-{$ext}", $extensions); + + // Automatically include php-related artifacts + array_unshift($packages, 'php'); + array_unshift($packages, 'php-micro'); + array_unshift($packages, 'php-embed'); + array_unshift($packages, 'php-fpm'); + + return $this->resolveFromPackages($packages); + } + + /** + * Resolve artifacts from package names. + * + * @param array $packages Package names + * @return array Artifact names + */ + private function resolveFromPackages(array $packages): array + { + $artifacts = []; + $include_suggests = !$this->getOption('without-suggests'); + + // Resolve package dependencies + $resolved_packages = DependencyResolver::resolve($packages, [], $include_suggests); + + foreach ($resolved_packages as $pkg_name) { + try { + $pkg = PackageLoader::getPackage($pkg_name); + if ($artifact = $pkg->getArtifact()) { + $artifacts[] = $artifact->getName(); + } + } catch (\Throwable $e) { + logger()->debug("Package {$pkg_name} has no artifact or failed to load: {$e->getMessage()}"); + } + } + + return array_unique($artifacts); + } +} diff --git a/src/StaticPHP/ConsoleApplication.php b/src/StaticPHP/ConsoleApplication.php index 9e37698c6..bd2e4eb19 100644 --- a/src/StaticPHP/ConsoleApplication.php +++ b/src/StaticPHP/ConsoleApplication.php @@ -12,6 +12,7 @@ use StaticPHP\Command\Dev\SortConfigCommand; use StaticPHP\Command\DoctorCommand; use StaticPHP\Command\DownloadCommand; +use StaticPHP\Command\DumpLicenseCommand; use StaticPHP\Command\ExtractCommand; use StaticPHP\Command\InstallPackageCommand; use StaticPHP\Command\SPCConfigCommand; @@ -55,6 +56,7 @@ public function __construct() new BuildLibsCommand(), new ExtractCommand(), new SPCConfigCommand(), + new DumpLicenseCommand(), // dev commands new ShellCommand(), diff --git a/src/StaticPHP/Util/LicenseDumper.php b/src/StaticPHP/Util/LicenseDumper.php new file mode 100644 index 000000000..57fc3aec4 --- /dev/null +++ b/src/StaticPHP/Util/LicenseDumper.php @@ -0,0 +1,262 @@ + Artifact names to dump */ + private array $artifacts = []; + + /** + * Add artifacts by name. + * + * @param array $artifacts Artifact names + */ + public function addArtifacts(array $artifacts): self + { + $this->artifacts = array_unique(array_merge($this->artifacts, $artifacts)); + return $this; + } + + /** + * Dump all collected artifact licenses to target directory. + * + * @param string $target_dir Target directory path + * @return bool True on success + */ + public function dump(string $target_dir): bool + { + // Create target directory if not exists (don't clean existing files) + if (!is_dir($target_dir)) { + FileSystem::createDir($target_dir); + } else { + logger()->debug("Target directory exists, will append/update licenses: {$target_dir}"); + } + + $license_summary = []; + $dumped_count = 0; + + foreach ($this->artifacts as $artifact_name) { + $artifact = ArtifactLoader::getArtifactInstance($artifact_name); + if ($artifact === null) { + logger()->warning("Artifact not found, skipping: {$artifact_name}"); + continue; + } + + try { + $result = $this->dumpArtifactLicense($artifact, $target_dir, $license_summary); + if ($result) { + ++$dumped_count; + } + } catch (\Throwable $e) { + logger()->warning("Failed to dump license for {$artifact_name}: {$e->getMessage()}"); + } + } + + // Generate LICENSE-SUMMARY.json (read-modify-write) + $this->generateSummary($target_dir, $license_summary); + + logger()->info("Successfully dumped {$dumped_count} license(s) to: {$target_dir}"); + return true; + } + + /** + * Dump license for a single artifact. + * + * @param Artifact $artifact Artifact instance + * @param string $target_dir Target directory + * @param array &$license_summary Summary data to populate + * @return bool True if dumped + * @throws SPCInternalException + */ + private function dumpArtifactLicense(Artifact $artifact, string $target_dir, array &$license_summary): bool + { + $artifact_name = $artifact->getName(); + + // Get metadata from ArtifactConfig + $artifact_config = ArtifactConfig::get($artifact_name); + $config = $artifact_config['metadata'] ?? null; + + if ($config === null) { + logger()->debug("No metadata for artifact: {$artifact_name}"); + return false; + } + + $license_type = $config['license'] ?? null; + $license_files = $config['license-files'] ?? []; + + // Ensure license_files is array + if (is_string($license_files)) { + $license_files = [$license_files]; + } + + if (empty($license_files)) { + logger()->debug("No license files specified for: {$artifact_name}"); + return false; + } + + // Record in summary + $summary_license = $license_type ?? 'Custom'; + if (!isset($license_summary[$summary_license])) { + $license_summary[$summary_license] = []; + } + $license_summary[$summary_license][] = $artifact_name; + + // Dump each license file + $file_count = count($license_files); + $dumped_any = false; + + foreach ($license_files as $index => $license_file_path) { + // Construct output filename + if ($file_count === 1) { + $output_filename = "{$artifact_name}_LICENSE.txt"; + } else { + $output_filename = "{$artifact_name}_LICENSE_{$index}.txt"; + } + + $output_path = "{$target_dir}/{$output_filename}"; + + // Skip if file already exists (avoid duplicate writes) + if (file_exists($output_path)) { + logger()->debug("License file already exists, skipping: {$output_filename}"); + $dumped_any = true; // Still count as dumped + continue; + } + + // Try to read license file from source directory + $license_content = $this->readLicenseFile($artifact, $license_file_path); + if ($license_content === null) { + logger()->warning("License file not found for {$artifact_name}: {$license_file_path}"); + continue; + } + + // Write to target + if (file_put_contents($output_path, $license_content) === false) { + throw new SPCInternalException("Failed to write license file: {$output_path}"); + } + + logger()->info("Dumped license: {$output_filename}"); + $dumped_any = true; + } + + return $dumped_any; + } + + /** + * Read license file content from artifact's source directory. + * + * @param Artifact $artifact Artifact instance + * @param string $license_file_path Relative path to license file + * @return null|string License content, or null if not found + */ + private function readLicenseFile(Artifact $artifact, string $license_file_path): ?string + { + $artifact_name = $artifact->getName(); + + // Try source directory first (if extracted) + if ($artifact->isSourceExtracted()) { + $source_dir = $artifact->getSourceDir(); + $full_path = "{$source_dir}/{$license_file_path}"; + + logger()->debug("Checking license file: {$full_path}"); + if (file_exists($full_path)) { + logger()->info("Reading license from source: {$full_path}"); + return file_get_contents($full_path); + } + } else { + logger()->debug("Artifact source not extracted: {$artifact_name}"); + } + + // Fallback: try SOURCE_PATH directly + $fallback_path = SOURCE_PATH . "/{$artifact_name}/{$license_file_path}"; + logger()->debug("Checking fallback path: {$fallback_path}"); + if (file_exists($fallback_path)) { + logger()->info("Reading license from fallback path: {$fallback_path}"); + return file_get_contents($fallback_path); + } + + logger()->debug("License file not found in any location for {$artifact_name}"); + return null; + } + + /** + * Generate LICENSE-SUMMARY.json file with read-modify-write support. + * + * @param string $target_dir Target directory + * @param array $license_summary License summary data (license_type => [artifacts]) + */ + private function generateSummary(string $target_dir, array $license_summary): void + { + if (empty($license_summary)) { + logger()->debug('No licenses to summarize'); + return; + } + + $summary_file = "{$target_dir}/LICENSE-SUMMARY.json"; + + // Read existing summary if exists + $existing_data = []; + if (file_exists($summary_file)) { + $content = file_get_contents($summary_file); + $existing_data = json_decode($content, true) ?? []; + logger()->debug('Loaded existing LICENSE-SUMMARY.json'); + } + + // Initialize structure + if (!isset($existing_data['artifacts'])) { + $existing_data['artifacts'] = []; + } + if (!isset($existing_data['summary'])) { + $existing_data['summary'] = ['license_types' => []]; + } + + // Merge new license information + foreach ($license_summary as $license_type => $artifacts) { + foreach ($artifacts as $artifact_name) { + // Add/update artifact info + $existing_data['artifacts'][$artifact_name] = [ + 'license' => $license_type, + 'dumped_at' => date('Y-m-d H:i:s'), + ]; + + // Update license_types summary + if (!isset($existing_data['summary']['license_types'][$license_type])) { + $existing_data['summary']['license_types'][$license_type] = []; + } + if (!in_array($artifact_name, $existing_data['summary']['license_types'][$license_type])) { + $existing_data['summary']['license_types'][$license_type][] = $artifact_name; + } + } + } + + // Sort license types and artifacts + ksort($existing_data['summary']['license_types']); + foreach ($existing_data['summary']['license_types'] as &$artifacts) { + sort($artifacts); + } + ksort($existing_data['artifacts']); + + // Update totals + $existing_data['summary']['total_artifacts'] = count($existing_data['artifacts']); + $existing_data['summary']['total_license_types'] = count($existing_data['summary']['license_types']); + $existing_data['summary']['last_updated'] = date('Y-m-d H:i:s'); + + // Write JSON file + file_put_contents( + $summary_file, + json_encode($existing_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) + ); + + logger()->info('Generated LICENSE-SUMMARY.json with ' . $existing_data['summary']['total_artifacts'] . ' artifact(s)'); + } +} From 22fc7030f69a6d42aff0a424c2ad1d5f31c38be5 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 22 Jan 2026 16:05:21 +0800 Subject: [PATCH 04/99] Implement attr, brotli, bzip2 build for unix --- config/artifact.yaml | 33 ++++++++++++++++++++ config/pkg.lib.yaml | 21 +++++++++++++ spc.registry.json | 4 +-- src/Package/Artifact/attr.php | 23 ++++++++++++++ src/Package/Artifact/bzip2.php | 24 +++++++++++++++ src/Package/Library/attr.php | 29 ++++++++++++++++++ src/Package/Library/brotli.php | 55 ++++++++++++++++++++++++++++++++++ src/Package/Library/bzip2.php | 25 ++++++++++++++++ src/globals/licenses/bzip2.txt | 14 +++++++++ 9 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 config/artifact.yaml create mode 100644 config/pkg.lib.yaml create mode 100644 src/Package/Artifact/attr.php create mode 100644 src/Package/Artifact/bzip2.php create mode 100644 src/Package/Library/attr.php create mode 100644 src/Package/Library/brotli.php create mode 100644 src/Package/Library/bzip2.php create mode 100644 src/globals/licenses/bzip2.txt diff --git a/config/artifact.yaml b/config/artifact.yaml new file mode 100644 index 000000000..7ed407363 --- /dev/null +++ b/config/artifact.yaml @@ -0,0 +1,33 @@ +attr: + source: + type: url + url: 'https://download.savannah.nongnu.org/releases/attr/attr-2.5.2.tar.gz' + source-mirror: + type: url + url: 'https://mirror.souseiseki.middlendian.com/nongnu/attr/attr-2.5.2.tar.gz' + metadata: + license-files: ['doc/COPYING.LGPL'] + license: LGPL-2.1-or-later + +brotli: + source: + type: ghtagtar + repo: google/brotli + match: 'v1\.\d.*' + binary: hosted # 等价于v2的provide-pre-built: true + metadata: + license-files: ['LICENSE'] + license: MIT + +bzip2: + source: + type: url + url: 'https://dl.static-php.dev/static-php-cli/deps/bzip2/bzip2-1.0.8.tar.gz' + source-mirror: + type: filelist + url: 'https://sourceware.org/pub/bzip2/' + regex: '/href="(?bzip2-(?[^"]+)\.tar\.gz)"/' + binary: hosted + metadata: + license-files: ['{registry_root}/src/globals/licenses/bzip2.txt'] + license: bzip2-1.0.6 diff --git a/config/pkg.lib.yaml b/config/pkg.lib.yaml new file mode 100644 index 000000000..baf4ebc9d --- /dev/null +++ b/config/pkg.lib.yaml @@ -0,0 +1,21 @@ +attr: + type: library + static-libs@unix: + - libattr.a + artifact: attr +brotli: + type: library + pkg-configs: + - libbrotlicommon + - libbrotlidec + - libbrotlienc + headers: + - brotli + artifact: brotli +bzip2: + type: library + static-libs@unix: + - libbz2.a + headers: + - bzlib.h + artifact: bzip2 diff --git a/spc.registry.json b/spc.registry.json index 7c5a8ce7b..3ced56564 100644 --- a/spc.registry.json +++ b/spc.registry.json @@ -12,13 +12,13 @@ }, "config": [ "config/pkg.ext.json", - "config/pkg.lib.json", + "config/pkg.lib.yaml", "config/pkg.target.json" ] }, "artifact": { "config": [ - "config/artifact.json" + "config/artifact.yaml" ], "psr-4": { "Package\\Artifact": "src/Package/Artifact" diff --git a/src/Package/Artifact/attr.php b/src/Package/Artifact/attr.php new file mode 100644 index 000000000..e80c8c831 --- /dev/null +++ b/src/Package/Artifact/attr.php @@ -0,0 +1,23 @@ +getSourceDir()); + } + } +} diff --git a/src/Package/Artifact/bzip2.php b/src/Package/Artifact/bzip2.php new file mode 100644 index 000000000..a6a1e58e5 --- /dev/null +++ b/src/Package/Artifact/bzip2.php @@ -0,0 +1,24 @@ +getSourceDir() . '/Makefile', + 'CFLAGS=-Wall', + 'CFLAGS=-fPIC -Wall' + ); + } +} diff --git a/src/Package/Library/attr.php b/src/Package/Library/attr.php new file mode 100644 index 000000000..40637e80a --- /dev/null +++ b/src/Package/Library/attr.php @@ -0,0 +1,29 @@ +appendEnv([ + 'CFLAGS' => '-Wno-int-conversion -Wno-implicit-function-declaration', + ]) + ->exec('libtoolize --force --copy') + ->exec('./autogen.sh || autoreconf -if') + ->configure('--disable-nls') + ->make('install-attributes_h install-data install-libattr_h install-libLTLIBRARIES install-pkgincludeHEADERS install-pkgconfDATA', with_install: false); + $lib->patchPkgconfPrefix(['libattr.pc'], PKGCONF_PATCH_PREFIX); + } +} diff --git a/src/Package/Library/brotli.php b/src/Package/Library/brotli.php new file mode 100644 index 000000000..f22b9ef29 --- /dev/null +++ b/src/Package/Library/brotli.php @@ -0,0 +1,55 @@ +setBuildDir($lib->getSourceDir() . '/build-dir') + ->addConfigureArgs("-DSHARE_INSTALL_PREFIX={$lib->getBuildRootPath()}") + ->build(); + + // Patch pkg-config files + $lib->patchPkgconfPrefix(['libbrotlicommon.pc', 'libbrotlidec.pc', 'libbrotlienc.pc'], PKGCONF_PATCH_PREFIX); + + // Add -lbrotlicommon to libbrotlidec.pc and libbrotlienc.pc + FileSystem::replaceFileLineContainsString( + $lib->getLibDir() . '/pkgconfig/libbrotlidec.pc', + 'Libs: -L${libdir} -lbrotlidec', + 'Libs: -L${libdir} -lbrotlidec -lbrotlicommon' + ); + FileSystem::replaceFileLineContainsString( + $lib->getLibDir() . '/pkgconfig/libbrotlienc.pc', + 'Libs: -L${libdir} -lbrotlienc', + 'Libs: -L${libdir} -lbrotlienc -lbrotlicommon' + ); + + // Create symlink: libbrotli.a -> libbrotlicommon.a + shell()->cd($lib->getLibDir())->exec('ln -sf libbrotlicommon.a libbrotli.a'); + + // Remove dynamic libraries + foreach (FileSystem::scanDirFiles($lib->getLibDir(), false, true) as $filename) { + if (str_starts_with($filename, 'libbrotli') && (str_contains($filename, '.so') || str_ends_with($filename, '.dylib'))) { + unlink($lib->getLibDir() . '/' . $filename); + } + } + + // Remove brotli binary if exists + if (file_exists($lib->getBinDir() . '/brotli')) { + unlink($lib->getBinDir() . '/brotli'); + } + } +} diff --git a/src/Package/Library/bzip2.php b/src/Package/Library/bzip2.php new file mode 100644 index 000000000..7f554ab48 --- /dev/null +++ b/src/Package/Library/bzip2.php @@ -0,0 +1,25 @@ +cd($lib->getSourceDir())->initializeEnv($lib) + ->exec("make PREFIX='{$lib->getBuildRootPath()}' clean") + ->exec("make -j{$builder->concurrency} PREFIX='{$lib->getBuildRootPath()}' libbz2.a") + ->exec('cp libbz2.a ' . $lib->getLibDir()) + ->exec('cp bzlib.h ' . $lib->getIncludeDir()); + } +} diff --git a/src/globals/licenses/bzip2.txt b/src/globals/licenses/bzip2.txt new file mode 100644 index 000000000..2f2ed1cd0 --- /dev/null +++ b/src/globals/licenses/bzip2.txt @@ -0,0 +1,14 @@ +This program, "bzip2", the associated library "libbzip2", and all documentation, are copyright (C) 1996-2010 Julian R Seward. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + 3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + 4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Julian Seward, jseward@bzip.org bzip2/libbzip2 version 1.0.6 of 6 September 2010 + +PATENTS: To the best of my knowledge, bzip2 and libbzip2 do not use any patented algorithms. However, I do not have the resources to carry out a patent search. Therefore I cannot give any guarantee of the above statement. From c27ed8b0b4990848d4676bb45d48c500ec92bb4b Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 22 Jan 2026 16:50:31 +0800 Subject: [PATCH 05/99] Implement fastlz, zlib (unix) --- config/artifact.yaml | 19 ++++++++++++++++++ config/pkg.lib.yaml | 17 ++++++++++++++++ src/Package/Library/fastlz.php | 36 ++++++++++++++++++++++++++++++++++ src/Package/Library/zlib.php | 24 +++++++++++++++++++++++ src/globals/licenses/zlib.txt | 20 +++++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 src/Package/Library/fastlz.php create mode 100644 src/Package/Library/zlib.php create mode 100644 src/globals/licenses/zlib.txt diff --git a/config/artifact.yaml b/config/artifact.yaml index 7ed407363..cfbb832d0 100644 --- a/config/artifact.yaml +++ b/config/artifact.yaml @@ -31,3 +31,22 @@ bzip2: metadata: license-files: ['{registry_root}/src/globals/licenses/bzip2.txt'] license: bzip2-1.0.6 + +fastlz: + source: + type: git + url: 'https://github.com/ariya/FastLZ.git' + rev: master + metadata: + license-files: ['LICENSE.MIT'] + license: MIT + +zlib: + source: + type: ghrel + repo: madler/zlib + match: 'zlib.+\.tar\.gz' + binary: hosted + metadata: + license-files: ['{registry_root}/src/globals/licenses/zlib.txt'] + license: Zlib-Custom diff --git a/config/pkg.lib.yaml b/config/pkg.lib.yaml index baf4ebc9d..f38a5512d 100644 --- a/config/pkg.lib.yaml +++ b/config/pkg.lib.yaml @@ -19,3 +19,20 @@ bzip2: headers: - bzlib.h artifact: bzip2 + +fastlz: + type: library + static-libs@unix: + - libfastlz.a + headers: + - fastlz.h + artifact: fastlz + +zlib: + type: library + static-libs@unix: + - libz.a + headers: + - zlib.h + - zconf.h + artifact: zlib diff --git a/src/Package/Library/fastlz.php b/src/Package/Library/fastlz.php new file mode 100644 index 000000000..f9dd010dc --- /dev/null +++ b/src/Package/Library/fastlz.php @@ -0,0 +1,36 @@ +cd($lib->getSourceDir())->initializeEnv($lib) + ->exec("{$cc} -c -O3 -fPIC fastlz.c -o fastlz.o") + ->exec("{$ar} rcs libfastlz.a fastlz.o"); + + // Copy header file + if (!copy($lib->getSourceDir() . '/fastlz.h', $lib->getIncludeDir() . '/fastlz.h')) { + throw new BuildFailureException('Failed to copy fastlz.h'); + } + + // Copy static library + if (!copy($lib->getSourceDir() . '/libfastlz.a', $lib->getLibDir() . '/libfastlz.a')) { + throw new BuildFailureException('Failed to copy libfastlz.a'); + } + } +} diff --git a/src/Package/Library/zlib.php b/src/Package/Library/zlib.php new file mode 100644 index 000000000..8706dfe9b --- /dev/null +++ b/src/Package/Library/zlib.php @@ -0,0 +1,24 @@ +exec("./configure --static --prefix={$lib->getBuildRootPath()}")->make(); + + // Patch pkg-config file + $lib->patchPkgconfPrefix(['zlib.pc'], PKGCONF_PATCH_PREFIX); + } +} diff --git a/src/globals/licenses/zlib.txt b/src/globals/licenses/zlib.txt new file mode 100644 index 000000000..b698f3434 --- /dev/null +++ b/src/globals/licenses/zlib.txt @@ -0,0 +1,20 @@ +(C) 1995-2022 Jean-loup Gailly and Mark Adler + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +Jean-loup Gailly Mark Adler +jloup@gzip.org madler@alumni.caltech.edu From 223dd10ac6bbe97934e0e4fa7648b655d6b71ca9 Mon Sep 17 00:00:00 2001 From: henderkes Date: Sat, 24 Jan 2026 20:26:16 +0100 Subject: [PATCH 06/99] fix spx shared libadd --- src/SPC/builder/Extension.php | 2 +- src/SPC/builder/extension/excimer.php | 19 ------------------- src/SPC/builder/extension/spx.php | 7 +++++++ 3 files changed, 8 insertions(+), 20 deletions(-) delete mode 100644 src/SPC/builder/extension/excimer.php diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index 925a4c8ef..f5a5d9561 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -542,7 +542,7 @@ public function getLibraryDependencies(bool $recursive = false): array */ protected function getSharedExtensionEnv(): array { - $config = (new SPCConfigUtil($this->builder))->getExtensionConfig($this); + $config = (new SPCConfigUtil($this->builder, ['no_php' => true]))->getExtensionConfig($this); [$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']); $preStatic = PHP_OS_FAMILY === 'Darwin' ? '' : '-Wl,--start-group '; $postStatic = PHP_OS_FAMILY === 'Darwin' ? '' : ' -Wl,--end-group '; diff --git a/src/SPC/builder/extension/excimer.php b/src/SPC/builder/extension/excimer.php deleted file mode 100644 index 03dd8f228..000000000 --- a/src/SPC/builder/extension/excimer.php +++ /dev/null @@ -1,19 +0,0 @@ -source_dir . '/src/php_spx.h', $this->source_dir . '/php_spx.h'); return true; } + + public function getSharedExtensionEnv(): array + { + $env = parent::getSharedExtensionEnv(); + $env['SPX_SHARED_LIBADD'] = $env['LIBS']; + return $env; + } } From a7092212233668189db51c623198e42be1ccd3b2 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 26 Jan 2026 00:43:57 +0800 Subject: [PATCH 07/99] Add skip helper function for calling events --- src/StaticPHP/DI/CallbackInvoker.php | 8 +++++++- src/StaticPHP/Exception/SkipException.php | 7 +++++++ src/globals/functions.php | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/StaticPHP/Exception/SkipException.php diff --git a/src/StaticPHP/DI/CallbackInvoker.php b/src/StaticPHP/DI/CallbackInvoker.php index 0d77f7aab..5b70d7b2a 100644 --- a/src/StaticPHP/DI/CallbackInvoker.php +++ b/src/StaticPHP/DI/CallbackInvoker.php @@ -5,6 +5,7 @@ namespace StaticPHP\DI; use DI\Container; +use StaticPHP\Exception\SkipException; use StaticPHP\Exception\SPCInternalException; /** @@ -92,7 +93,12 @@ public function invoke(callable $callback, array $context = []): mixed ); } - return $callback(...$args); + try { + return $callback(...$args); + } catch (SkipException $e) { + logger()->debug("Skipped invocation: {$e->getMessage()}"); + return null; + } } /** diff --git a/src/StaticPHP/Exception/SkipException.php b/src/StaticPHP/Exception/SkipException.php new file mode 100644 index 000000000..af25b6da5 --- /dev/null +++ b/src/StaticPHP/Exception/SkipException.php @@ -0,0 +1,7 @@ + Date: Mon, 26 Jan 2026 00:46:42 +0800 Subject: [PATCH 08/99] Add openssl lib support --- config/artifact.yaml | 15 ++++ config/pkg.lib.yaml | 10 +++ src/Package/Artifact/openssl.php | 35 +++++++++ src/Package/Library/openssl.php | 89 ++++++++++++++++++++++ src/StaticPHP/Package/LibraryPackage.php | 11 +++ src/StaticPHP/Package/Package.php | 16 ++++ src/StaticPHP/Package/PackageInstaller.php | 53 +++++++++++++ src/StaticPHP/Util/SPCConfigUtil.php | 25 +++--- 8 files changed, 243 insertions(+), 11 deletions(-) create mode 100644 src/Package/Artifact/openssl.php create mode 100644 src/Package/Library/openssl.php diff --git a/config/artifact.yaml b/config/artifact.yaml index cfbb832d0..d01d62670 100644 --- a/config/artifact.yaml +++ b/config/artifact.yaml @@ -41,6 +41,21 @@ fastlz: license-files: ['LICENSE.MIT'] license: MIT +openssl: + source: + type: ghrel + repo: openssl/openssl + match: 'openssl.+\.tar\.gz' + prefer-stable: true + source-mirror: + type: filelist + url: 'https://www.openssl.org/source/' + regex: '/href="(?openssl-(?[^"]+)\.tar\.gz)"/' + binary: hosted + metadata: + license-files: ['LICENSE.txt'] + license: OpenSSL + zlib: source: type: ghrel diff --git a/config/pkg.lib.yaml b/config/pkg.lib.yaml index f38a5512d..7c4df0005 100644 --- a/config/pkg.lib.yaml +++ b/config/pkg.lib.yaml @@ -28,6 +28,16 @@ fastlz: - fastlz.h artifact: fastlz +openssl: + type: library + static-libs@unix: + - libssl.a + - libcrypto.a + headers: ['openssl'] + depends: + - zlib + artifact: openssl + zlib: type: library static-libs@unix: diff --git a/src/Package/Artifact/openssl.php b/src/Package/Artifact/openssl.php new file mode 100644 index 000000000..8809d9ce0 --- /dev/null +++ b/src/Package/Artifact/openssl.php @@ -0,0 +1,35 @@ +', + '#include ' . PHP_EOL . '#include ' + ); + } +} diff --git a/src/Package/Library/openssl.php b/src/Package/Library/openssl.php new file mode 100644 index 000000000..e0ee9edc3 --- /dev/null +++ b/src/Package/Library/openssl.php @@ -0,0 +1,89 @@ +getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles(); + $arch = getenv('SPC_ARCH'); + + shell()->cd($pkg->getSourceDir())->initializeEnv($pkg) + ->exec( + './Configure no-shared zlib ' . + "--prefix={$pkg->getBuildRootPath()} " . + '--libdir=lib ' . + '--openssldir=/etc/ssl ' . + "darwin64-{$arch}-cc" + ) + ->exec('make clean') + ->exec("make -j{$pkg->getBuilder()->concurrency} CNF_EX_LIBS=\"{$zlib_libs}\"") + ->exec('make install_sw'); + $this->patchPkgConfig($pkg); + } + + #[BuildFor('Linux')] + public function build(LibraryPackage $lib): void + { + $arch = getenv('SPC_ARCH'); + + $env = "CC='" . getenv('CC') . ' -idirafter ' . BUILD_INCLUDE_PATH . + ' -idirafter /usr/include/ ' . + ' -idirafter /usr/include/' . getenv('SPC_ARCH') . '-linux-gnu/ ' . + "' "; + + $ex_lib = trim($lib->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles()) . ' -ldl -pthread'; + $zlib_extra = + '--with-zlib-include=' . BUILD_INCLUDE_PATH . ' ' . + '--with-zlib-lib=' . BUILD_LIB_PATH . ' '; + + $openssl_dir = getenv('OPENSSLDIR') ?: null; + // TODO: in v3 use the following: $openssl_dir ??= SystemUtil::getOSRelease()['dist'] === 'redhat' ? '/etc/pki/tls' : '/etc/ssl'; + $openssl_dir ??= '/etc/ssl'; + $ex_lib = trim($ex_lib); + + shell()->cd($lib->getSourceDir())->initializeEnv($lib) + ->exec( + "{$env} ./Configure no-shared zlib " . + "--prefix={$lib->getBuildRootPath()} " . + "--libdir={$lib->getLibDir()} " . + "--openssldir={$openssl_dir} " . + "{$zlib_extra}" . + 'enable-pie ' . + 'no-legacy ' . + 'no-tests ' . + "linux-{$arch}" + ) + ->exec('make clean') + ->exec("make -j{$lib->getBuilder()->concurrency} CNF_EX_LIBS=\"{$ex_lib}\"") + ->exec('make install_sw'); + $this->patchPkgConfig($lib); + } + + private function patchPkgConfig(LibraryPackage $pkg): void + { + $pkg->patchPkgconfPrefix(['libssl.pc', 'openssl.pc', 'libcrypto.pc']); + // patch for openssl 3.3.0+ + if (!str_contains($file = FileSystem::readFile("{$pkg->getLibDir()}/pkgconfig/libssl.pc"), 'prefix=')) { + FileSystem::writeFile("{$pkg->getLibDir()}/pkgconfig/libssl.pc", "prefix={$pkg->getBuildRootPath()}\n{$file}"); + } + if (!str_contains($file = FileSystem::readFile("{$pkg->getLibDir()}/pkgconfig/openssl.pc"), 'prefix=')) { + FileSystem::writeFile("{$pkg->getLibDir()}/pkgconfig/openssl.pc", "prefix={$pkg->getBuildRootPath()}\n{$file}"); + } + if (!str_contains($file = FileSystem::readFile("{$pkg->getLibDir()}/pkgconfig/libcrypto.pc"), 'prefix=')) { + FileSystem::writeFile("{$pkg->getLibDir()}/pkgconfig/libcrypto.pc", "prefix={$pkg->getBuildRootPath()}\n{$file}"); + } + FileSystem::replaceFileRegex("{$pkg->getLibDir()}/pkgconfig/libcrypto.pc", '/Libs.private:.*/m', 'Requires.private: zlib'); + FileSystem::replaceFileRegex("{$pkg->getLibDir()}/cmake/OpenSSL/OpenSSLConfig.cmake", '/set\(OPENSSL_LIBCRYPTO_DEPENDENCIES .*\)/m', 'set(OPENSSL_LIBCRYPTO_DEPENDENCIES "${OPENSSL_LIBRARY_DIR}/libz.a")'); + } +} diff --git a/src/StaticPHP/Package/LibraryPackage.php b/src/StaticPHP/Package/LibraryPackage.php index 97ec80077..1cdd229e0 100644 --- a/src/StaticPHP/Package/LibraryPackage.php +++ b/src/StaticPHP/Package/LibraryPackage.php @@ -7,6 +7,7 @@ use StaticPHP\Config\PackageConfig; use StaticPHP\Exception\PatchException; use StaticPHP\Util\FileSystem; +use StaticPHP\Util\SPCConfigUtil; /** * Represents a library package with platform-specific build functions. @@ -159,6 +160,16 @@ public function patchPkgconfPrefix(array $files = [], int $patch_option = PKGCON } } + /** + * Get static library files for current package and its dependencies. + */ + public function getStaticLibFiles(): string + { + $config = new SPCConfigUtil(['libs_only_deps' => true, 'absolute_libs' => true]); + $res = $config->config([$this->getName()]); + return $res['libs']; + } + /** * Get extra LIBS for current package. * You need to define the environment variable in the format of {LIBRARY_NAME}_LIBS diff --git a/src/StaticPHP/Package/Package.php b/src/StaticPHP/Package/Package.php index aa8ab6f00..943d03b0d 100644 --- a/src/StaticPHP/Package/Package.php +++ b/src/StaticPHP/Package/Package.php @@ -135,6 +135,22 @@ public function hasBuildFunctionForCurrentOS(): bool return isset($this->build_functions[PHP_OS_FAMILY]); } + /** + * Get the PackageBuilder instance for this package. + */ + public function getBuilder(): PackageBuilder + { + return ApplicationContext::get(PackageBuilder::class); + } + + /** + * Get the PackageInstaller instance for this package. + */ + public function getInstaller(): PackageInstaller + { + return ApplicationContext::get(PackageInstaller::class); + } + /** * Get the name of the package. */ diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index ae3b7346a..c4262d9a3 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -402,11 +402,64 @@ public function installBinary(Package $package): int return SPC_STATUS_INSTALLED; } + /** + * @internal internally calling only, for users, please use specific getter, such as 'getLibraryPackage', 'getTaretPackage', etc + * @param string $package_name Package name + */ public function getPackage(string $package_name): ?Package { return $this->packages[$package_name] ?? null; } + /** + * Get a library package by name. + * + * @param string $package_name Package name + * @return null|LibraryPackage The library package instance or null if not found + */ + public function getLibraryPackage(string $package_name): ?LibraryPackage + { + $pkg = $this->getPackage($package_name); + if ($pkg instanceof LibraryPackage) { + return $pkg; + } + return null; + } + + /** + * Get a target package by name. + * + * @param string $package_name Package name + * @return null|TargetPackage The target package instance or null if not found + */ + public function getTargetPackage(string $package_name): ?TargetPackage + { + $pkg = $this->getPackage($package_name); + if ($pkg instanceof TargetPackage) { + return $pkg; + } + return null; + } + + /** + * Get a PHP extension by name. + * + * @param string $package_or_ext_name Extension name + * @return null|PhpExtensionPackage The target package instance or null if not found + */ + public function getPhpExtensionPackage(string $package_or_ext_name): ?PhpExtensionPackage + { + $pkg = $this->getPackage($package_or_ext_name); + if ($pkg instanceof PhpExtensionPackage) { + return $pkg; + } + $pkg = $this->getPackage("ext-{$package_or_ext_name}"); + if ($pkg instanceof PhpExtensionPackage) { + return $pkg; + } + return null; + } + /** * Validate that a package has required artifacts. */ diff --git a/src/StaticPHP/Util/SPCConfigUtil.php b/src/StaticPHP/Util/SPCConfigUtil.php index c525f8c79..3a20e56fe 100644 --- a/src/StaticPHP/Util/SPCConfigUtil.php +++ b/src/StaticPHP/Util/SPCConfigUtil.php @@ -209,18 +209,21 @@ private function getLibsString(array $packages, bool $use_short_libs = true): st $frameworks = []; foreach ($packages as $package) { - // add pkg-configs libs - $pkg_configs = PackageConfig::get($package, 'pkg-configs', []); - foreach ($pkg_configs as $pkg_config) { - if (!file_exists(BUILD_LIB_PATH . "/pkgconfig/{$pkg_config}.pc")) { - throw new WrongUsageException("pkg-config file '{$pkg_config}.pc' for lib [{$package}] does not exist in '" . BUILD_LIB_PATH . "/pkgconfig'. Please build it first."); + // parse pkg-configs only for unix systems + if (SystemTarget::isUnix()) { + // add pkg-configs libs + $pkg_configs = PackageConfig::get($package, 'pkg-configs', []); + foreach ($pkg_configs as $pkg_config) { + if (!file_exists(BUILD_LIB_PATH . "/pkgconfig/{$pkg_config}.pc")) { + throw new WrongUsageException("pkg-config file '{$pkg_config}.pc' for lib [{$package}] does not exist in '" . BUILD_LIB_PATH . "/pkgconfig'. Please build it first."); + } + } + $pkg_configs = implode(' ', $pkg_configs); + if ($pkg_configs !== '') { + // static libs with dependencies come in reverse order, so reverse this too + $pc_libs = array_reverse(PkgConfigUtil::getLibsArray($pkg_configs)); + $lib_names = [...$lib_names, ...$pc_libs]; } - } - $pkg_configs = implode(' ', $pkg_configs); - if ($pkg_configs !== '') { - // static libs with dependencies come in reverse order, so reverse this too - $pc_libs = array_reverse(PkgConfigUtil::getLibsArray($pkg_configs)); - $lib_names = [...$lib_names, ...$pc_libs]; } // convert all static-libs to short names $libs = array_reverse(PackageConfig::get($package, 'static-libs', [])); From 3a575f0bf7f1a6487beef0fd69f10df76a38f8e7 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 26 Jan 2026 00:50:39 +0800 Subject: [PATCH 09/99] Use yml instead of yaml (sync with craft) --- config/{artifact.yaml => artifact.yml} | 0 config/{pkg.lib.yaml => pkg.lib.yml} | 0 spc.registry.json | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename config/{artifact.yaml => artifact.yml} (100%) rename config/{pkg.lib.yaml => pkg.lib.yml} (100%) diff --git a/config/artifact.yaml b/config/artifact.yml similarity index 100% rename from config/artifact.yaml rename to config/artifact.yml diff --git a/config/pkg.lib.yaml b/config/pkg.lib.yml similarity index 100% rename from config/pkg.lib.yaml rename to config/pkg.lib.yml diff --git a/spc.registry.json b/spc.registry.json index 3ced56564..0e4720aa5 100644 --- a/spc.registry.json +++ b/spc.registry.json @@ -12,13 +12,13 @@ }, "config": [ "config/pkg.ext.json", - "config/pkg.lib.yaml", + "config/pkg.lib.yml", "config/pkg.target.json" ] }, "artifact": { "config": [ - "config/artifact.yaml" + "config/artifact.yml" ], "psr-4": { "Package\\Artifact": "src/Package/Artifact" From 51415fb6bf1771a72687028c4759918f90925add Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 26 Jan 2026 00:50:53 +0800 Subject: [PATCH 10/99] Use shorter summary json file name --- src/StaticPHP/Util/LicenseDumper.php | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/StaticPHP/Util/LicenseDumper.php b/src/StaticPHP/Util/LicenseDumper.php index 57fc3aec4..f45c395a7 100644 --- a/src/StaticPHP/Util/LicenseDumper.php +++ b/src/StaticPHP/Util/LicenseDumper.php @@ -63,7 +63,7 @@ public function dump(string $target_dir): bool } } - // Generate LICENSE-SUMMARY.json (read-modify-write) + // Generate SUMMARY.json (read-modify-write) $this->generateSummary($target_dir, $license_summary); logger()->info("Successfully dumped {$dumped_count} license(s) to: {$target_dir}"); @@ -190,7 +190,7 @@ private function readLicenseFile(Artifact $artifact, string $license_file_path): } /** - * Generate LICENSE-SUMMARY.json file with read-modify-write support. + * Generate SUMMARY.json file with read-modify-write support. * * @param string $target_dir Target directory * @param array $license_summary License summary data (license_type => [artifacts]) @@ -202,14 +202,14 @@ private function generateSummary(string $target_dir, array $license_summary): vo return; } - $summary_file = "{$target_dir}/LICENSE-SUMMARY.json"; + $summary_file = "{$target_dir}/SUMMARY.json"; // Read existing summary if exists $existing_data = []; if (file_exists($summary_file)) { $content = file_get_contents($summary_file); $existing_data = json_decode($content, true) ?? []; - logger()->debug('Loaded existing LICENSE-SUMMARY.json'); + logger()->debug('Loaded existing SUMMARY.json'); } // Initialize structure @@ -217,7 +217,7 @@ private function generateSummary(string $target_dir, array $license_summary): vo $existing_data['artifacts'] = []; } if (!isset($existing_data['summary'])) { - $existing_data['summary'] = ['license_types' => []]; + $existing_data['summary'] = ['license-types' => []]; } // Merge new license information @@ -226,30 +226,30 @@ private function generateSummary(string $target_dir, array $license_summary): vo // Add/update artifact info $existing_data['artifacts'][$artifact_name] = [ 'license' => $license_type, - 'dumped_at' => date('Y-m-d H:i:s'), + 'dumped-at' => date('Y-m-d H:i:s'), ]; // Update license_types summary - if (!isset($existing_data['summary']['license_types'][$license_type])) { - $existing_data['summary']['license_types'][$license_type] = []; + if (!isset($existing_data['summary']['license-types'][$license_type])) { + $existing_data['summary']['license-types'][$license_type] = []; } - if (!in_array($artifact_name, $existing_data['summary']['license_types'][$license_type])) { - $existing_data['summary']['license_types'][$license_type][] = $artifact_name; + if (!in_array($artifact_name, $existing_data['summary']['license-types'][$license_type])) { + $existing_data['summary']['license-types'][$license_type][] = $artifact_name; } } } // Sort license types and artifacts - ksort($existing_data['summary']['license_types']); - foreach ($existing_data['summary']['license_types'] as &$artifacts) { + ksort($existing_data['summary']['license-types']); + foreach ($existing_data['summary']['license-types'] as &$artifacts) { sort($artifacts); } ksort($existing_data['artifacts']); // Update totals - $existing_data['summary']['total_artifacts'] = count($existing_data['artifacts']); - $existing_data['summary']['total_license_types'] = count($existing_data['summary']['license_types']); - $existing_data['summary']['last_updated'] = date('Y-m-d H:i:s'); + $existing_data['summary']['total-artifacts'] = count($existing_data['artifacts']); + $existing_data['summary']['total-license-types'] = count($existing_data['summary']['license-types']); + $existing_data['summary']['last-updated'] = date('Y-m-d H:i:s'); // Write JSON file file_put_contents( @@ -257,6 +257,6 @@ private function generateSummary(string $target_dir, array $license_summary): vo json_encode($existing_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ); - logger()->info('Generated LICENSE-SUMMARY.json with ' . $existing_data['summary']['total_artifacts'] . ' artifact(s)'); + logger()->info('Generated SUMMARY.json with ' . $existing_data['summary']['total-artifacts'] . ' artifact(s)'); } } From 4531c9fe57d54062c499c007687c412df13e8451 Mon Sep 17 00:00:00 2001 From: henderkes Date: Tue, 27 Jan 2026 00:57:58 +0100 Subject: [PATCH 11/99] add option to allow linking musl dynamically on alpine --- config/env.ini | 4 +++- src/SPC/util/SPCTarget.php | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config/env.ini b/config/env.ini index c1d4dbc4a..a16bc160f 100644 --- a/config/env.ini +++ b/config/env.ini @@ -75,8 +75,10 @@ SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime ; - musl-native: used for alpine linux, can build `musl` and `musl -dynamic` target. ; - gnu-native: used for general linux distros, can build gnu target for the installed glibc version only. -; LEGACY option to specify the target +; option to specify the target, superceded by SPC_TARGET if set SPC_LIBC=musl +; uncomment to link libc dynamically on musl +; SPC_MUSL_DYNAMIC=true ; Recommended: specify your target here. Zig toolchain will be used. ; examples: diff --git a/src/SPC/util/SPCTarget.php b/src/SPC/util/SPCTarget.php index b8f5367ae..037c6d8c3 100644 --- a/src/SPC/util/SPCTarget.php +++ b/src/SPC/util/SPCTarget.php @@ -27,10 +27,10 @@ public static function isStatic(): bool return true; } if (ToolchainManager::getToolchainClass() === GccNativeToolchain::class) { - return PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist(); + return PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist() && !getenv('SPC_MUSL_DYNAMIC'); } if (ToolchainManager::getToolchainClass() === ClangNativeToolchain::class) { - return PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist(); + return PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist() && !getenv('SPC_MUSL_DYNAMIC'); } // if SPC_LIBC is set, it means the target is static, remove it when 3.0 is released if ($target = getenv('SPC_TARGET')) { From c5882c1f8e2d95643c50e29740209c1ea9e8dae8 Mon Sep 17 00:00:00 2001 From: henderkes Date: Fri, 30 Jan 2026 19:41:39 +0100 Subject: [PATCH 12/99] fix gettext v1.0 release --- src/SPC/builder/unix/library/gettext.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/library/gettext.php b/src/SPC/builder/unix/library/gettext.php index 332e25c96..d383faf77 100644 --- a/src/SPC/builder/unix/library/gettext.php +++ b/src/SPC/builder/unix/library/gettext.php @@ -16,7 +16,11 @@ protected function build(): void ->addConfigureArgs( '--disable-java', '--disable-c++', - '--with-included-gettext', + '--disable-d', + '--disable-rpath', + '--disable-modula2', + '--disable-libasprintf', + '--with-included-libintl', "--with-iconv-prefix={$this->getBuildRootPath()}", ); From a414c65f372f376f8d716fded13de53f2165a940 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 09:59:10 +0800 Subject: [PATCH 13/99] Forward-port #1022 spc target env var --- src/StaticPHP/Toolchain/ClangNativeToolchain.php | 2 +- src/StaticPHP/Toolchain/GccNativeToolchain.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StaticPHP/Toolchain/ClangNativeToolchain.php b/src/StaticPHP/Toolchain/ClangNativeToolchain.php index c34e619cd..2513dd714 100644 --- a/src/StaticPHP/Toolchain/ClangNativeToolchain.php +++ b/src/StaticPHP/Toolchain/ClangNativeToolchain.php @@ -52,6 +52,6 @@ public function getCompilerInfo(): ?string public function isStatic(): bool { - return PHP_OS_FAMILY === 'Linux' && LinuxUtil::isMuslDist(); + return PHP_OS_FAMILY === 'Linux' && LinuxUtil::isMuslDist() && !getenv('SPC_MUSL_DYNAMIC'); } } diff --git a/src/StaticPHP/Toolchain/GccNativeToolchain.php b/src/StaticPHP/Toolchain/GccNativeToolchain.php index 92b82892e..dbf9925ec 100644 --- a/src/StaticPHP/Toolchain/GccNativeToolchain.php +++ b/src/StaticPHP/Toolchain/GccNativeToolchain.php @@ -49,6 +49,6 @@ public function getCompilerInfo(): ?string public function isStatic(): bool { - return PHP_OS_FAMILY === 'Linux' && LinuxUtil::isMuslDist(); + return PHP_OS_FAMILY === 'Linux' && LinuxUtil::isMuslDist() && !getenv('SPC_MUSL_DYNAMIC'); } } From 455d42d1628f63280be3206d3a48211a83715899 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 13:32:35 +0800 Subject: [PATCH 14/99] Separate package config --- README-en.md | 3 - config/artifact.yaml | 67 -------------------- config/pkg.lib.yaml | 48 -------------- config/pkg/lib/attr.yml | 14 ++++ config/pkg/lib/brotli.yml | 17 +++++ config/pkg/lib/bzip2.yml | 18 ++++++ config/pkg/lib/fastlz.yml | 14 ++++ config/pkg/lib/gmp.yml | 19 ++++++ config/pkg/lib/openssl.yml | 22 +++++++ config/pkg/lib/zlib.yml | 16 +++++ spc.registry.json | 7 +- src/Package/Artifact/openssl.php | 4 +- src/Package/Library/gmp.php | 25 ++++++++ src/StaticPHP/Config/ArtifactConfig.php | 15 +++++ src/StaticPHP/Config/ConfigValidator.php | 42 +++++++++++- src/StaticPHP/Config/PackageConfig.php | 31 ++++++++- src/StaticPHP/Doctor/Item/LinuxToolCheck.php | 5 +- src/StaticPHP/Package/Package.php | 17 ++++- src/StaticPHP/Registry/Registry.php | 48 ++++++++------ src/StaticPHP/Util/LicenseDumper.php | 18 ++++-- src/globals/licenses/gmp.txt | 1 + 21 files changed, 294 insertions(+), 157 deletions(-) delete mode 100755 README-en.md delete mode 100644 config/artifact.yaml delete mode 100644 config/pkg.lib.yaml create mode 100644 config/pkg/lib/attr.yml create mode 100644 config/pkg/lib/brotli.yml create mode 100644 config/pkg/lib/bzip2.yml create mode 100644 config/pkg/lib/fastlz.yml create mode 100644 config/pkg/lib/gmp.yml create mode 100644 config/pkg/lib/openssl.yml create mode 100644 config/pkg/lib/zlib.yml create mode 100644 src/Package/Library/gmp.php create mode 100644 src/globals/licenses/gmp.txt diff --git a/README-en.md b/README-en.md deleted file mode 100755 index 505fd70a2..000000000 --- a/README-en.md +++ /dev/null @@ -1,3 +0,0 @@ -# static-php-cli - -English README has been moved to [README.md](README.md). diff --git a/config/artifact.yaml b/config/artifact.yaml deleted file mode 100644 index d01d62670..000000000 --- a/config/artifact.yaml +++ /dev/null @@ -1,67 +0,0 @@ -attr: - source: - type: url - url: 'https://download.savannah.nongnu.org/releases/attr/attr-2.5.2.tar.gz' - source-mirror: - type: url - url: 'https://mirror.souseiseki.middlendian.com/nongnu/attr/attr-2.5.2.tar.gz' - metadata: - license-files: ['doc/COPYING.LGPL'] - license: LGPL-2.1-or-later - -brotli: - source: - type: ghtagtar - repo: google/brotli - match: 'v1\.\d.*' - binary: hosted # 等价于v2的provide-pre-built: true - metadata: - license-files: ['LICENSE'] - license: MIT - -bzip2: - source: - type: url - url: 'https://dl.static-php.dev/static-php-cli/deps/bzip2/bzip2-1.0.8.tar.gz' - source-mirror: - type: filelist - url: 'https://sourceware.org/pub/bzip2/' - regex: '/href="(?bzip2-(?[^"]+)\.tar\.gz)"/' - binary: hosted - metadata: - license-files: ['{registry_root}/src/globals/licenses/bzip2.txt'] - license: bzip2-1.0.6 - -fastlz: - source: - type: git - url: 'https://github.com/ariya/FastLZ.git' - rev: master - metadata: - license-files: ['LICENSE.MIT'] - license: MIT - -openssl: - source: - type: ghrel - repo: openssl/openssl - match: 'openssl.+\.tar\.gz' - prefer-stable: true - source-mirror: - type: filelist - url: 'https://www.openssl.org/source/' - regex: '/href="(?openssl-(?[^"]+)\.tar\.gz)"/' - binary: hosted - metadata: - license-files: ['LICENSE.txt'] - license: OpenSSL - -zlib: - source: - type: ghrel - repo: madler/zlib - match: 'zlib.+\.tar\.gz' - binary: hosted - metadata: - license-files: ['{registry_root}/src/globals/licenses/zlib.txt'] - license: Zlib-Custom diff --git a/config/pkg.lib.yaml b/config/pkg.lib.yaml deleted file mode 100644 index 7c4df0005..000000000 --- a/config/pkg.lib.yaml +++ /dev/null @@ -1,48 +0,0 @@ -attr: - type: library - static-libs@unix: - - libattr.a - artifact: attr -brotli: - type: library - pkg-configs: - - libbrotlicommon - - libbrotlidec - - libbrotlienc - headers: - - brotli - artifact: brotli -bzip2: - type: library - static-libs@unix: - - libbz2.a - headers: - - bzlib.h - artifact: bzip2 - -fastlz: - type: library - static-libs@unix: - - libfastlz.a - headers: - - fastlz.h - artifact: fastlz - -openssl: - type: library - static-libs@unix: - - libssl.a - - libcrypto.a - headers: ['openssl'] - depends: - - zlib - artifact: openssl - -zlib: - type: library - static-libs@unix: - - libz.a - headers: - - zlib.h - - zconf.h - artifact: zlib diff --git a/config/pkg/lib/attr.yml b/config/pkg/lib/attr.yml new file mode 100644 index 000000000..4e7858513 --- /dev/null +++ b/config/pkg/lib/attr.yml @@ -0,0 +1,14 @@ +attr: + type: library + static-libs@unix: + - libattr.a + artifact: + source: + type: url + url: 'https://download.savannah.nongnu.org/releases/attr/attr-2.5.2.tar.gz' + source-mirror: + type: url + url: 'https://mirror.souseiseki.middlendian.com/nongnu/attr/attr-2.5.2.tar.gz' + metadata: + license-files: ['doc/COPYING.LGPL'] + license: LGPL-2.1-or-later diff --git a/config/pkg/lib/brotli.yml b/config/pkg/lib/brotli.yml new file mode 100644 index 000000000..342359aab --- /dev/null +++ b/config/pkg/lib/brotli.yml @@ -0,0 +1,17 @@ +brotli: + type: library + pkg-configs: + - libbrotlicommon + - libbrotlidec + - libbrotlienc + headers: + - brotli + artifact: + source: + type: ghtagtar + repo: google/brotli + match: 'v1\.\d.*' + binary: hosted + metadata: + license-files: ['LICENSE'] + license: MIT diff --git a/config/pkg/lib/bzip2.yml b/config/pkg/lib/bzip2.yml new file mode 100644 index 000000000..6f7b483b5 --- /dev/null +++ b/config/pkg/lib/bzip2.yml @@ -0,0 +1,18 @@ +bzip2: + type: library + static-libs@unix: + - libbz2.a + headers: + - bzlib.h + artifact: + source: + type: url + url: 'https://dl.static-php.dev/static-php-cli/deps/bzip2/bzip2-1.0.8.tar.gz' + source-mirror: + type: filelist + url: 'https://sourceware.org/pub/bzip2/' + regex: '/href="(?bzip2-(?[^"]+)\.tar\.gz)"/' + binary: hosted + metadata: + license-files: ['{registry_root}/src/globals/licenses/bzip2.txt'] + license: bzip2-1.0.6 \ No newline at end of file diff --git a/config/pkg/lib/fastlz.yml b/config/pkg/lib/fastlz.yml new file mode 100644 index 000000000..32f26d8a0 --- /dev/null +++ b/config/pkg/lib/fastlz.yml @@ -0,0 +1,14 @@ +fastlz: + type: library + static-libs@unix: + - libfastlz.a + headers: + - fastlz.h + artifact: + source: + type: git + url: 'https://github.com/ariya/FastLZ.git' + rev: master + metadata: + license-files: ['LICENSE.MIT'] + license: MIT \ No newline at end of file diff --git a/config/pkg/lib/gmp.yml b/config/pkg/lib/gmp.yml new file mode 100644 index 000000000..8a28069b8 --- /dev/null +++ b/config/pkg/lib/gmp.yml @@ -0,0 +1,19 @@ +gmp: + type: library + static-libs@unix: + - libgmp.a + headers: + - gmp.h + pkg-configs: + - gmp + artifact: + source: + type: filelist + url: 'https://gmplib.org/download/gmp/' + regex: '/href="(?gmp-(?[^"]+)\.tar\.xz)"/' + source-mirror: + type: url + url: 'https://dl.static-php.dev/static-php-cli/deps/gmp/gmp-6.3.0.tar.xz' + metadata: + license-files: ['@/gmp.txt'] + license: Custom diff --git a/config/pkg/lib/openssl.yml b/config/pkg/lib/openssl.yml new file mode 100644 index 000000000..ce83a4d41 --- /dev/null +++ b/config/pkg/lib/openssl.yml @@ -0,0 +1,22 @@ +openssl: + type: library + static-libs@unix: + - libssl.a + - libcrypto.a + headers: ['openssl'] + depends: + - zlib + artifact: + source: + type: ghrel + repo: openssl/openssl + match: 'openssl.+\.tar\.gz' + prefer-stable: true + source-mirror: + type: filelist + url: 'https://www.openssl.org/source/' + regex: '/href="(?openssl-(?[^"]+)\.tar\.gz)"/' + binary: hosted + metadata: + license-files: ['LICENSE.txt'] + license: OpenSSL diff --git a/config/pkg/lib/zlib.yml b/config/pkg/lib/zlib.yml new file mode 100644 index 000000000..0a7eec716 --- /dev/null +++ b/config/pkg/lib/zlib.yml @@ -0,0 +1,16 @@ +zlib: + type: library + static-libs@unix: + - libz.a + headers: + - zlib.h + - zconf.h + artifact: + source: + type: ghrel + repo: madler/zlib + match: 'zlib.+\.tar\.gz' + binary: hosted + metadata: + license-files: ['{registry_root}/src/globals/licenses/zlib.txt'] + license: Zlib-Custom \ No newline at end of file diff --git a/spc.registry.json b/spc.registry.json index 3ced56564..e8be4b5d8 100644 --- a/spc.registry.json +++ b/spc.registry.json @@ -1,5 +1,5 @@ { - "name": "internal", + "name": "core", "autoload": "vendor/autoload.php", "doctor": { "psr-4": { @@ -12,14 +12,11 @@ }, "config": [ "config/pkg.ext.json", - "config/pkg.lib.yaml", + "config/pkg/lib", "config/pkg.target.json" ] }, "artifact": { - "config": [ - "config/artifact.yaml" - ], "psr-4": { "Package\\Artifact": "src/Package/Artifact" } diff --git a/src/Package/Artifact/openssl.php b/src/Package/Artifact/openssl.php index 8809d9ce0..325e6a09e 100644 --- a/src/Package/Artifact/openssl.php +++ b/src/Package/Artifact/openssl.php @@ -27,9 +27,9 @@ public function patchOpenssl11Darwin(string $target_path): void spc_skip_if(!file_exists("{$target_path}/openssl/test/v3ext.c"), 'v3ext.c not found, skipping patch.'); FileSystem::replaceFileStr( - SOURCE_PATH . '/openssl/test/v3ext.c', + "{$target_path}/openssl/test/v3ext.c", '#include ', - '#include ' . PHP_EOL . '#include ' + "#include \n#include " ); } } diff --git a/src/Package/Library/gmp.php b/src/Package/Library/gmp.php new file mode 100644 index 000000000..f8eb5f8e4 --- /dev/null +++ b/src/Package/Library/gmp.php @@ -0,0 +1,25 @@ +appendEnv(['CFLAGS' => '-std=c17']) + ->configure('--enable-fat') + ->make(); + $lib->patchPkgconfPrefix(['gmp.pc']); + } +} diff --git a/src/StaticPHP/Config/ArtifactConfig.php b/src/StaticPHP/Config/ArtifactConfig.php index 0ae9e2849..49bfec233 100644 --- a/src/StaticPHP/Config/ArtifactConfig.php +++ b/src/StaticPHP/Config/ArtifactConfig.php @@ -77,4 +77,19 @@ public static function get(string $artifact_name): ?array { return self::$artifact_configs[$artifact_name] ?? null; } + + /** + * Register an inline artifact configuration. + * Used when artifact is defined inline within a package configuration. + * + * @param string $artifact_name Artifact name (usually same as package name) + * @param array $config Artifact configuration + * @param string $registry_name Registry name + * @param string $source_info Source info for debugging + */ + public static function registerInlineArtifact(string $artifact_name, array $config, string $registry_name, string $source_info = 'inline'): void + { + self::$artifact_configs[$artifact_name] = $config; + Registry::_bindArtifactConfigFile($artifact_name, $registry_name, $source_info); + } } diff --git a/src/StaticPHP/Config/ConfigValidator.php b/src/StaticPHP/Config/ConfigValidator.php index b32f41063..1f3d5b9d9 100644 --- a/src/StaticPHP/Config/ConfigValidator.php +++ b/src/StaticPHP/Config/ConfigValidator.php @@ -18,7 +18,7 @@ class ConfigValidator 'type' => ConfigType::STRING, 'depends' => ConfigType::LIST_ARRAY, // @ 'suggests' => ConfigType::LIST_ARRAY, // @ - 'artifact' => ConfigType::STRING, + 'artifact' => [self::class, 'validateArtifactField'], // STRING or OBJECT 'license' => [ConfigType::class, 'validateLicenseField'], 'lang' => ConfigType::STRING, 'frameworks' => ConfigType::LIST_ARRAY, // @ @@ -102,7 +102,14 @@ public static function validateAndLintArtifacts(string $config_file_name, mixed if (!is_array($data)) { throw new ValidationException("{$config_file_name} is broken"); } + + // Define allowed artifact fields + $allowed_artifact_fields = ['source', 'source-mirror', 'binary', 'binary-mirror', 'metadata']; + foreach ($data as $name => $artifact) { + // First pass: validate unknown fields + self::validateNoInvalidFields('artifact', $name, $artifact, $allowed_artifact_fields); + foreach ($artifact as $k => $v) { // check source field if ($k === 'source' || $k === 'source-mirror') { @@ -202,6 +209,11 @@ public static function validateAndLintPackages(string $config_file_name, mixed & throw new ValidationException("Package [{$name}] in {$config_file_name} of type '{$pkg['type']}' must have an 'artifact' field"); } + // validate and lint inline artifact object if present + if (isset($pkg['artifact']) && is_array($pkg['artifact'])) { + self::validateAndLintInlineArtifact($name, $data[$name]['artifact']); + } + // check if "php-extension" package has php-extension specific fields and validate inside if ($pkg['type'] === 'php-extension') { self::validatePhpExtensionFields($name, $pkg); @@ -234,6 +246,19 @@ public static function validatePlatformString(string $platform): void } } + /** + * Validate artifact field - can be string (reference) or object (inline). + * + * @param mixed $value Field value + */ + public static function validateArtifactField(mixed $value): bool + { + if (!is_string($value) && !is_assoc_array($value)) { + return false; + } + return true; + } + /** * Validate an artifact download object field. * @@ -373,4 +398,19 @@ private static function validateNoInvalidFields(string $config_type, int|string } } } + + /** + * Validate and lint inline artifact object structure. + * + * @param string $pkg_name Package name + * @param array $artifact Inline artifact configuration (passed by reference to apply linting) + */ + private static function validateAndLintInlineArtifact(string $pkg_name, array &$artifact): void + { + // Validate and lint as if it's a standalone artifact + $temp_data = [$pkg_name => $artifact]; + self::validateAndLintArtifacts("inline artifact in package '{$pkg_name}'", $temp_data); + // Write back the linted artifact configuration + $artifact = $temp_data[$pkg_name]; + } } diff --git a/src/StaticPHP/Config/PackageConfig.php b/src/StaticPHP/Config/PackageConfig.php index 92c01f70f..bd9786c3d 100644 --- a/src/StaticPHP/Config/PackageConfig.php +++ b/src/StaticPHP/Config/PackageConfig.php @@ -23,7 +23,7 @@ public static function loadFromDir(string $dir, string $registry_name): array throw new WrongUsageException("Directory {$dir} does not exist, cannot load pkg.json config."); } $loaded = []; - $files = glob("{$dir}/pkg.*.json"); + $files = glob("{$dir}/*"); if (is_array($files)) { foreach ($files as $file) { self::loadFromFile($file, $registry_name); @@ -58,10 +58,39 @@ public static function loadFromFile(string $file, string $registry_name): string foreach ($data as $pkg_name => $config) { self::$package_configs[$pkg_name] = $config; Registry::_bindPackageConfigFile($pkg_name, $registry_name, $file); + + // Register inline artifact if present + if (isset($config['artifact']) && is_array($config['artifact'])) { + ArtifactConfig::registerInlineArtifact( + $pkg_name, + $config['artifact'], + $registry_name, + "inline in {$file}" + ); + } } return $file; } + public static function loadFromArray(array $data, string $registry_name): void + { + ConfigValidator::validateAndLintPackages('array_input', $data); + foreach ($data as $pkg_name => $config) { + self::$package_configs[$pkg_name] = $config; + Registry::_bindPackageConfigFile($pkg_name, $registry_name, 'array_input'); + + // Register inline artifact if present + if (isset($config['artifact']) && is_array($config['artifact'])) { + ArtifactConfig::registerInlineArtifact( + $pkg_name, + $config['artifact'], + $registry_name, + 'inline in array_input' + ); + } + } + } + /** * Check if a package configuration exists. */ diff --git a/src/StaticPHP/Doctor/Item/LinuxToolCheck.php b/src/StaticPHP/Doctor/Item/LinuxToolCheck.php index 09161c74e..f4cdc3111 100644 --- a/src/StaticPHP/Doctor/Item/LinuxToolCheck.php +++ b/src/StaticPHP/Doctor/Item/LinuxToolCheck.php @@ -19,7 +19,6 @@ class LinuxToolCheck 'bzip2', 'cmake', 'gcc', 'g++', 'patch', 'binutils-gold', 'libtoolize', 'which', - 'patchelf', ]; public const TOOLS_DEBIAN = [ @@ -28,7 +27,6 @@ class LinuxToolCheck 'tar', 'unzip', 'gzip', 'gcc', 'g++', 'bzip2', 'cmake', 'patch', 'xz', 'libtoolize', 'which', - 'patchelf', ]; public const TOOLS_RHEL = [ @@ -36,8 +34,7 @@ class LinuxToolCheck 'git', 'autoconf', 'automake', 'tar', 'unzip', 'gzip', 'gcc', 'g++', 'bzip2', 'cmake', 'patch', 'which', - 'xz', 'libtool', 'gettext-devel', - 'patchelf', 'file', + 'xz', 'libtool', 'gettext-devel', 'file', ]; public const TOOLS_ARCH = [ diff --git a/src/StaticPHP/Package/Package.php b/src/StaticPHP/Package/Package.php index 943d03b0d..a0f415d13 100644 --- a/src/StaticPHP/Package/Package.php +++ b/src/StaticPHP/Package/Package.php @@ -175,8 +175,21 @@ public function getType(): string public function getArtifact(): ?Artifact { // find config - $artifact_name = PackageConfig::get($this->name, 'artifact'); - return $artifact_name !== null ? ArtifactLoader::getArtifactInstance($artifact_name) : null; + $artifact_field = PackageConfig::get($this->name, 'artifact'); + + if ($artifact_field === null) { + return null; + } + + if (is_string($artifact_field)) { + return ArtifactLoader::getArtifactInstance($artifact_field); + } + + if (is_array($artifact_field)) { + return ArtifactLoader::getArtifactInstance($this->name); + } + + return null; } /** diff --git a/src/StaticPHP/Registry/Registry.php b/src/StaticPHP/Registry/Registry.php index e464ed471..68aa766d2 100644 --- a/src/StaticPHP/Registry/Registry.php +++ b/src/StaticPHP/Registry/Registry.php @@ -13,6 +13,8 @@ class Registry { + private static ?string $current_registry_name = null; + /** @var string[] List of loaded registries */ private static array $loaded_registries = []; @@ -35,7 +37,7 @@ class Registry public static function getRegistryConfig(?string $registry_name = null): array { if ($registry_name === null && spc_mode(SPC_MODE_SOURCE)) { - return self::$registry_configs['internal']; + return self::$registry_configs['core']; } if ($registry_name !== null && isset(self::$registry_configs[$registry_name])) { return self::$registry_configs[$registry_name]; @@ -83,6 +85,8 @@ public static function loadRegistry(string $registry_file, bool $auto_require = logger()->debug("Loading registry '{$registry_name}' from file: {$registry_file}"); + self::$current_registry_name = $registry_name; + // Load composer autoload if specified (for external registries with their own dependencies) if (isset($data['autoload']) && is_string($data['autoload'])) { $autoload_path = FileSystem::fullpath($data['autoload'], dirname($registry_file)); @@ -94,24 +98,6 @@ public static function loadRegistry(string $registry_file, bool $auto_require = } } - // load doctor items from PSR-4 directories - if (isset($data['doctor']['psr-4']) && is_assoc_array($data['doctor']['psr-4'])) { - foreach ($data['doctor']['psr-4'] as $namespace => $path) { - $path = FileSystem::fullpath($path, dirname($registry_file)); - DoctorLoader::loadFromPsr4Dir($path, $namespace, $auto_require); - } - } - - // load doctor items from specific classes - // Supports both array format ["ClassName"] and map format {"ClassName": "path/to/file.php"} - if (isset($data['doctor']['classes']) && is_array($data['doctor']['classes'])) { - foreach ($data['doctor']['classes'] as $key => $value) { - [$class, $file] = self::parseClassEntry($key, $value); - self::requireClassFile($class, $file, dirname($registry_file), $auto_require); - DoctorLoader::loadFromClass($class); - } - } - // load package configs if (isset($data['package']['config']) && is_array($data['package']['config'])) { foreach ($data['package']['config'] as $path) { @@ -136,6 +122,24 @@ public static function loadRegistry(string $registry_file, bool $auto_require = } } + // load doctor items from PSR-4 directories + if (isset($data['doctor']['psr-4']) && is_assoc_array($data['doctor']['psr-4'])) { + foreach ($data['doctor']['psr-4'] as $namespace => $path) { + $path = FileSystem::fullpath($path, dirname($registry_file)); + DoctorLoader::loadFromPsr4Dir($path, $namespace, $auto_require); + } + } + + // load doctor items from specific classes + // Supports both array format ["ClassName"] and map format {"ClassName": "path/to/file.php"} + if (isset($data['doctor']['classes']) && is_array($data['doctor']['classes'])) { + foreach ($data['doctor']['classes'] as $key => $value) { + [$class, $file] = self::parseClassEntry($key, $value); + self::requireClassFile($class, $file, dirname($registry_file), $auto_require); + DoctorLoader::loadFromClass($class); + } + } + // load packages from PSR-4 directories if (isset($data['package']['psr-4']) && is_assoc_array($data['package']['psr-4'])) { foreach ($data['package']['psr-4'] as $namespace => $path) { @@ -193,6 +197,7 @@ public static function loadRegistry(string $registry_file, bool $auto_require = } ConsoleApplication::_addAdditionalCommands($instances); } + self::$current_registry_name = null; } /** @@ -300,6 +305,11 @@ public static function getLoadedArtifactConfigs(): array return self::$loaded_artifact_configs; } + public static function getCurrentRegistryName(): ?string + { + return self::$current_registry_name; + } + /** * Parse a class entry from the classes array. * Supports two formats: diff --git a/src/StaticPHP/Util/LicenseDumper.php b/src/StaticPHP/Util/LicenseDumper.php index 57fc3aec4..54c6266bb 100644 --- a/src/StaticPHP/Util/LicenseDumper.php +++ b/src/StaticPHP/Util/LicenseDumper.php @@ -163,18 +163,26 @@ private function readLicenseFile(Artifact $artifact, string $license_file_path): { $artifact_name = $artifact->getName(); - // Try source directory first (if extracted) - if ($artifact->isSourceExtracted()) { - $source_dir = $artifact->getSourceDir(); - $full_path = "{$source_dir}/{$license_file_path}"; + // replace + if (str_starts_with($license_file_path, '@/')) { + $license_file_path = str_replace('@/', ROOT_DIR . '/src/globals/licenses/', $license_file_path); + } + $source_dir = $artifact->getSourceDir(); + if (FileSystem::isRelativePath($license_file_path)) { + $full_path = "{$source_dir}/{$license_file_path}"; + } else { + $full_path = $license_file_path; + } + // Try source directory first (if extracted) + if ($artifact->isSourceExtracted() || file_exists($full_path)) { logger()->debug("Checking license file: {$full_path}"); if (file_exists($full_path)) { logger()->info("Reading license from source: {$full_path}"); return file_get_contents($full_path); } } else { - logger()->debug("Artifact source not extracted: {$artifact_name}"); + logger()->warning("Artifact source not extracted: {$artifact_name}"); } // Fallback: try SOURCE_PATH directly diff --git a/src/globals/licenses/gmp.txt b/src/globals/licenses/gmp.txt new file mode 100644 index 000000000..488f3b902 --- /dev/null +++ b/src/globals/licenses/gmp.txt @@ -0,0 +1 @@ +Since version 6, GMP is distributed under the dual licenses, GNU LGPL v3 and GNU GPL v2. These licenses make the library free to use, share, and improve, and allow you to pass on the result. The GNU licenses give freedoms, but also set firm restrictions on the use with non-free programs. From f4a29c4830b0eacfe33313a83d6a360c54a97bfb Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 14:13:05 +0800 Subject: [PATCH 15/99] Add dev:lint-config to replace sort-config command --- config/pkg/lib/attr.yml | 6 +++--- config/pkg/lib/brotli.yml | 14 +++++++------- config/pkg/lib/bzip2.yml | 10 +++++----- config/pkg/lib/fastlz.yml | 12 ++++++------ config/pkg/lib/gmp.yml | 12 ++++++------ config/pkg/lib/openssl.yml | 17 +++++++++-------- config/pkg/lib/zlib.yml | 14 +++++++------- src/StaticPHP/ConsoleApplication.php | 4 ++-- 8 files changed, 45 insertions(+), 44 deletions(-) diff --git a/config/pkg/lib/attr.yml b/config/pkg/lib/attr.yml index 4e7858513..289bcd67b 100644 --- a/config/pkg/lib/attr.yml +++ b/config/pkg/lib/attr.yml @@ -1,7 +1,5 @@ attr: type: library - static-libs@unix: - - libattr.a artifact: source: type: url @@ -10,5 +8,7 @@ attr: type: url url: 'https://mirror.souseiseki.middlendian.com/nongnu/attr/attr-2.5.2.tar.gz' metadata: - license-files: ['doc/COPYING.LGPL'] + license-files: [doc/COPYING.LGPL] license: LGPL-2.1-or-later + static-libs@unix: + - libattr.a diff --git a/config/pkg/lib/brotli.yml b/config/pkg/lib/brotli.yml index 342359aab..524f9ddc8 100644 --- a/config/pkg/lib/brotli.yml +++ b/config/pkg/lib/brotli.yml @@ -1,11 +1,5 @@ brotli: type: library - pkg-configs: - - libbrotlicommon - - libbrotlidec - - libbrotlienc - headers: - - brotli artifact: source: type: ghtagtar @@ -13,5 +7,11 @@ brotli: match: 'v1\.\d.*' binary: hosted metadata: - license-files: ['LICENSE'] + license-files: [LICENSE] license: MIT + headers: + - brotli + pkg-configs: + - libbrotlicommon + - libbrotlidec + - libbrotlienc diff --git a/config/pkg/lib/bzip2.yml b/config/pkg/lib/bzip2.yml index 6f7b483b5..132a24ffa 100644 --- a/config/pkg/lib/bzip2.yml +++ b/config/pkg/lib/bzip2.yml @@ -1,9 +1,5 @@ bzip2: type: library - static-libs@unix: - - libbz2.a - headers: - - bzlib.h artifact: source: type: url @@ -15,4 +11,8 @@ bzip2: binary: hosted metadata: license-files: ['{registry_root}/src/globals/licenses/bzip2.txt'] - license: bzip2-1.0.6 \ No newline at end of file + license: bzip2-1.0.6 + headers: + - bzlib.h + static-libs@unix: + - libbz2.a diff --git a/config/pkg/lib/fastlz.yml b/config/pkg/lib/fastlz.yml index 32f26d8a0..e3296831b 100644 --- a/config/pkg/lib/fastlz.yml +++ b/config/pkg/lib/fastlz.yml @@ -1,14 +1,14 @@ fastlz: type: library - static-libs@unix: - - libfastlz.a - headers: - - fastlz.h artifact: source: type: git url: 'https://github.com/ariya/FastLZ.git' rev: master metadata: - license-files: ['LICENSE.MIT'] - license: MIT \ No newline at end of file + license-files: [LICENSE.MIT] + license: MIT + headers: + - fastlz.h + static-libs@unix: + - libfastlz.a diff --git a/config/pkg/lib/gmp.yml b/config/pkg/lib/gmp.yml index 8a28069b8..bdc13b559 100644 --- a/config/pkg/lib/gmp.yml +++ b/config/pkg/lib/gmp.yml @@ -1,11 +1,5 @@ gmp: type: library - static-libs@unix: - - libgmp.a - headers: - - gmp.h - pkg-configs: - - gmp artifact: source: type: filelist @@ -17,3 +11,9 @@ gmp: metadata: license-files: ['@/gmp.txt'] license: Custom + headers: + - gmp.h + pkg-configs: + - gmp + static-libs@unix: + - libgmp.a diff --git a/config/pkg/lib/openssl.yml b/config/pkg/lib/openssl.yml index ce83a4d41..22d065088 100644 --- a/config/pkg/lib/openssl.yml +++ b/config/pkg/lib/openssl.yml @@ -1,16 +1,10 @@ openssl: type: library - static-libs@unix: - - libssl.a - - libcrypto.a - headers: ['openssl'] - depends: - - zlib artifact: source: type: ghrel repo: openssl/openssl - match: 'openssl.+\.tar\.gz' + match: openssl.+\.tar\.gz prefer-stable: true source-mirror: type: filelist @@ -18,5 +12,12 @@ openssl: regex: '/href="(?openssl-(?[^"]+)\.tar\.gz)"/' binary: hosted metadata: - license-files: ['LICENSE.txt'] + license-files: [LICENSE.txt] license: OpenSSL + depends: + - zlib + headers: + - openssl + static-libs@unix: + - libssl.a + - libcrypto.a diff --git a/config/pkg/lib/zlib.yml b/config/pkg/lib/zlib.yml index 0a7eec716..9ca70f426 100644 --- a/config/pkg/lib/zlib.yml +++ b/config/pkg/lib/zlib.yml @@ -1,16 +1,16 @@ zlib: type: library - static-libs@unix: - - libz.a - headers: - - zlib.h - - zconf.h artifact: source: type: ghrel repo: madler/zlib - match: 'zlib.+\.tar\.gz' + match: zlib.+\.tar\.gz binary: hosted metadata: license-files: ['{registry_root}/src/globals/licenses/zlib.txt'] - license: Zlib-Custom \ No newline at end of file + license: Zlib-Custom + headers: + - zlib.h + - zconf.h + static-libs@unix: + - libz.a diff --git a/src/StaticPHP/ConsoleApplication.php b/src/StaticPHP/ConsoleApplication.php index bd2e4eb19..e65617053 100644 --- a/src/StaticPHP/ConsoleApplication.php +++ b/src/StaticPHP/ConsoleApplication.php @@ -8,8 +8,8 @@ use StaticPHP\Command\BuildTargetCommand; use StaticPHP\Command\Dev\EnvCommand; use StaticPHP\Command\Dev\IsInstalledCommand; +use StaticPHP\Command\Dev\LintConfigCommand; use StaticPHP\Command\Dev\ShellCommand; -use StaticPHP\Command\Dev\SortConfigCommand; use StaticPHP\Command\DoctorCommand; use StaticPHP\Command\DownloadCommand; use StaticPHP\Command\DumpLicenseCommand; @@ -62,7 +62,7 @@ public function __construct() new ShellCommand(), new IsInstalledCommand(), new EnvCommand(), - new SortConfigCommand(), + new LintConfigCommand(), ]); // add additional commands from registries From f437efebb7a93ce8a28ae90ab8d3228965775238 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 14:14:01 +0800 Subject: [PATCH 16/99] Add dev:lint-config to replace sort-config command --- .../Command/Dev/LintConfigCommand.php | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/StaticPHP/Command/Dev/LintConfigCommand.php diff --git a/src/StaticPHP/Command/Dev/LintConfigCommand.php b/src/StaticPHP/Command/Dev/LintConfigCommand.php new file mode 100644 index 000000000..1b08bb85c --- /dev/null +++ b/src/StaticPHP/Command/Dev/LintConfigCommand.php @@ -0,0 +1,121 @@ +sortConfigFile($file, 'artifact'); + } + $loaded_pkg_configs = Registry::getLoadedPackageConfigs(); + foreach ($loaded_pkg_configs as $file) { + $this->sortConfigFile($file, 'package'); + } + return static::SUCCESS; + } + + public function artifactSortKey(string $a, string $b): int + { + // sort by predefined order, other not matching keys go to the end alphabetically + $order = ['source', 'source-mirror', 'binary', 'binary-mirror', 'metadata']; + + $pos_a = array_search($a, $order, true); + $pos_b = array_search($b, $order, true); + + // Both in order list + if ($pos_a !== false && $pos_b !== false) { + return $pos_a <=> $pos_b; + } + + // Only $a in order list + if ($pos_a !== false) { + return -1; + } + + // Only $b in order list + if ($pos_b !== false) { + return 1; + } + + // Neither in order list, sort alphabetically + return $a <=> $b; + } + + public function packageSortKey(string $a, string $b): int + { + // sort by predefined order, other not matching keys go to the end alphabetically + $order = ['type', 'artifact']; + + // Handle suffix patterns (e.g., 'depends@unix', 'static-libs@windows') + $base_a = preg_replace('/@(unix|windows|macos|linux|freebsd|bsd)$/', '', $a); + $base_b = preg_replace('/@(unix|windows|macos|linux|freebsd|bsd)$/', '', $b); + + $pos_a = array_search($base_a, $order, true); + $pos_b = array_search($base_b, $order, true); + + // Both in order list + if ($pos_a !== false && $pos_b !== false) { + if ($pos_a === $pos_b) { + // Same base field, sort by suffix + return $a <=> $b; + } + return $pos_a <=> $pos_b; + } + + // Only $a in order list + if ($pos_a !== false) { + return -1; + } + + // Only $b in order list + if ($pos_b !== false) { + return 1; + } + + // Neither in order list, sort alphabetically + return $a <=> $b; + } + + private function sortConfigFile(mixed $file, string $config_type): void + { + // read file content with different extensions + $content = file_get_contents($file); + if ($content === false) { + $this->output->writeln("Failed to read artifact config file: {$file}"); + return; + } + $data = match (pathinfo($file, PATHINFO_EXTENSION)) { + 'json' => json_decode($content, true), + 'yml', 'yaml' => Yaml::parse($content), // skip yaml files for now + default => null, + }; + if (!is_array($data)) { + $this->output->writeln("Invalid JSON format in artifact config file: {$file}"); + return; + } + ksort($data); + foreach ($data as $artifact_name => &$config) { + uksort($config, $config_type === 'artifact' ? [$this, 'artifactSortKey'] : [$this, 'packageSortKey']); + } + unset($config); + $new_content = match (pathinfo($file, PATHINFO_EXTENSION)) { + 'json' => json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n", + 'yml', 'yaml' => Yaml::dump($data, 4, 2), + default => null, + }; + file_put_contents($file, $new_content); + $this->output->writeln("Sorted artifact config file: {$file}"); + } +} From 5a8341203ba190020fe21c46c6cfddbf0df76671 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 14:14:19 +0800 Subject: [PATCH 17/99] Remove sort config command --- .../Command/Dev/SortConfigCommand.php | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 src/StaticPHP/Command/Dev/SortConfigCommand.php diff --git a/src/StaticPHP/Command/Dev/SortConfigCommand.php b/src/StaticPHP/Command/Dev/SortConfigCommand.php deleted file mode 100644 index aa3a9ecdb..000000000 --- a/src/StaticPHP/Command/Dev/SortConfigCommand.php +++ /dev/null @@ -1,49 +0,0 @@ -sortConfigFile($file); - } - $loaded_pkg_configs = Registry::getLoadedPackageConfigs(); - foreach ($loaded_pkg_configs as $file) { - $this->sortConfigFile($file); - } - return static::SUCCESS; - } - - private function sortConfigFile(mixed $file): void - { - $content = file_get_contents($file); - if ($content === false) { - $this->output->writeln("Failed to read artifact config file: {$file}"); - return; - } - $data = json_decode($content, true); - if (!is_array($data)) { - $this->output->writeln("Invalid JSON format in artifact config file: {$file}"); - return; - } - ksort($data); - foreach ($data as $artifact_name => &$config) { - ksort($config); - } - unset($config); - $new_content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; - file_put_contents($file, $new_content); - $this->output->writeln("Sorted artifact config file: {$file}"); - } -} From 23db10d3cdfd95c5ab2262df20f94c291d4af70c Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 15:35:59 +0800 Subject: [PATCH 18/99] Add libiconv,libssh2,libxml2,xz --- config/pkg/lib/libiconv.yml | 18 ++++++ config/pkg/lib/libssh2.yml | 22 +++++++ config/pkg/lib/libxml2.yml | 19 ++++++ config/pkg/lib/xz.yml | 20 +++++++ src/Package/Library/libiconv.php | 9 +-- src/Package/Library/libssh2.php | 29 +++++++++ src/Package/Library/libxml2.php | 69 ++++++++++++++-------- src/Package/Library/xz.php | 30 ++++++++++ src/StaticPHP/Command/BuildLibsCommand.php | 4 +- 9 files changed, 190 insertions(+), 30 deletions(-) create mode 100644 config/pkg/lib/libiconv.yml create mode 100644 config/pkg/lib/libssh2.yml create mode 100644 config/pkg/lib/libxml2.yml create mode 100644 config/pkg/lib/xz.yml create mode 100644 src/Package/Library/libssh2.php create mode 100644 src/Package/Library/xz.php diff --git a/config/pkg/lib/libiconv.yml b/config/pkg/lib/libiconv.yml new file mode 100644 index 000000000..7fc3bddad --- /dev/null +++ b/config/pkg/lib/libiconv.yml @@ -0,0 +1,18 @@ +libiconv: + type: library + artifact: + source: + type: filelist + url: 'https://ftp.gnu.org/gnu/libiconv/' + regex: '/href="(?libiconv-(?[^"]+)\.tar\.gz)"/' + binary: hosted + metadata: + license-files: [COPYING.LIB] + license: LGPL-2.0-or-later + headers: + - iconv.h + - libcharset.h + - localcharset.h + static-libs@unix: + - libiconv.a + - libcharset.a diff --git a/config/pkg/lib/libssh2.yml b/config/pkg/lib/libssh2.yml new file mode 100644 index 000000000..4b66ed2b7 --- /dev/null +++ b/config/pkg/lib/libssh2.yml @@ -0,0 +1,22 @@ +libssh2: + type: library + artifact: + source: + type: ghrel + repo: libssh2/libssh2 + match: libssh2.+\.tar\.gz + prefer-stable: true + binary: hosted + metadata: + license-files: [COPYING] + license: BSD-3-Clause + depends: + - openssl + headers: + - libssh2.h + - libssh2_publickey.h + - libssh2_sftp.h + static-libs@unix: + - libssh2.a + pkg-configs: + - libssh2 diff --git a/config/pkg/lib/libxml2.yml b/config/pkg/lib/libxml2.yml new file mode 100644 index 000000000..4d628ffb0 --- /dev/null +++ b/config/pkg/lib/libxml2.yml @@ -0,0 +1,19 @@ +libxml2: + type: library + artifact: + source: + type: ghtagtar + repo: GNOME/libxml2 + match: 'v2\.\d+\.\d+$' + metadata: + license-files: [Copyright] + license: MIT + depends@unix: + - libiconv + suggests@unix: + - xz + - zlib + headers: + - libxml2 + pkg-configs: + - libxml-2.0 diff --git a/config/pkg/lib/xz.yml b/config/pkg/lib/xz.yml new file mode 100644 index 000000000..a64cf2e9a --- /dev/null +++ b/config/pkg/lib/xz.yml @@ -0,0 +1,20 @@ +xz: + type: library + artifact: + source: + type: ghrel + repo: tukaani-project/xz + match: xz.+\.tar\.xz + prefer-stable: true + binary: hosted + metadata: + license-files: [COPYING] + license: 0BSD + depends@unix: + - libiconv + headers@unix: + - lzma + static-libs@unix: + - liblzma.a + pkg-configs: + - liblzma diff --git a/src/Package/Library/libiconv.php b/src/Package/Library/libiconv.php index ac91d188a..d2ac4d26c 100644 --- a/src/Package/Library/libiconv.php +++ b/src/Package/Library/libiconv.php @@ -12,16 +12,17 @@ #[Library('libiconv')] class libiconv { + #[BuildFor('Linux')] #[BuildFor('Darwin')] - public function build(LibraryPackage $package): void + public function build(LibraryPackage $lib): void { - UnixAutoconfExecutor::create($package) + UnixAutoconfExecutor::create($lib) ->configure( '--enable-extra-encodings', '--enable-year2038', ) ->make('install-lib', with_install: false) - ->make('install-lib', with_install: false, dir: "{$package->getSourceDir()}/libcharset"); - $package->patchLaDependencyPrefix(); + ->make('install-lib', with_install: false, dir: $lib->getSourceDir() . '/libcharset'); + $lib->patchLaDependencyPrefix(); } } diff --git a/src/Package/Library/libssh2.php b/src/Package/Library/libssh2.php new file mode 100644 index 000000000..f71d508ac --- /dev/null +++ b/src/Package/Library/libssh2.php @@ -0,0 +1,29 @@ +optionalPackage('zlib', ...cmake_boolean_args('ENABLE_ZLIB_COMPRESSION')) + ->addConfigureArgs( + '-DBUILD_EXAMPLES=OFF', + '-DBUILD_TESTING=OFF' + ) + ->build(); + + $lib->patchPkgconfPrefix(['libssh2.pc']); + } +} diff --git a/src/Package/Library/libxml2.php b/src/Package/Library/libxml2.php index 4767efcb0..7c35d6855 100644 --- a/src/Package/Library/libxml2.php +++ b/src/Package/Library/libxml2.php @@ -8,47 +8,68 @@ use StaticPHP\Attribute\Package\Library; use StaticPHP\Package\LibraryPackage; use StaticPHP\Runtime\Executor\UnixCMakeExecutor; -use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\FileSystem; #[Library('libxml2')] class libxml2 { - #[BuildFor('Darwin')] - public function build(LibraryPackage $package): void + #[BuildFor('Linux')] + public function buildForLinux(LibraryPackage $lib): void { - $cmake = UnixCMakeExecutor::create($package) + UnixCMakeExecutor::create($lib) ->optionalPackage( 'zlib', '-DLIBXML2_WITH_ZLIB=ON ' . - "-DZLIB_LIBRARY={$package->getLibDir()}/libz.a " . - "-DZLIB_INCLUDE_DIR={$package->getIncludeDir()}", + "-DZLIB_LIBRARY={$lib->getLibDir()}/libz.a " . + "-DZLIB_INCLUDE_DIR={$lib->getIncludeDir()}", '-DLIBXML2_WITH_ZLIB=OFF', ) ->optionalPackage('xz', ...cmake_boolean_args('LIBXML2_WITH_LZMA')) ->addConfigureArgs( '-DLIBXML2_WITH_ICONV=ON', + '-DIconv_IS_BUILT_IN=OFF', '-DLIBXML2_WITH_ICU=OFF', // optional, but discouraged: https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/README.md '-DLIBXML2_WITH_PYTHON=OFF', '-DLIBXML2_WITH_PROGRAMS=OFF', '-DLIBXML2_WITH_TESTS=OFF', - ); - - if (SystemTarget::getTargetOS() === 'Linux') { - $cmake->addConfigureArgs('-DIconv_IS_BUILT_IN=OFF'); - } - - $cmake->build(); - - FileSystem::replaceFileStr( - BUILD_LIB_PATH . '/pkgconfig/libxml-2.0.pc', - '-lxml2 -liconv', - '-lxml2' - ); - FileSystem::replaceFileStr( - BUILD_LIB_PATH . '/pkgconfig/libxml-2.0.pc', - '-lxml2', - '-lxml2 -liconv' - ); + ) + ->build(); + + $this->patchPkgConfig($lib); + } + + #[BuildFor('Darwin')] + public function buildForDarwin(LibraryPackage $lib): void + { + UnixCMakeExecutor::create($lib) + ->optionalPackage( + 'zlib', + '-DLIBXML2_WITH_ZLIB=ON ' . + "-DZLIB_LIBRARY={$lib->getLibDir()}/libz.a " . + "-DZLIB_INCLUDE_DIR={$lib->getIncludeDir()}", + '-DLIBXML2_WITH_ZLIB=OFF', + ) + ->optionalPackage('xz', ...cmake_boolean_args('LIBXML2_WITH_LZMA')) + ->addConfigureArgs( + '-DLIBXML2_WITH_ICONV=ON', + '-DLIBXML2_WITH_ICU=OFF', + '-DLIBXML2_WITH_PYTHON=OFF', + '-DLIBXML2_WITH_PROGRAMS=OFF', + '-DLIBXML2_WITH_TESTS=OFF', + ) + ->build(); + + $this->patchPkgConfig($lib); + } + + private function patchPkgConfig(LibraryPackage $lib): void + { + $pcFile = "{$lib->getLibDir()}/pkgconfig/libxml-2.0.pc"; + + // Remove -liconv from original + FileSystem::replaceFileStr($pcFile, '-lxml2 -liconv', '-lxml2'); + + // Add -liconv after -lxml2 + FileSystem::replaceFileStr($pcFile, '-lxml2', '-lxml2 -liconv'); } } diff --git a/src/Package/Library/xz.php b/src/Package/Library/xz.php new file mode 100644 index 000000000..3486d4c17 --- /dev/null +++ b/src/Package/Library/xz.php @@ -0,0 +1,30 @@ +configure( + '--disable-scripts', + '--disable-doc', + '--with-libiconv', + '--bindir=/tmp/xz', // xz binary will corrupt `tar` command, that's really strange. + ) + ->make(); + $lib->patchPkgconfPrefix(['liblzma.pc']); + $lib->patchLaDependencyPrefix(); + } +} diff --git a/src/StaticPHP/Command/BuildLibsCommand.php b/src/StaticPHP/Command/BuildLibsCommand.php index 2ff36b49a..309867c0c 100644 --- a/src/StaticPHP/Command/BuildLibsCommand.php +++ b/src/StaticPHP/Command/BuildLibsCommand.php @@ -7,10 +7,10 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; -#[AsCommand('build:libs')] +#[AsCommand('build:libs', 'Build specified library packages')] class BuildLibsCommand extends BaseCommand { - public function configure() + public function configure(): void { $this->addArgument('libraries', InputArgument::REQUIRED, 'The library packages will be compiled, comma separated'); } From 1586825b5bd00c0e12176599851931c715a8c7b8 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 15:55:41 +0800 Subject: [PATCH 19/99] Add builder options for build:libs command --- src/StaticPHP/Command/BuildLibsCommand.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/StaticPHP/Command/BuildLibsCommand.php b/src/StaticPHP/Command/BuildLibsCommand.php index 309867c0c..a415ca3e1 100644 --- a/src/StaticPHP/Command/BuildLibsCommand.php +++ b/src/StaticPHP/Command/BuildLibsCommand.php @@ -4,8 +4,11 @@ namespace StaticPHP\Command; +use StaticPHP\Package\PackageInstaller; +use StaticPHP\Util\V2CompatLayer; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; #[AsCommand('build:libs', 'Build specified library packages')] class BuildLibsCommand extends BaseCommand @@ -13,13 +16,20 @@ class BuildLibsCommand extends BaseCommand public function configure(): void { $this->addArgument('libraries', InputArgument::REQUIRED, 'The library packages will be compiled, comma separated'); + // Builder options + $this->getDefinition()->addOptions([ + new InputOption('with-suggests', ['L', 'E'], null, 'Resolve and install suggested packages as well'), + new InputOption('with-packages', null, InputOption::VALUE_REQUIRED, 'add additional packages to install/build, comma separated', ''), + new InputOption('no-download', null, null, 'Skip downloading artifacts (use existing cached files)'), + ...V2CompatLayer::getLegacyBuildOptions(), + ]); } public function handle(): int { $libs = parse_comma_list($this->input->getArgument('libraries')); - $installer = new \StaticPHP\Package\PackageInstaller($this->input->getOptions()); + $installer = new PackageInstaller($this->input->getOptions()); foreach ($libs as $lib) { $installer->addBuildPackage($lib); } From 82ab14165e966303565d5fa9f2a47fe658fe4b68 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 15:56:12 +0800 Subject: [PATCH 20/99] Add nghttp2, nghttp3, ngtcp2 --- config/pkg/lib/nghttp2.yml | 24 +++++++++++++++ config/pkg/lib/nghttp3.yml | 19 ++++++++++++ config/pkg/lib/ngtcp2.yml | 24 +++++++++++++++ src/Package/Library/nghttp2.php | 41 ++++++++++++++++++++++++++ src/Package/Library/nghttp3.php | 25 ++++++++++++++++ src/Package/Library/ngtcp2.php | 52 +++++++++++++++++++++++++++++++++ 6 files changed, 185 insertions(+) create mode 100644 config/pkg/lib/nghttp2.yml create mode 100644 config/pkg/lib/nghttp3.yml create mode 100644 config/pkg/lib/ngtcp2.yml create mode 100644 src/Package/Library/nghttp2.php create mode 100644 src/Package/Library/nghttp3.php create mode 100644 src/Package/Library/ngtcp2.php diff --git a/config/pkg/lib/nghttp2.yml b/config/pkg/lib/nghttp2.yml new file mode 100644 index 000000000..9678d16b2 --- /dev/null +++ b/config/pkg/lib/nghttp2.yml @@ -0,0 +1,24 @@ +nghttp2: + type: library + artifact: + source: + type: ghrel + repo: nghttp2/nghttp2 + match: nghttp2.+\.tar\.xz + prefer-stable: true + metadata: + license-files: [COPYING] + depends: + - zlib + - openssl + suggests: + - libxml2 + - nghttp3 + - ngtcp2 + - brotli + headers: + - nghttp2 + static-libs@unix: + - libnghttp2.a + pkg-configs: + - libnghttp2 diff --git a/config/pkg/lib/nghttp3.yml b/config/pkg/lib/nghttp3.yml new file mode 100644 index 000000000..7d15c6020 --- /dev/null +++ b/config/pkg/lib/nghttp3.yml @@ -0,0 +1,19 @@ +nghttp3: + type: library + artifact: + source: + type: ghrel + repo: ngtcp2/nghttp3 + match: nghttp3.+\.tar\.xz + prefer-stable: true + metadata: + license-files: [COPYING] + license: MIT + depends: + - openssl + headers: + - nghttp3 + static-libs@unix: + - libnghttp3.a + pkg-configs: + - libnghttp3 diff --git a/config/pkg/lib/ngtcp2.yml b/config/pkg/lib/ngtcp2.yml new file mode 100644 index 000000000..44b0aaaeb --- /dev/null +++ b/config/pkg/lib/ngtcp2.yml @@ -0,0 +1,24 @@ +ngtcp2: + type: library + artifact: + source: + type: ghrel + repo: ngtcp2/ngtcp2 + match: ngtcp2.+\.tar\.xz + prefer-stable: true + metadata: + license-files: [COPYING] + license: MIT + depends: + - openssl + suggests: + - nghttp3 + - brotli + headers: + - ngtcp2 + static-libs@unix: + - libngtcp2.a + - libngtcp2_crypto_ossl.a + pkg-configs: + - libngtcp2 + - libngtcp2_crypto_ossl diff --git a/src/Package/Library/nghttp2.php b/src/Package/Library/nghttp2.php new file mode 100644 index 000000000..3a85ada4a --- /dev/null +++ b/src/Package/Library/nghttp2.php @@ -0,0 +1,41 @@ +optionalPackage('zlib', ...ac_with_args('zlib', true)) + ->optionalPackage('openssl', ...ac_with_args('openssl', true)) + ->optionalPackage('libxml2', ...ac_with_args('libxml2', true)) + ->optionalPackage('ngtcp2', ...ac_with_args('libngtcp2', true)) + ->optionalPackage('nghttp3', ...ac_with_args('libnghttp3', true)) + ->optionalPackage( + 'brotli', + fn (LibraryPackage $brotli) => implode(' ', [ + '--with-brotlidec=yes', + "LIBBROTLIDEC_CFLAGS=\"-I{$brotli->getIncludeDir()}\"", + "LIBBROTLIDEC_LIBS=\"{$brotli->getStaticLibFiles()}\"", + '--with-libbrotlienc=yes', + "LIBBROTLIENC_CFLAGS=\"-I{$brotli->getIncludeDir()}\"", + "LIBBROTLIENC_LIBS=\"{$brotli->getStaticLibFiles()}\"", + ]) + ) + ->configure('--enable-lib-only') + ->make(); + + $lib->patchPkgconfPrefix(['libnghttp2.pc'], PKGCONF_PATCH_PREFIX); + } +} diff --git a/src/Package/Library/nghttp3.php b/src/Package/Library/nghttp3.php new file mode 100644 index 000000000..1f686b7b5 --- /dev/null +++ b/src/Package/Library/nghttp3.php @@ -0,0 +1,25 @@ +configure('--enable-lib-only') + ->make(); + + $lib->patchPkgconfPrefix(['libnghttp3.pc'], PKGCONF_PATCH_PREFIX); + } +} diff --git a/src/Package/Library/ngtcp2.php b/src/Package/Library/ngtcp2.php new file mode 100644 index 000000000..15821225b --- /dev/null +++ b/src/Package/Library/ngtcp2.php @@ -0,0 +1,52 @@ +optionalPackage( + 'openssl', + fn (LibraryPackage $openssl) => implode(' ', [ + '--with-openssl=yes', + "OPENSSL_LIBS=\"{$openssl->getStaticLibFiles()}\"", + "OPENSSL_CFLAGS=\"-I{$openssl->getIncludeDir()}\"", + ]), + '--with-openssl=no' + ) + ->optionalPackage('nghttp3', ...ac_with_args('libnghttp3', true)) + ->optionalPackage( + 'brotli', + fn (LibraryPackage $brotli) => implode(' ', [ + '--with-brotlidec=yes', + "LIBBROTLIDEC_CFLAGS=\"-I{$brotli->getIncludeDir()}\"", + "LIBBROTLIDEC_LIBS=\"{$brotli->getStaticLibFiles()}\"", + '--with-libbrotlienc=yes', + "LIBBROTLIENC_CFLAGS=\"-I{$brotli->getIncludeDir()}\"", + "LIBBROTLIENC_LIBS=\"{$brotli->getStaticLibFiles()}\"", + ]) + ) + ->appendEnv(['PKG_CONFIG' => '$PKG_CONFIG --static']) + ->configure('--enable-lib-only') + ->make(); + + $lib->patchPkgconfPrefix(['libngtcp2.pc', 'libngtcp2_crypto_ossl.pc'], PKGCONF_PATCH_PREFIX); + + // On macOS, the static library may contain other static libraries + // ld: archive member 'libssl.a' not a mach-o file in libngtcp2_crypto_ossl.a + $AR = getenv('AR') ?: 'ar'; + shell()->cd($lib->getLibDir())->exec("{$AR} -t libngtcp2_crypto_ossl.a | grep '\\.a\$' | xargs -n1 {$AR} d libngtcp2_crypto_ossl.a"); + } +} From 3d102363c42761aaa9667553dc5a1385e623f2a8 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 16:15:25 +0800 Subject: [PATCH 21/99] Add PatchBeforeBuild attribute --- .../Attribute/Package/PatchBeforeBuild.php | 8 +++++++ .../Package/PackageCallbacksTrait.php | 22 ++++++++++--------- src/StaticPHP/Registry/PackageLoader.php | 8 +++++++ 3 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 src/StaticPHP/Attribute/Package/PatchBeforeBuild.php diff --git a/src/StaticPHP/Attribute/Package/PatchBeforeBuild.php b/src/StaticPHP/Attribute/Package/PatchBeforeBuild.php new file mode 100644 index 000000000..254e1a6b6 --- /dev/null +++ b/src/StaticPHP/Attribute/Package/PatchBeforeBuild.php @@ -0,0 +1,8 @@ +validate_callback = $callback; } - public function setPatchBeforeBuildCallback(callable $callback): void + public function addPatchBeforeBuildCallback(callable $callback): void { - $this->patch_before_build_callback = $callback; + $this->patch_before_build_callbacks[] = $callback; } public function patchBeforeBuild(): void @@ -58,16 +58,18 @@ public function patchBeforeBuild(): void if (file_exists("{$this->getSourceDir()}/.spc-patched")) { return; } - if ($this->patch_before_build_callback === null) { + if ($this->patch_before_build_callbacks === null) { return; } // Use CallbackInvoker with current package as context - $ret = ApplicationContext::invoke($this->patch_before_build_callback, [ - Package::class => $this, - static::class => $this, - ]); - if ($ret === true) { - FileSystem::writeFile("{$this->getSourceDir()}/.spc-patched", 'PATCHED!!!'); + foreach ($this->patch_before_build_callbacks as $callback) { + $ret = ApplicationContext::invoke($callback, [ + Package::class => $this, + static::class => $this, + ]); + if ($ret === true) { + FileSystem::writeFile("{$this->getSourceDir()}/.spc-patched", 'PATCHED!!!'); + } } } diff --git a/src/StaticPHP/Registry/PackageLoader.php b/src/StaticPHP/Registry/PackageLoader.php index 29ce0a96b..e95021273 100644 --- a/src/StaticPHP/Registry/PackageLoader.php +++ b/src/StaticPHP/Registry/PackageLoader.php @@ -12,6 +12,7 @@ use StaticPHP\Attribute\Package\Info; use StaticPHP\Attribute\Package\InitPackage; use StaticPHP\Attribute\Package\Library; +use StaticPHP\Attribute\Package\PatchBeforeBuild; use StaticPHP\Attribute\Package\ResolveBuild; use StaticPHP\Attribute\Package\Stage; use StaticPHP\Attribute\Package\Target; @@ -196,6 +197,8 @@ public static function loadFromClass(mixed $class): void match ($method_attribute->getName()) { // #[BuildFor(PHP_OS_FAMILY)] BuildFor::class => self::addBuildFunction($pkg, $method_instance, [$instance_class, $method->getName()]), + // #[BeforeBuild] + PatchBeforeBuild::class => self::addPatchBeforeBuildFunction($pkg, [$instance_class, $method->getName()]), // #[CustomPhpConfigureArg(PHP_OS_FAMILY)] CustomPhpConfigureArg::class => self::bindCustomPhpConfigureArg($pkg, $method_attribute->newInstance(), [$instance_class, $method->getName()]), // #[Stage('stage_name')] @@ -332,6 +335,11 @@ private static function addBuildFunction(Package $pkg, object $attr, callable $f $pkg->addBuildFunction($attr->os, $fn); } + private static function addPatchBeforeBuildFunction(Package $pkg, callable $fn): void + { + $pkg->addPatchBeforeBuildCallback($fn); + } + private static function addStage(\ReflectionMethod $method, Package $pkg, object $instance_class, object $method_instance): void { $name = $method_instance->function; From 6ee8dc799497485977ef6a3023b42850b24bb342 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 16:15:36 +0800 Subject: [PATCH 22/99] Add zstd,libcares --- config/pkg/lib/libcares.yml | 23 +++++++++++++++++++ config/pkg/lib/zstd.yml | 19 ++++++++++++++++ src/Package/Library/libcares.php | 38 ++++++++++++++++++++++++++++++++ src/Package/Library/zstd.php | 29 ++++++++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 config/pkg/lib/libcares.yml create mode 100644 config/pkg/lib/zstd.yml create mode 100644 src/Package/Library/libcares.php create mode 100644 src/Package/Library/zstd.php diff --git a/config/pkg/lib/libcares.yml b/config/pkg/lib/libcares.yml new file mode 100644 index 000000000..da0d1ad64 --- /dev/null +++ b/config/pkg/lib/libcares.yml @@ -0,0 +1,23 @@ +libcares: + type: library + artifact: + source: + type: ghrel + repo: c-ares/c-ares + match: c-ares-.+\.tar\.gz + prefer-stable: true + source-mirror: + type: filelist + url: 'https://c-ares.org/download/' + regex: '/href="\/download\/(?c-ares-(?[^"]+)\.tar\.gz)"/' + binary: hosted + metadata: + license-files: [LICENSE.md] + headers@unix: + - ares.h + - ares_dns.h + - ares_nameser.h + static-libs@unix: + - libcares.a + pkg-configs: + - libcares diff --git a/config/pkg/lib/zstd.yml b/config/pkg/lib/zstd.yml new file mode 100644 index 000000000..60d335197 --- /dev/null +++ b/config/pkg/lib/zstd.yml @@ -0,0 +1,19 @@ +zstd: + type: library + artifact: + source: + type: ghrel + repo: facebook/zstd + match: zstd.+\.tar\.gz + prefer-stable: true + metadata: + license-files: [LICENSE] + license: BSD-3-Clause + headers@unix: + - zdict.h + - zstd.h + - zstd_errors.h + static-libs@unix: + - libzstd.a + pkg-configs: + - libzstd diff --git a/src/Package/Library/libcares.php b/src/Package/Library/libcares.php new file mode 100644 index 000000000..fbfb58303 --- /dev/null +++ b/src/Package/Library/libcares.php @@ -0,0 +1,38 @@ +getSourceDir()}/src/lib/thirdparty/apple/dnsinfo.h")) { + FileSystem::createDir("{$lib->getSourceDir()}/src/lib/thirdparty/apple"); + copy(ROOT_DIR . '/src/globals/extra/libcares_dnsinfo.h', "{$lib->getSourceDir()}/src/lib/thirdparty/apple/dnsinfo.h"); + return true; + } + return false; + } + + #[BuildFor('Linux')] + #[BuildFor('Darwin')] + public function build(LibraryPackage $lib): void + { + UnixAutoconfExecutor::create($lib)->configure('--disable-tests')->make(); + + $lib->patchPkgconfPrefix(['libcares.pc'], PKGCONF_PATCH_PREFIX); + } +} diff --git a/src/Package/Library/zstd.php b/src/Package/Library/zstd.php new file mode 100644 index 000000000..ab538358e --- /dev/null +++ b/src/Package/Library/zstd.php @@ -0,0 +1,29 @@ +setBuildDir("{$lib->getSourceDir()}/build/cmake/build") + ->addConfigureArgs( + '-DZSTD_BUILD_STATIC=ON', + '-DZSTD_BUILD_SHARED=OFF', + ) + ->build(); + + $lib->patchPkgconfPrefix(['libzstd.pc'], PKGCONF_PATCH_PREFIX); + } +} From 19e11caa830b00b6525fb69a4e2144dc5258cfc0 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 16:53:04 +0800 Subject: [PATCH 23/99] Add ldap,libcares,libsodium,libunistring, lint all configs --- config/pkg/lib/ldap.yml | 17 ++++++++ config/pkg/lib/libcares.yml | 4 +- config/pkg/lib/libsodium.yml | 15 +++++++ config/pkg/lib/libssh2.yml | 4 +- config/pkg/lib/libunistring.yml | 16 ++++++++ config/pkg/lib/libxml2.yml | 8 ++-- config/pkg/lib/nghttp2.yml | 12 +++--- config/pkg/lib/nghttp3.yml | 4 +- config/pkg/lib/ngtcp2.yml | 12 +++--- config/pkg/lib/xz.yml | 4 +- config/pkg/lib/zstd.yml | 4 +- src/Package/Library/ldap.php | 59 ++++++++++++++++++++++++++++ src/Package/Library/libsodium.php | 24 +++++++++++ src/Package/Library/libunistring.php | 25 ++++++++++++ 14 files changed, 182 insertions(+), 26 deletions(-) create mode 100644 config/pkg/lib/ldap.yml create mode 100644 config/pkg/lib/libsodium.yml create mode 100644 config/pkg/lib/libunistring.yml create mode 100644 src/Package/Library/ldap.php create mode 100644 src/Package/Library/libsodium.php create mode 100644 src/Package/Library/libunistring.php diff --git a/config/pkg/lib/ldap.yml b/config/pkg/lib/ldap.yml new file mode 100644 index 000000000..d1e7db2ae --- /dev/null +++ b/config/pkg/lib/ldap.yml @@ -0,0 +1,17 @@ +ldap: + type: library + artifact: + source: + type: filelist + url: 'https://www.openldap.org/software/download/OpenLDAP/openldap-release/' + regex: '/href="(?openldap-(?[^"]+)\.tgz)"/' + metadata: + license-files: [LICENSE] + depends: + - openssl + - zlib + - gmp + - libsodium + pkg-configs: + - ldap + - lber diff --git a/config/pkg/lib/libcares.yml b/config/pkg/lib/libcares.yml index da0d1ad64..5d3f8c9d1 100644 --- a/config/pkg/lib/libcares.yml +++ b/config/pkg/lib/libcares.yml @@ -17,7 +17,7 @@ libcares: - ares.h - ares_dns.h - ares_nameser.h - static-libs@unix: - - libcares.a pkg-configs: - libcares + static-libs@unix: + - libcares.a diff --git a/config/pkg/lib/libsodium.yml b/config/pkg/lib/libsodium.yml new file mode 100644 index 000000000..f5a551b93 --- /dev/null +++ b/config/pkg/lib/libsodium.yml @@ -0,0 +1,15 @@ +libsodium: + type: library + artifact: + source: + type: ghrel + repo: jedisct1/libsodium + match: 'libsodium-(?!1\.0\.21)\d+(\.\d+)*\.tar\.gz' + prefer-stable: true + binary: hosted + metadata: + license-files: [LICENSE] + pkg-configs: + - libsodium + static-libs@unix: + - libsodium.a diff --git a/config/pkg/lib/libssh2.yml b/config/pkg/lib/libssh2.yml index 4b66ed2b7..8e1d82754 100644 --- a/config/pkg/lib/libssh2.yml +++ b/config/pkg/lib/libssh2.yml @@ -16,7 +16,7 @@ libssh2: - libssh2.h - libssh2_publickey.h - libssh2_sftp.h - static-libs@unix: - - libssh2.a pkg-configs: - libssh2 + static-libs@unix: + - libssh2.a diff --git a/config/pkg/lib/libunistring.yml b/config/pkg/lib/libunistring.yml new file mode 100644 index 000000000..2b2ffd334 --- /dev/null +++ b/config/pkg/lib/libunistring.yml @@ -0,0 +1,16 @@ +libunistring: + type: library + artifact: + source: + type: filelist + url: 'https://ftp.gnu.org/gnu/libunistring/' + regex: '/href="(?libunistring-(?[^"]+)\.tar\.gz)"/' + binary: hosted + metadata: + license-files: [COPYING.LIB] + license: LGPL-3.0-or-later + headers: + - unistr.h + - unistring/ + static-libs@unix: + - libunistring.a diff --git a/config/pkg/lib/libxml2.yml b/config/pkg/lib/libxml2.yml index 4d628ffb0..8ba0f8e18 100644 --- a/config/pkg/lib/libxml2.yml +++ b/config/pkg/lib/libxml2.yml @@ -4,16 +4,16 @@ libxml2: source: type: ghtagtar repo: GNOME/libxml2 - match: 'v2\.\d+\.\d+$' + match: v2\.\d+\.\d+$ metadata: license-files: [Copyright] license: MIT depends@unix: - libiconv - suggests@unix: - - xz - - zlib headers: - libxml2 pkg-configs: - libxml-2.0 + suggests@unix: + - xz + - zlib diff --git a/config/pkg/lib/nghttp2.yml b/config/pkg/lib/nghttp2.yml index 9678d16b2..20a1840db 100644 --- a/config/pkg/lib/nghttp2.yml +++ b/config/pkg/lib/nghttp2.yml @@ -11,14 +11,14 @@ nghttp2: depends: - zlib - openssl + headers: + - nghttp2 + pkg-configs: + - libnghttp2 + static-libs@unix: + - libnghttp2.a suggests: - libxml2 - nghttp3 - ngtcp2 - brotli - headers: - - nghttp2 - static-libs@unix: - - libnghttp2.a - pkg-configs: - - libnghttp2 diff --git a/config/pkg/lib/nghttp3.yml b/config/pkg/lib/nghttp3.yml index 7d15c6020..f9adc05b5 100644 --- a/config/pkg/lib/nghttp3.yml +++ b/config/pkg/lib/nghttp3.yml @@ -13,7 +13,7 @@ nghttp3: - openssl headers: - nghttp3 - static-libs@unix: - - libnghttp3.a pkg-configs: - libnghttp3 + static-libs@unix: + - libnghttp3.a diff --git a/config/pkg/lib/ngtcp2.yml b/config/pkg/lib/ngtcp2.yml index 44b0aaaeb..a609d3ca5 100644 --- a/config/pkg/lib/ngtcp2.yml +++ b/config/pkg/lib/ngtcp2.yml @@ -11,14 +11,14 @@ ngtcp2: license: MIT depends: - openssl - suggests: - - nghttp3 - - brotli headers: - ngtcp2 - static-libs@unix: - - libngtcp2.a - - libngtcp2_crypto_ossl.a pkg-configs: - libngtcp2 - libngtcp2_crypto_ossl + static-libs@unix: + - libngtcp2.a + - libngtcp2_crypto_ossl.a + suggests: + - nghttp3 + - brotli diff --git a/config/pkg/lib/xz.yml b/config/pkg/lib/xz.yml index a64cf2e9a..7d0af682b 100644 --- a/config/pkg/lib/xz.yml +++ b/config/pkg/lib/xz.yml @@ -14,7 +14,7 @@ xz: - libiconv headers@unix: - lzma - static-libs@unix: - - liblzma.a pkg-configs: - liblzma + static-libs@unix: + - liblzma.a diff --git a/config/pkg/lib/zstd.yml b/config/pkg/lib/zstd.yml index 60d335197..3d76e270c 100644 --- a/config/pkg/lib/zstd.yml +++ b/config/pkg/lib/zstd.yml @@ -13,7 +13,7 @@ zstd: - zdict.h - zstd.h - zstd_errors.h - static-libs@unix: - - libzstd.a pkg-configs: - libzstd + static-libs@unix: + - libzstd.a diff --git a/src/Package/Library/ldap.php b/src/Package/Library/ldap.php new file mode 100644 index 000000000..21e7f8c04 --- /dev/null +++ b/src/Package/Library/ldap.php @@ -0,0 +1,59 @@ +getSourceDir() . '/configure', '"-lssl -lcrypto', '"-lssl -lcrypto -lz ' . $extra); + return true; + } + + #[BuildFor('Linux')] + #[BuildFor('Darwin')] + public function build(LibraryPackage $lib): void + { + UnixAutoconfExecutor::create($lib) + ->optionalPackage('openssl', '--with-tls=openssl') + ->optionalPackage('gmp', '--with-mp=gmp') + ->optionalPackage('libsodium', '--with-argon2=libsodium', '--enable-argon2=no') + ->addConfigureArgs( + '--disable-slapd', + '--without-systemd', + '--without-cyrus-sasl', + 'ac_cv_func_pthread_kill_other_threads_np=no' + ) + ->appendEnv([ + 'CFLAGS' => '-Wno-date-time', + 'LDFLAGS' => "-L{$lib->getLibDir()}", + 'CPPFLAGS' => "-I{$lib->getIncludeDir()}", + ]) + ->configure() + ->exec('sed -i -e "s/SUBDIRS= include libraries clients servers tests doc/SUBDIRS= include libraries clients servers/g" Makefile') + ->make(); + + FileSystem::replaceFileLineContainsString( + $lib->getLibDir() . '/pkgconfig/ldap.pc', + 'Libs: -L${libdir} -lldap', + 'Libs: -L${libdir} -lldap -llber' + ); + $lib->patchPkgconfPrefix(['ldap.pc', 'lber.pc']); + $lib->patchLaDependencyPrefix(); + } +} diff --git a/src/Package/Library/libsodium.php b/src/Package/Library/libsodium.php new file mode 100644 index 000000000..50d706ba4 --- /dev/null +++ b/src/Package/Library/libsodium.php @@ -0,0 +1,24 @@ +configure()->make(); + + // Patch pkg-config file + $lib->patchPkgconfPrefix(['libsodium.pc'], PKGCONF_PATCH_PREFIX); + } +} diff --git a/src/Package/Library/libunistring.php b/src/Package/Library/libunistring.php new file mode 100644 index 000000000..5f5575dc2 --- /dev/null +++ b/src/Package/Library/libunistring.php @@ -0,0 +1,25 @@ +configure('--disable-nls') + ->make(); + + $lib->patchLaDependencyPrefix(); + } +} From a6c79e30a8d9b53e2a56d6e19695066cd96b05f8 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 2 Feb 2026 16:53:28 +0800 Subject: [PATCH 24/99] Add dump license files after installing --- src/StaticPHP/Package/PackageInstaller.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index c4262d9a3..9ceb21078 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -17,6 +17,7 @@ use StaticPHP\Util\FileSystem; use StaticPHP\Util\GlobalEnvManager; use StaticPHP\Util\InteractiveTerm; +use StaticPHP\Util\LicenseDumper; use StaticPHP\Util\V2CompatLayer; use ZM\Logger\ConsoleColor; @@ -208,6 +209,11 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): } } } + + $this->dumpLicenseFiles($this->packages); + if ($interactive) { + InteractiveTerm::success('Exported package licenses', true); + } } public function isBuildPackage(Package|string $package): bool @@ -460,6 +466,21 @@ public function getPhpExtensionPackage(string $package_or_ext_name): ?PhpExtensi return null; } + /** + * @param Package[] $packages + */ + private function dumpLicenseFiles(array $packages): void + { + $dumper = new LicenseDumper(); + foreach ($packages as $package) { + $artifact = $package->getArtifact(); + if ($artifact !== null) { + $dumper->addArtifacts([$artifact->getName()]); + } + } + $dumper->dump(BUILD_ROOT_PATH . '/license'); + } + /** * Validate that a package has required artifacts. */ From 3492992b562eac4c9dcf5703496c0defa2be873e Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 09:42:53 +0800 Subject: [PATCH 25/99] Add ncurses --- config/pkg/lib/ncurses.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 config/pkg/lib/ncurses.yml diff --git a/config/pkg/lib/ncurses.yml b/config/pkg/lib/ncurses.yml new file mode 100644 index 000000000..be50a6c7f --- /dev/null +++ b/config/pkg/lib/ncurses.yml @@ -0,0 +1,12 @@ +ncurses: + type: library + artifact: + source: + type: filelist + url: 'https://ftp.gnu.org/pub/gnu/ncurses/' + regex: '/href="(?ncurses-(?[^"]+)\.tar\.gz)"/' + binary: hosted + metadata: + license-files: ['COPYING'] + static-libs@unix: + - libncurses.a From fddcdb87962c7787e4ae2415cf89022fac1247ab Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 10:05:11 +0800 Subject: [PATCH 26/99] Add filelist downloader debug message --- src/StaticPHP/Artifact/Downloader/Type/FileList.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/StaticPHP/Artifact/Downloader/Type/FileList.php b/src/StaticPHP/Artifact/Downloader/Type/FileList.php index 8290a1cc3..314f0c330 100644 --- a/src/StaticPHP/Artifact/Downloader/Type/FileList.php +++ b/src/StaticPHP/Artifact/Downloader/Type/FileList.php @@ -20,6 +20,7 @@ public function download(string $name, array $config, ArtifactDownloader $downlo throw new DownloaderException("Failed to get {$name} file list from {$config['url']}"); } $versions = []; + logger()->debug('Matched ' . count($matches['version']) . " versions for {$name}"); foreach ($matches['version'] as $i => $version) { $lower = strtolower($version); foreach (['alpha', 'beta', 'rc', 'pre', 'nightly', 'snapshot', 'dev'] as $beta) { From e732543bd731dce1afa62a1d1c8184846246d2f6 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 10:05:28 +0800 Subject: [PATCH 27/99] Fix wrong debug message show --- src/StaticPHP/Artifact/ArtifactDownloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StaticPHP/Artifact/ArtifactDownloader.php b/src/StaticPHP/Artifact/ArtifactDownloader.php index b53ddd8a2..b4d79b769 100644 --- a/src/StaticPHP/Artifact/ArtifactDownloader.php +++ b/src/StaticPHP/Artifact/ArtifactDownloader.php @@ -438,7 +438,7 @@ private function downloadWithType(Artifact $artifact, int $current, int $total, break; } } - $vvv = ApplicationContext::isDebug() ? "\nIf the problem persists, consider using `-vvv` to enable verbose mode, and disable parallel downloading for more details." : ''; + $vvv = !ApplicationContext::isDebug() ? "\nIf the problem persists, consider using `-vvv` to enable verbose mode, or disable parallel downloading for more details." : ''; throw new DownloaderException("Download artifact '{$artifact->getName()}' failed. Please check your internet connection and try again.{$vvv}"); } From e4d6723b01f2b33f19282f35a8bc2b114d55a634 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 10:05:39 +0800 Subject: [PATCH 28/99] Add gettext --- config/pkg/lib/gettext.yml | 19 +++++++++++++ src/Package/Library/gettext.php | 48 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 config/pkg/lib/gettext.yml create mode 100644 src/Package/Library/gettext.php diff --git a/config/pkg/lib/gettext.yml b/config/pkg/lib/gettext.yml new file mode 100644 index 000000000..4be2e7d72 --- /dev/null +++ b/config/pkg/lib/gettext.yml @@ -0,0 +1,19 @@ +gettext: + type: library + artifact: + source: + type: filelist + url: 'https://ftp.gnu.org/pub/gnu/gettext/' + regex: '/href="(?gettext-(?[^"]+)\.tar\.xz)"/' + metadata: + license-files: [gettext-runtime/intl/COPYING.LIB] + license: LGPL-2.1-or-later + static-libs@unix: + - libintl.a + depends: + - libiconv + suggests: + - ncurses + - libxml2 + frameworks: + - CoreFoundation diff --git a/src/Package/Library/gettext.php b/src/Package/Library/gettext.php new file mode 100644 index 000000000..7af69905e --- /dev/null +++ b/src/Package/Library/gettext.php @@ -0,0 +1,48 @@ +optionalPackage('ncurses', "--with-libncurses-prefix={$pkg->getBuildRootPath()}") + ->optionalPackage('libxml2', "--with-libxml2-prefix={$pkg->getBuildRootPath()}") + ->addConfigureArgs( + '--disable-java', + '--disable-c++', + '--disable-d', + '--disable-rpath', + '--disable-modula2', + '--disable-libasprintf', + '--with-included-libintl', + "--with-iconv-prefix={$pkg->getBuildRootPath()}", + ); + + // zts + if ($builder->getOption('enable-zts')) { + $autoconf->addConfigureArgs('--enable-threads=isoc+posix') + ->appendEnv([ + 'CFLAGS' => '-lpthread -D_REENTRANT', + 'LDFLGAS' => '-lpthread', + ]); + } else { + $autoconf->addConfigureArgs('--disable-threads'); + } + + $autoconf->configure()->make(dir: "{$pkg->getSourceDir()}/gettext-runtime/intl"); + $pkg->patchLaDependencyPrefix(); + } +} From 2e8f6bbb310989088fc69a79cc92f5587e35758f Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 10:10:10 +0800 Subject: [PATCH 29/99] Add idn2 --- config/pkg/lib/idn2.yml | 21 +++++++++++++++++++++ src/Package/Library/idn2.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 config/pkg/lib/idn2.yml create mode 100644 src/Package/Library/idn2.php diff --git a/config/pkg/lib/idn2.yml b/config/pkg/lib/idn2.yml new file mode 100644 index 000000000..c2970d3ec --- /dev/null +++ b/config/pkg/lib/idn2.yml @@ -0,0 +1,21 @@ +idn2: + type: library + artifact: + source: + type: filelist + url: 'https://ftp.gnu.org/gnu/libidn/' + regex: '/href="(?libidn2-(?[^"]+)\.tar\.gz)"/' + metadata: + license-files: ['COPYING.LESSERv3'] + license: LGPL-3.0-or-later + pkg-configs: + - libidn2 + headers: + - idn2.h + suggests@unix: + - libiconv + - gettext + - libunistring + depends@macos: + - libiconv + - gettext diff --git a/src/Package/Library/idn2.php b/src/Package/Library/idn2.php new file mode 100644 index 000000000..3cffd9be5 --- /dev/null +++ b/src/Package/Library/idn2.php @@ -0,0 +1,33 @@ +configure( + '--disable-nls', + '--disable-doc', + '--enable-year2038', + '--disable-rpath' + ) + ->optionalPackage('libiconv', '--with-libiconv-prefix=' . BUILD_ROOT_PATH) + ->optionalPackage('libunistring', '--with-libunistring-prefix=' . BUILD_ROOT_PATH) + ->optionalPackage('gettext', '--with-libnintl-prefix=' . BUILD_ROOT_PATH) + ->make(); + $lib->patchPkgconfPrefix(['libidn2.pc']); + $lib->patchLaDependencyPrefix(); + } +} From 668881960520109d636f851c91140e8586951d15 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 10:17:13 +0800 Subject: [PATCH 30/99] Add libedit --- config/pkg/lib/libedit.yml | 15 +++++++++++++++ src/Package/Library/libedit.php | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 config/pkg/lib/libedit.yml diff --git a/config/pkg/lib/libedit.yml b/config/pkg/lib/libedit.yml new file mode 100644 index 000000000..c782238bf --- /dev/null +++ b/config/pkg/lib/libedit.yml @@ -0,0 +1,15 @@ +libedit: + type: library + artifact: + source: + type: filelist + url: 'https://thrysoee.dk/editline/' + regex: '/href="(?libedit-(?[^"]+)\.tar\.gz)"/' + binary: hosted + metadata: + license-files: ['COPYING'] + license: BSD-3-Clause + static-libs@unix: + - libedit.a + depends: + - ncurses diff --git a/src/Package/Library/libedit.php b/src/Package/Library/libedit.php index 08a435da7..c06dbae3f 100644 --- a/src/Package/Library/libedit.php +++ b/src/Package/Library/libedit.php @@ -4,9 +4,9 @@ namespace Package\Library; -use StaticPHP\Attribute\Package\BeforeStage; use StaticPHP\Attribute\Package\BuildFor; use StaticPHP\Attribute\Package\Library; +use StaticPHP\Attribute\Package\PatchBeforeBuild; use StaticPHP\Package\LibraryPackage; use StaticPHP\Runtime\Executor\UnixAutoconfExecutor; use StaticPHP\Util\FileSystem; @@ -14,7 +14,7 @@ #[Library('libedit')] class libedit extends LibraryPackage { - #[BeforeStage(stage: 'build')] + #[PatchBeforeBuild] public function patchBeforeBuild(): void { FileSystem::replaceFileRegex( From a2409d9c0fba0c45dedd5c27f9be7e531dd90a99 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 10:59:16 +0800 Subject: [PATCH 31/99] Add getSourceRoot for artifacts --- src/Package/Target/php/unix.php | 2 +- src/StaticPHP/Artifact/Artifact.php | 13 +++++++++++++ src/StaticPHP/Package/Package.php | 13 +++++++++++++ .../Runtime/Executor/UnixAutoconfExecutor.php | 8 ++++---- .../Runtime/Executor/UnixCMakeExecutor.php | 4 ++-- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Package/Target/php/unix.php b/src/Package/Target/php/unix.php index 23d465dfb..1ce2ff95a 100644 --- a/src/Package/Target/php/unix.php +++ b/src/Package/Target/php/unix.php @@ -436,7 +436,7 @@ private function processLibphpSoFile(string $libphpSo, PackageInstaller $install */ private function makeVars(PackageInstaller $installer): array { - $config = (new SPCConfigUtil(['libs_only_deps' => true]))->config(array_map(fn ($x) => $x->getName(), $installer->getResolvedPackages())); + $config = new SPCConfigUtil(['libs_only_deps' => true])->config(array_map(fn ($x) => $x->getName(), $installer->getResolvedPackages())); $static = ApplicationContext::get(ToolchainInterface::class)->isStatic() ? '-all-static' : ''; $pie = SystemTarget::getTargetOS() === 'Linux' ? '-pie' : ''; diff --git a/src/StaticPHP/Artifact/Artifact.php b/src/StaticPHP/Artifact/Artifact.php index b5cf74c00..67876d900 100644 --- a/src/StaticPHP/Artifact/Artifact.php +++ b/src/StaticPHP/Artifact/Artifact.php @@ -268,6 +268,19 @@ public function getSourceDir(): string return FileSystem::convertPath(SOURCE_PATH . '/' . $path); } + /** + * Get source build root directory. + * It's only worked when 'source-root' is defined in artifact config. + * Normally it's equal to source dir. + */ + public function getSourceRoot(): string + { + if (isset($this->config['metadata']['source-root'])) { + return $this->getSourceDir() . '/' . ltrim($this->config['metadata']['source-root'], '/'); + } + return $this->getSourceDir(); + } + /** * Get binary extraction directory and mode. * diff --git a/src/StaticPHP/Package/Package.php b/src/StaticPHP/Package/Package.php index a0f415d13..cd4f38409 100644 --- a/src/StaticPHP/Package/Package.php +++ b/src/StaticPHP/Package/Package.php @@ -212,6 +212,19 @@ public function getSourceDir(): string throw new SPCInternalException("Source directory for package {$this->name} is not available because the source artifact is missing."); } + /** + * Get source build root directory. + * It's only worked when 'source-root' is defined in artifact config. + * Normally it's equal to source dir. + */ + public function getSourceRoot(): string + { + if (($artifact = $this->getArtifact()) && $artifact->hasSource()) { + return $artifact->getSourceRoot(); + } + throw new SPCInternalException("Source root for package {$this->name} is not available because the source artifact is missing."); + } + /** * Check if the package has a binary available for current OS and architecture. */ diff --git a/src/StaticPHP/Runtime/Executor/UnixAutoconfExecutor.php b/src/StaticPHP/Runtime/Executor/UnixAutoconfExecutor.php index e75a7ed06..41bc6e784 100644 --- a/src/StaticPHP/Runtime/Executor/UnixAutoconfExecutor.php +++ b/src/StaticPHP/Runtime/Executor/UnixAutoconfExecutor.php @@ -169,7 +169,7 @@ private function getDefaultConfigureArgs(): array */ private function initShell(): void { - $this->shell = shell()->cd($this->package->getSourceDir())->initializeEnv($this->package)->appendEnv([ + $this->shell = shell()->cd($this->package->getSourceRoot())->initializeEnv($this->package)->appendEnv([ 'CFLAGS' => "-I{$this->package->getIncludeDir()}", 'CXXFLAGS' => "-I{$this->package->getIncludeDir()}", 'LDFLAGS' => "-L{$this->package->getLibDir()}", @@ -185,12 +185,12 @@ private function seekLogFileOnException(mixed $callable): static $callable(); return $this; } catch (SPCException $e) { - if (file_exists("{$this->package->getSourceDir()}/config.log")) { - logger()->debug("Config log file found: {$this->package->getSourceDir()}/config.log"); + if (file_exists("{$this->package->getSourceRoot()}/config.log")) { + logger()->debug("Config log file found: {$this->package->getSourceRoot()}/config.log"); $log_file = "lib.{$this->package->getName()}.console.log"; logger()->debug('Saved config log file to: ' . SPC_LOGS_DIR . "/{$log_file}"); $e->addExtraLogFile("{$this->package->getName()} library config.log", $log_file); - copy("{$this->package->getSourceDir()}/config.log", SPC_LOGS_DIR . "/{$log_file}"); + copy("{$this->package->getSourceRoot()}/config.log", SPC_LOGS_DIR . "/{$log_file}"); } throw $e; } diff --git a/src/StaticPHP/Runtime/Executor/UnixCMakeExecutor.php b/src/StaticPHP/Runtime/Executor/UnixCMakeExecutor.php index 107aac116..3d628b894 100644 --- a/src/StaticPHP/Runtime/Executor/UnixCMakeExecutor.php +++ b/src/StaticPHP/Runtime/Executor/UnixCMakeExecutor.php @@ -233,7 +233,7 @@ private function getDefaultCMakeArgs(): array private function initBuildDir(): void { if ($this->build_dir === null) { - $this->build_dir = "{$this->package->getSourceDir()}/build"; + $this->build_dir = "{$this->package->getSourceRoot()}/build"; } } @@ -295,7 +295,7 @@ private function makeCmakeToolchainFile(): string */ private function initShell(): void { - $this->shell = shell()->cd($this->package->getSourceDir())->initializeEnv($this->package)->appendEnv([ + $this->shell = shell()->cd($this->package->getSourceRoot())->initializeEnv($this->package)->appendEnv([ 'CFLAGS' => "-I{$this->package->getIncludeDir()}", 'CXXFLAGS' => "-I{$this->package->getIncludeDir()}", 'LDFLAGS' => "-L{$this->package->getLibDir()}", From 09ddd2fdd8401db5b4f4b64d6175c2b5f3c3a414 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 10:59:37 +0800 Subject: [PATCH 32/99] Add methods to retrieve package sub-dependencies and configuration --- src/StaticPHP/Util/DependencyResolver.php | 85 +++++++++++++++++ src/StaticPHP/Util/SPCConfigUtil.php | 111 ++++++++++++++++++++++ 2 files changed, 196 insertions(+) diff --git a/src/StaticPHP/Util/DependencyResolver.php b/src/StaticPHP/Util/DependencyResolver.php index defb00ba2..b03f80892 100644 --- a/src/StaticPHP/Util/DependencyResolver.php +++ b/src/StaticPHP/Util/DependencyResolver.php @@ -55,6 +55,58 @@ public static function resolve(array $packages, array $dependency_overrides = [] return $resolved; } + /** + * Get all dependencies of a specific package within a resolved package set. + * This is useful when you need to get build flags for a specific library and its deps. + * + * The method will only include dependencies that exist in the resolved set, + * which properly handles optional dependencies (suggests) - only those that + * were actually resolved will be included. + * + * @param string $package_name The package to get dependencies for + * @param string[] $resolved_packages The resolved package list (from resolve()) + * @param bool $include_suggests Whether to include suggests that are in resolved set + * @return string[] Dependencies of the package (NOT including itself), ordered for building + */ + public static function getSubDependencies(string $package_name, array $resolved_packages, bool $include_suggests = false): array + { + // Create a lookup set for O(1) membership check + $resolved_set = array_flip($resolved_packages); + + // Verify the target package is in the resolved set + if (!isset($resolved_set[$package_name])) { + return []; + } + + // Build dependency map from config + $dep_map = []; + foreach ($resolved_packages as $pkg) { + $dep_map[$pkg] = [ + 'depends' => PackageConfig::get($pkg, 'depends', []), + 'suggests' => PackageConfig::get($pkg, 'suggests', []), + ]; + } + + // Collect all sub-dependencies recursively (excluding the package itself) + $visited = []; + $sorted = []; + + // Get dependencies to process for the target package + $deps = $dep_map[$package_name]['depends'] ?? []; + if ($include_suggests) { + $deps = array_merge($deps, $dep_map[$package_name]['suggests'] ?? []); + } + + // Only visit dependencies that are in the resolved set + foreach ($deps as $dep) { + if (isset($resolved_set[$dep])) { + self::visitSubDeps($dep, $dep_map, $resolved_set, $include_suggests, $visited, $sorted); + } + } + + return $sorted; + } + /** * Build a reverse dependency map for the resolved packages. * For each package that is depended upon, list which packages depend on it. @@ -89,6 +141,39 @@ private static function buildReverseDependencyMap(array $resolved, array $dep_li return $why; } + /** + * Recursive helper for getSubDependencies. + * Visits dependencies in topological order (dependencies first). + */ + private static function visitSubDeps( + string $pkg_name, + array $dep_map, + array $resolved_set, + bool $include_suggests, + array &$visited, + array &$sorted + ): void { + if (isset($visited[$pkg_name])) { + return; + } + $visited[$pkg_name] = true; + + // Get dependencies to process + $deps = $dep_map[$pkg_name]['depends'] ?? []; + if ($include_suggests) { + $deps = array_merge($deps, $dep_map[$pkg_name]['suggests'] ?? []); + } + + // Only visit dependencies that are in the resolved set + foreach ($deps as $dep) { + if (isset($resolved_set[$dep])) { + self::visitSubDeps($dep, $dep_map, $resolved_set, $include_suggests, $visited, $sorted); + } + } + + $sorted[] = $pkg_name; + } + /** * Visitor pattern implementation for dependency resolution. * diff --git a/src/StaticPHP/Util/SPCConfigUtil.php b/src/StaticPHP/Util/SPCConfigUtil.php index 3a20e56fe..a31b771f8 100644 --- a/src/StaticPHP/Util/SPCConfigUtil.php +++ b/src/StaticPHP/Util/SPCConfigUtil.php @@ -149,6 +149,117 @@ public function getLibraryConfig(array|LibraryPackage $lib, bool $include_sugges return $ret; } + /** + * Get build configuration for a package and its sub-dependencies within a resolved set. + * + * This is useful when you need to statically link something against a specific + * library and all its transitive dependencies. It properly handles optional + * dependencies by only including those that were actually resolved. + * + * @param string $package_name The package to get config for + * @param string[] $resolved_packages The full resolved package list + * @param bool $include_suggests Whether to include resolved suggests + * @return array{ + * cflags: string, + * ldflags: string, + * libs: string + * } + */ + public function getPackageDepsConfig(string $package_name, array $resolved_packages, bool $include_suggests = false): array + { + // Get sub-dependencies within the resolved set + $sub_deps = DependencyResolver::getSubDependencies($package_name, $resolved_packages, $include_suggests); + + if (empty($sub_deps)) { + return [ + 'cflags' => '', + 'ldflags' => '', + 'libs' => '', + ]; + } + + // Use libs_only_deps mode and no_php for library linking + $save_no_php = $this->no_php; + $save_libs_only_deps = $this->libs_only_deps; + $this->no_php = true; + $this->libs_only_deps = true; + + $ret = $this->configWithResolvedPackages($sub_deps); + + $this->no_php = $save_no_php; + $this->libs_only_deps = $save_libs_only_deps; + + return $ret; + } + + /** + * Get configuration using already-resolved packages (skip dependency resolution). + * + * @param string[] $resolved_packages Already resolved package names in build order + * @return array{ + * cflags: string, + * ldflags: string, + * libs: string + * } + */ + public function configWithResolvedPackages(array $resolved_packages): array + { + $ldflags = $this->getLdflagsString(); + $cflags = $this->getIncludesString($resolved_packages); + $libs = $this->getLibsString($resolved_packages, !$this->absolute_libs); + + // additional OS-specific libraries (e.g. macOS -lresolv) + if ($extra_libs = SystemTarget::getRuntimeLibs()) { + $libs .= " {$extra_libs}"; + } + + $extra_env = getenv('SPC_EXTRA_LIBS'); + if (is_string($extra_env) && !empty($extra_env)) { + $libs .= " {$extra_env}"; + } + + // package frameworks + if (SystemTarget::getTargetOS() === 'Darwin') { + $libs .= " {$this->getFrameworksString($resolved_packages)}"; + } + + // C++ + if ($this->hasCpp($resolved_packages)) { + $libcpp = SystemTarget::getTargetOS() === 'Darwin' ? '-lc++' : '-lstdc++'; + $libs = str_replace($libcpp, '', $libs) . " {$libcpp}"; + } + + if ($this->libs_only_deps) { + // mimalloc must come first + if (in_array('mimalloc', $resolved_packages) && file_exists(BUILD_LIB_PATH . '/libmimalloc.a')) { + $libs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace([BUILD_LIB_PATH . '/libmimalloc.a', '-lmimalloc'], ['', ''], $libs); + } + return [ + 'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags), + 'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags), + 'libs' => clean_spaces(getenv('LIBS') . ' ' . $libs), + ]; + } + + // embed + if (!$this->no_php) { + $libs = "-lphp {$libs} -lc"; + } + + $allLibs = getenv('LIBS') . ' ' . $libs; + + // mimalloc must come first + if (in_array('mimalloc', $resolved_packages) && file_exists(BUILD_LIB_PATH . '/libmimalloc.a')) { + $allLibs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace([BUILD_LIB_PATH . '/libmimalloc.a', '-lmimalloc'], ['', ''], $allLibs); + } + + return [ + 'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags), + 'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags), + 'libs' => clean_spaces($allLibs), + ]; + } + private function hasCpp(array $packages): bool { foreach ($packages as $package) { From c536fedff7f2e447cc2200fb0234b5077640a9e7 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 10:59:56 +0800 Subject: [PATCH 33/99] Add krb5 and lint configs --- config/pkg/lib/gettext.yml | 8 ++--- config/pkg/lib/idn2.yml | 12 +++---- config/pkg/lib/krb5.yml | 23 +++++++++++++ config/pkg/lib/libedit.yml | 6 ++-- config/pkg/lib/ncurses.yml | 2 +- config/pkg/lib/zlib.yml | 2 +- src/Package/Library/krb5.php | 63 ++++++++++++++++++++++++++++++++++++ 7 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 config/pkg/lib/krb5.yml create mode 100644 src/Package/Library/krb5.php diff --git a/config/pkg/lib/gettext.yml b/config/pkg/lib/gettext.yml index 4be2e7d72..5b1f57a63 100644 --- a/config/pkg/lib/gettext.yml +++ b/config/pkg/lib/gettext.yml @@ -8,12 +8,12 @@ gettext: metadata: license-files: [gettext-runtime/intl/COPYING.LIB] license: LGPL-2.1-or-later - static-libs@unix: - - libintl.a depends: - libiconv + frameworks: + - CoreFoundation + static-libs@unix: + - libintl.a suggests: - ncurses - libxml2 - frameworks: - - CoreFoundation diff --git a/config/pkg/lib/idn2.yml b/config/pkg/lib/idn2.yml index c2970d3ec..5d2e20439 100644 --- a/config/pkg/lib/idn2.yml +++ b/config/pkg/lib/idn2.yml @@ -6,16 +6,16 @@ idn2: url: 'https://ftp.gnu.org/gnu/libidn/' regex: '/href="(?libidn2-(?[^"]+)\.tar\.gz)"/' metadata: - license-files: ['COPYING.LESSERv3'] + license-files: [COPYING.LESSERv3] license: LGPL-3.0-or-later - pkg-configs: - - libidn2 + depends@macos: + - libiconv + - gettext headers: - idn2.h + pkg-configs: + - libidn2 suggests@unix: - libiconv - gettext - libunistring - depends@macos: - - libiconv - - gettext diff --git a/config/pkg/lib/krb5.yml b/config/pkg/lib/krb5.yml new file mode 100644 index 000000000..305a95a18 --- /dev/null +++ b/config/pkg/lib/krb5.yml @@ -0,0 +1,23 @@ +krb5: + type: library + artifact: + source: + type: ghtagtar + repo: krb5/krb5 + match: krb5.+-final + metadata: + license-files: [NOTICE] + license: BSD-3-Clause + source-root: src + depends: + - openssl + frameworks: + - Kerberos + headers: + - krb5.h + - gssapi/gssapi.h + pkg-configs: + - krb5-gssapi + suggests: + - ldap + - libedit diff --git a/config/pkg/lib/libedit.yml b/config/pkg/lib/libedit.yml index c782238bf..02d6dd810 100644 --- a/config/pkg/lib/libedit.yml +++ b/config/pkg/lib/libedit.yml @@ -7,9 +7,9 @@ libedit: regex: '/href="(?libedit-(?[^"]+)\.tar\.gz)"/' binary: hosted metadata: - license-files: ['COPYING'] + license-files: [COPYING] license: BSD-3-Clause - static-libs@unix: - - libedit.a depends: - ncurses + static-libs@unix: + - libedit.a diff --git a/config/pkg/lib/ncurses.yml b/config/pkg/lib/ncurses.yml index be50a6c7f..cbc1ba676 100644 --- a/config/pkg/lib/ncurses.yml +++ b/config/pkg/lib/ncurses.yml @@ -7,6 +7,6 @@ ncurses: regex: '/href="(?ncurses-(?[^"]+)\.tar\.gz)"/' binary: hosted metadata: - license-files: ['COPYING'] + license-files: [COPYING] static-libs@unix: - libncurses.a diff --git a/config/pkg/lib/zlib.yml b/config/pkg/lib/zlib.yml index 9ca70f426..cf7f11ba0 100644 --- a/config/pkg/lib/zlib.yml +++ b/config/pkg/lib/zlib.yml @@ -7,7 +7,7 @@ zlib: match: zlib.+\.tar\.gz binary: hosted metadata: - license-files: ['{registry_root}/src/globals/licenses/zlib.txt'] + license-files: ['@/zlib.txt'] license: Zlib-Custom headers: - zlib.h diff --git a/src/Package/Library/krb5.php b/src/Package/Library/krb5.php new file mode 100644 index 000000000..303c3b63e --- /dev/null +++ b/src/Package/Library/krb5.php @@ -0,0 +1,63 @@ +cd($lib->getSourceRoot())->exec('autoreconf -if'); + + $resolved = array_keys($installer->getResolvedPackages()); + $spc = new SPCConfigUtil(['no_php' => true, 'libs_only_deps' => true]); + $config = $spc->getPackageDepsConfig($lib->getName(), $resolved, include_suggests: true); + $extraEnv = [ + 'CFLAGS' => '-fcommon', + 'LIBS' => $config['libs'], + ]; + if (getenv('SPC_LD_LIBRARY_PATH') && getenv('SPC_LIBRARY_PATH')) { + $extraEnv = [...$extraEnv, ...[ + 'LD_LIBRARY_PATH' => getenv('SPC_LD_LIBRARY_PATH'), + 'LIBRARY_PATH' => getenv('SPC_LIBRARY_PATH'), + ]]; + } + $args = [ + '--disable-nls', + '--disable-rpath', + '--without-system-verto', + ]; + if (SystemTarget::getTargetOS() === 'Darwin') { + $extraEnv['LDFLAGS'] = '-framework Kerberos'; + $args[] = 'ac_cv_func_secure_getenv=no'; + } + UnixAutoconfExecutor::create($lib) + ->appendEnv($extraEnv) + ->optionalPackage('ldap', '--with-ldap', '--without-ldap') + ->optionalPackage('libedit', '--with-libedit', '--without-libedit') + ->configure(...$args) + ->make(); + $lib->patchPkgconfPrefix([ + 'krb5-gssapi.pc', + 'krb5.pc', + 'kadm-server.pc', + 'kadm-client.pc', + 'kdb.pc', + 'mit-krb5-gssapi.pc', + 'mit-krb5.pc', + 'gssrpc.pc', + ]); + } +} From 103b5b35853f69813399b5446f5ff450e7fe2d2c Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 11:22:32 +0800 Subject: [PATCH 34/99] Upgrade phpstan to v2 --- composer.json | 2 +- composer.lock | 299 +++++++++--------- phpstan.neon | 6 +- src/Package/Target/php.php | 1 - src/StaticPHP/Artifact/ArtifactDownloader.php | 2 +- src/StaticPHP/Artifact/ArtifactExtractor.php | 1 + src/StaticPHP/Registry/PackageLoader.php | 6 +- src/StaticPHP/Util/LicenseDumper.php | 2 +- src/StaticPHP/Util/System/UnixUtil.php | 2 +- 9 files changed, 162 insertions(+), 159 deletions(-) diff --git a/composer.json b/composer.json index fb17f9a0d..fbdbc94b8 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "captainhook/hook-installer": "^1.0", "friendsofphp/php-cs-fixer": "^3.60", "humbug/box": "^4.5.0 || ^4.6.0", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^10.3 || ^9.5" }, "autoload": { diff --git a/composer.lock b/composer.lock index a0538ce8e..40489a068 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "edb3243ddaa8b05d8f6545266a146e93", + "content-hash": "f30595c9c60e55083112410cd1ffb203", "packages": [ { "name": "laravel/prompts", - "version": "v0.3.8", + "version": "v0.3.11", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "096748cdfb81988f60090bbb839ce3205ace0d35" + "reference": "dd2a2ed95acacbcccd32fd98dee4c946ae7a7217" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35", - "reference": "096748cdfb81988f60090bbb839ce3205ace0d35", + "url": "https://api.github.com/repos/laravel/prompts/zipball/dd2a2ed95acacbcccd32fd98dee4c946ae7a7217", + "reference": "dd2a2ed95acacbcccd32fd98dee4c946ae7a7217", "shasum": "" }, "require": { @@ -61,22 +61,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.8" + "source": "https://github.com/laravel/prompts/tree/v0.3.11" }, - "time": "2025-11-21T20:52:52+00:00" + "time": "2026-01-27T02:55:06+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.7", + "version": "v2.0.8", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd" + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd", - "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7581a4407012f5f53365e11bafc520fd7f36bc9b", + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b", "shasum": "" }, "require": { @@ -124,7 +124,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-11-21T20:52:36+00:00" + "time": "2026-01-08T16:22:46+00:00" }, { "name": "nette/php-generator", @@ -200,16 +200,16 @@ }, { "name": "nette/utils", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0" + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", - "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", + "url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72", + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72", "shasum": "" }, "require": { @@ -283,9 +283,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.0" + "source": "https://github.com/nette/utils/tree/v4.1.1" }, - "time": "2025-12-01T17:49:23+00:00" + "time": "2025-12-22T12:14:32+00:00" }, { "name": "php-di/invoker", @@ -520,16 +520,16 @@ }, { "name": "symfony/console", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8" + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", - "reference": "0bc0f45254b99c58d45a8fbf9fb955d46cbd1bb8", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", "shasum": "" }, "require": { @@ -594,7 +594,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.0" + "source": "https://github.com/symfony/console/tree/v7.4.4" }, "funding": [ { @@ -614,7 +614,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1020,16 +1020,16 @@ }, { "name": "symfony/process", - "version": "v7.4.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8" + "reference": "608476f4604102976d687c483ac63a79ba18cc97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", "shasum": "" }, "require": { @@ -1061,7 +1061,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.0" + "source": "https://github.com/symfony/process/tree/v7.4.5" }, "funding": [ { @@ -1081,7 +1081,7 @@ "type": "tidelift" } ], - "time": "2025-10-16T11:21:06+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/service-contracts", @@ -1172,16 +1172,16 @@ }, { "name": "symfony/string", - "version": "v8.0.0", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f929eccf09531078c243df72398560e32fa4cf4f" + "reference": "758b372d6882506821ed666032e43020c4f57194" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f929eccf09531078c243df72398560e32fa4cf4f", - "reference": "f929eccf09531078c243df72398560e32fa4cf4f", + "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194", + "reference": "758b372d6882506821ed666032e43020c4f57194", "shasum": "" }, "require": { @@ -1238,7 +1238,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.0" + "source": "https://github.com/symfony/string/tree/v8.0.4" }, "funding": [ { @@ -1258,20 +1258,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:37:55+00:00" + "time": "2026-01-12T12:37:40+00:00" }, { "name": "symfony/yaml", - "version": "v7.4.0", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810" + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6c84a4b55aee4cd02034d1c528e83f69ddf63810", - "reference": "6c84a4b55aee4cd02034d1c528e83f69ddf63810", + "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", "shasum": "" }, "require": { @@ -1314,7 +1314,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.0" + "source": "https://github.com/symfony/yaml/tree/v7.4.1" }, "funding": [ { @@ -1334,7 +1334,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2025-12-04T18:11:45+00:00" }, { "name": "zhamao/logger", @@ -1719,16 +1719,16 @@ }, { "name": "amphp/parallel", - "version": "v2.3.2", + "version": "v2.3.3", "source": { "type": "git", "url": "https://github.com/amphp/parallel.git", - "reference": "321b45ae771d9c33a068186b24117e3cd1c48dce" + "reference": "296b521137a54d3a02425b464e5aee4c93db2c60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/parallel/zipball/321b45ae771d9c33a068186b24117e3cd1c48dce", - "reference": "321b45ae771d9c33a068186b24117e3cd1c48dce", + "url": "https://api.github.com/repos/amphp/parallel/zipball/296b521137a54d3a02425b464e5aee4c93db2c60", + "reference": "296b521137a54d3a02425b464e5aee4c93db2c60", "shasum": "" }, "require": { @@ -1791,7 +1791,7 @@ ], "support": { "issues": "https://github.com/amphp/parallel/issues", - "source": "https://github.com/amphp/parallel/tree/v2.3.2" + "source": "https://github.com/amphp/parallel/tree/v2.3.3" }, "funding": [ { @@ -1799,7 +1799,7 @@ "type": "github" } ], - "time": "2025-08-27T21:55:40+00:00" + "time": "2025-11-15T06:23:42+00:00" }, { "name": "amphp/parser", @@ -2217,7 +2217,7 @@ }, { "name": "captainhook/captainhook-phar", - "version": "5.27.3", + "version": "5.27.5", "source": { "type": "git", "url": "https://github.com/captainhook-git/captainhook-phar.git", @@ -2271,7 +2271,7 @@ ], "support": { "issues": "https://github.com/captainhook-git/captainhook/issues", - "source": "https://github.com/captainhook-git/captainhook-phar/tree/5.27.3" + "source": "https://github.com/captainhook-git/captainhook-phar/tree/5.27.5" }, "funding": [ { @@ -2968,16 +2968,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.91.0", + "version": "v3.93.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "c4a25f20390337789c26b693ae46faa125040352" + "reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/c4a25f20390337789c26b693ae46faa125040352", - "reference": "c4a25f20390337789c26b693ae46faa125040352", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b3546ab487c0762c39f308dc1ec0ea2c461fc21a", + "reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a", "shasum": "" }, "require": { @@ -3009,16 +3009,17 @@ }, "require-dev": { "facile-it/paraunit": "^1.3.1 || ^2.7", - "infection/infection": "^0.31.0", - "justinrainbow/json-schema": "^6.5", - "keradus/cli-executor": "^2.2", + "infection/infection": "^0.32", + "justinrainbow/json-schema": "^6.6", + "keradus/cli-executor": "^2.3", "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.9", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", - "phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34", - "symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2 || ^8.0", - "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2 || ^8.0" + "phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.48", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.26 || ^7.4.0 || ^8.0", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -3033,7 +3034,7 @@ "PhpCsFixer\\": "src/" }, "exclude-from-classmap": [ - "src/Fixer/Internal/*" + "src/**/Internal/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3059,7 +3060,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.91.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.93.1" }, "funding": [ { @@ -3067,7 +3068,7 @@ "type": "github" } ], - "time": "2025-11-28T22:07:42+00:00" + "time": "2026-01-28T23:50:50+00:00" }, { "name": "humbug/box", @@ -3316,21 +3317,21 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.6.2", + "version": "6.6.4", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7" + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/3c25fe750c1599716ef26aa997f7c026cee8c4b7", - "reference": "3c25fe750c1599716ef26aa997f7c026cee8c4b7", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7", + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7", "shasum": "" }, "require": { "ext-json": "*", - "marc-mabe/php-enum": "^4.0", + "marc-mabe/php-enum": "^4.4", "php": "^7.2 || ^8.0" }, "require-dev": { @@ -3385,9 +3386,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4" }, - "time": "2025-11-28T15:24:03+00:00" + "time": "2025-12-19T15:01:32+00:00" }, { "name": "kelunik/certificate", @@ -3449,20 +3450,20 @@ }, { "name": "league/uri", - "version": "7.6.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "f625804987a0a9112d954f9209d91fec52182344" + "reference": "4436c6ec8d458e4244448b069cc572d088230b76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", - "reference": "f625804987a0a9112d954f9209d91fec52182344", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76", + "reference": "4436c6ec8d458e4244448b069cc572d088230b76", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.6", + "league/uri-interfaces": "^7.8", "php": "^8.1", "psr/http-factory": "^1" }, @@ -3476,11 +3477,11 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "ext-uri": "to use the PHP native URI class", - "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", - "league/uri-components": "Needed to easily manipulate URI objects components", - "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3535,7 +3536,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.6.0" + "source": "https://github.com/thephpleague/uri/tree/7.8.0" }, "funding": [ { @@ -3543,20 +3544,20 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2026-01-14T17:24:56+00:00" }, { "name": "league/uri-interfaces", - "version": "7.6.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", - "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4", "shasum": "" }, "require": { @@ -3569,7 +3570,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3619,7 +3620,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.6.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0" }, "funding": [ { @@ -3627,7 +3628,7 @@ "type": "github" } ], - "time": "2025-11-18T12:17:23+00:00" + "time": "2026-01-15T06:54:53+00:00" }, { "name": "marc-mabe/php-enum", @@ -3816,16 +3817,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -3868,9 +3869,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/composer-distributor", @@ -4259,16 +4260,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.5", + "version": "5.6.6", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "90614c73d3800e187615e2dd236ad0e2a01bf761" + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/90614c73d3800e187615e2dd236ad0e2a01bf761", - "reference": "90614c73d3800e187615e2dd236ad0e2a01bf761", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", "shasum": "" }, "require": { @@ -4278,7 +4279,7 @@ "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.7", "phpstan/phpdoc-parser": "^1.7|^2.0", - "webmozart/assert": "^1.9.1" + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { "mockery/mockery": "~1.3.5 || ~1.6.0", @@ -4317,9 +4318,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.5" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" }, - "time": "2025-11-27T19:50:05+00:00" + "time": "2025-12-22T21:13:58+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -4381,16 +4382,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.0", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", - "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", "shasum": "" }, "require": { @@ -4422,21 +4423,21 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" }, - "time": "2025-08-30T15:50:23+00:00" + "time": "2026-01-25T14:56:51+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.32", + "version": "2.1.38", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8", - "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", + "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -4477,7 +4478,7 @@ "type": "github" } ], - "time": "2025-09-30T10:16:31+00:00" + "time": "2026-01-30T17:12:46+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4802,16 +4803,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.58", + "version": "10.5.63", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca" + "reference": "33198268dad71e926626b618f3ec3966661e4d90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca", - "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", "shasum": "" }, "require": { @@ -4832,7 +4833,7 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.4", + "sebastian/comparator": "^5.0.5", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", "sebastian/exporter": "^5.1.4", @@ -4883,7 +4884,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.58" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" }, "funding": [ { @@ -4907,7 +4908,7 @@ "type": "tidelift" } ], - "time": "2025-09-28T12:04:46+00:00" + "time": "2026-01-27T05:48:37+00:00" }, { "name": "psr/event-dispatcher", @@ -5141,16 +5142,16 @@ }, { "name": "react/child-process", - "version": "v0.6.6", + "version": "v0.6.7", "source": { "type": "git", "url": "https://github.com/reactphp/child-process.git", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", "shasum": "" }, "require": { @@ -5204,7 +5205,7 @@ ], "support": { "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.6" + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" }, "funding": [ { @@ -5212,7 +5213,7 @@ "type": "open_collective" } ], - "time": "2025-01-01T16:37:48+00:00" + "time": "2025-12-23T15:25:20+00:00" }, { "name": "react/dns", @@ -5835,16 +5836,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.4", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e" + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e", - "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", "shasum": "" }, "require": { @@ -5900,7 +5901,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" }, "funding": [ { @@ -5920,7 +5921,7 @@ "type": "tidelift" } ], - "time": "2025-09-07T05:25:07+00:00" + "time": "2026-01-24T09:25:16+00:00" }, { "name": "sebastian/complexity", @@ -6732,16 +6733,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v8.0.0", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "573f95783a2ec6e38752979db139f09fec033f03" + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/573f95783a2ec6e38752979db139f09fec033f03", - "reference": "573f95783a2ec6e38752979db139f09fec033f03", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47", + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47", "shasum": "" }, "require": { @@ -6793,7 +6794,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4" }, "funding": [ { @@ -6813,7 +6814,7 @@ "type": "tidelift" } ], - "time": "2025-10-30T14:17:19+00:00" + "time": "2026-01-05T11:45:55+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6963,16 +6964,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", "shasum": "" }, "require": { @@ -7007,7 +7008,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.0" + "source": "https://github.com/symfony/finder/tree/v7.4.5" }, "funding": [ { @@ -7027,7 +7028,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T05:42:40+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/options-resolver", @@ -7332,16 +7333,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece" + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41fd6c4ae28c38b294b42af6db61446594a0dece", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", "shasum": "" }, "require": { @@ -7395,7 +7396,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" }, "funding": [ { @@ -7415,7 +7416,7 @@ "type": "tidelift" } ], - "time": "2025-10-27T20:36:44+00:00" + "time": "2026-01-01T22:13:48+00:00" }, { "name": "thecodingmachine/safe", diff --git a/phpstan.neon b/phpstan.neon index 45e512ba0..ce7cdb2d2 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,11 +11,13 @@ parameters: - '#Function Swoole\\Coroutine\\run not found.#' - '#Static call to instance method ZM\\Logger\\ConsoleColor#' - '#Constant GNU_ARCH not found#' + - + identifiers: + - 'if.alwaysFalse' dynamicConstantNames: - PHP_OS_FAMILY excludePaths: analyseAndScan: - - ./src/globals/ext-tests/swoole.php - - ./src/globals/ext-tests/swoole.phpt + - ./src/globals/ext-tests/ - ./src/globals/test-extensions.php - ./src/SPC/ diff --git a/src/Package/Target/php.php b/src/Package/Target/php.php index 8cd6e9407..32e8a13b1 100644 --- a/src/Package/Target/php.php +++ b/src/Package/Target/php.php @@ -241,7 +241,6 @@ private function makeStaticExtensionString(PackageInstaller $installer): string { $arg = []; foreach ($installer->getResolvedPackages() as $package) { - /** @var PhpExtensionPackage $package */ if ($package->getType() !== 'php-extension' || !$package instanceof PhpExtensionPackage) { continue; } diff --git a/src/StaticPHP/Artifact/ArtifactDownloader.php b/src/StaticPHP/Artifact/ArtifactDownloader.php index b4d79b769..b07c56617 100644 --- a/src/StaticPHP/Artifact/ArtifactDownloader.php +++ b/src/StaticPHP/Artifact/ArtifactDownloader.php @@ -312,7 +312,7 @@ public function download(bool $interactive = true): void FileSystem::createDir(DOWNLOAD_PATH); } logger()->info('Downloading' . implode(', ', array_map(fn ($x) => " '{$x->getName()}'", $this->artifacts)) . " with concurrency {$this->parallel} ..."); - // Download artifacts parallely + // Download artifacts parallelly if ($this->parallel > 1) { $this->downloadWithConcurrency(); } else { diff --git a/src/StaticPHP/Artifact/ArtifactExtractor.php b/src/StaticPHP/Artifact/ArtifactExtractor.php index 93b363823..217a6f54f 100644 --- a/src/StaticPHP/Artifact/ArtifactExtractor.php +++ b/src/StaticPHP/Artifact/ArtifactExtractor.php @@ -247,6 +247,7 @@ protected function extractBinary(Artifact $artifact): int $artifact->emitAfterBinaryExtract($target_path, $platform); logger()->debug("Emitted after-binary-extract hooks for [{$name}]"); + /* @phpstan-ignore-next-line */ if ($hash !== null && $cache_info['cache_type'] !== 'file') { FileSystem::writeFile("{$target_path}/.spc-hash", $hash); } diff --git a/src/StaticPHP/Registry/PackageLoader.php b/src/StaticPHP/Registry/PackageLoader.php index e95021273..424363eb3 100644 --- a/src/StaticPHP/Registry/PackageLoader.php +++ b/src/StaticPHP/Registry/PackageLoader.php @@ -169,7 +169,7 @@ public static function loadFromClass(mixed $class): void } } - $pkg = self::$packages[$attribute_instance->name]; + $pkg = self::$packages[$attribute_instance->name] ?? null; // Use the package instance if it's a Package subclass, otherwise create a new instance $instance_class = is_a($class_name, Package::class, true) ? $pkg : $refClass->newInstance(); @@ -184,7 +184,7 @@ public static function loadFromClass(mixed $class): void if (!in_array($package_type, $pkg_type_attr, true)) { throw new RegistryException("Package [{$attribute_instance->name}] type mismatch: config type is [{$package_type}], but attribute type is [" . implode('|', $pkg_type_attr) . '].'); } - if ($pkg !== null && !PackageConfig::isPackageExists($pkg->getName())) { + if ($pkg instanceof Package && !PackageConfig::isPackageExists($pkg->getName())) { throw new RegistryException("Package [{$pkg->getName()}] config not found for class {$class}"); } @@ -355,7 +355,7 @@ private static function addBeforeStage(\ReflectionMethod $method, ?Package $pkg, $stage = $method_instance->stage; $stage = match (true) { is_string($stage) => $stage, - is_array($stage) && count($stage) === 2 => $stage[1], + count($stage) === 2 => $stage[1], default => throw new RegistryException('Invalid stage definition in BeforeStage attribute.'), }; if ($method_instance->package_name === '' && $pkg === null) { diff --git a/src/StaticPHP/Util/LicenseDumper.php b/src/StaticPHP/Util/LicenseDumper.php index 54c6266bb..f439ec523 100644 --- a/src/StaticPHP/Util/LicenseDumper.php +++ b/src/StaticPHP/Util/LicenseDumper.php @@ -75,7 +75,7 @@ public function dump(string $target_dir): bool * * @param Artifact $artifact Artifact instance * @param string $target_dir Target directory - * @param array &$license_summary Summary data to populate + * @param array &$license_summary Summary data to populate * @return bool True if dumped * @throws SPCInternalException */ diff --git a/src/StaticPHP/Util/System/UnixUtil.php b/src/StaticPHP/Util/System/UnixUtil.php index 8dd606188..aca50d9e4 100644 --- a/src/StaticPHP/Util/System/UnixUtil.php +++ b/src/StaticPHP/Util/System/UnixUtil.php @@ -44,7 +44,7 @@ public static function exportDynamicSymbols(string $lib_file): void continue; } $name = preg_replace('/@.*$/', '', $name); - if ($name !== '' && $name !== false) { + if (!empty($name)) { $defined[] = $name; } } From 7041e060f2fb028f028a6b0c85d0025cd5aa21c6 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 13:02:43 +0800 Subject: [PATCH 35/99] Add curl --- config/pkg/lib/curl.yml | 33 ++++++++++++++++++ src/Package/Library/curl.php | 61 +++++++++++++++++++++++++++++++++ src/StaticPHP/Doctor/Doctor.php | 3 +- 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 config/pkg/lib/curl.yml create mode 100644 src/Package/Library/curl.php diff --git a/config/pkg/lib/curl.yml b/config/pkg/lib/curl.yml new file mode 100644 index 000000000..38d632ce8 --- /dev/null +++ b/config/pkg/lib/curl.yml @@ -0,0 +1,33 @@ +curl: + type: library + artifact: + source: + type: ghrel + repo: curl/curl + match: 'curl.+\.tar\.xz' + prefer-stable: true + metadata: + license-files: [COPYING] + license: curl + static-libs@unix: + - libcurl.a + headers: + - curl + depends@unix: + - openssl + - zlib + suggests@unix: + - libssh2 + - brotli + - nghttp2 + - nghttp3 + - ngtcp2 + - zstd + - libcares + - ldap + - idn2 + - krb5 + frameworks: + - CoreFoundation + - CoreServices + - SystemConfiguration diff --git a/src/Package/Library/curl.php b/src/Package/Library/curl.php new file mode 100644 index 000000000..a2856ad4b --- /dev/null +++ b/src/Package/Library/curl.php @@ -0,0 +1,61 @@ +cd($lib->getSourceDir())->exec('sed -i.save s@\${CMAKE_C_IMPLICIT_LINK_LIBRARIES}@@ ./CMakeLists.txt'); + if (SystemTarget::getTargetOS() === 'Darwin') { + FileSystem::replaceFileRegex("{$lib->getSourceDir()}/curl/CMakeLists.txt", '/NOT COREFOUNDATION_FRAMEWORK/m', 'FALSE'); + FileSystem::replaceFileRegex("{$lib->getSourceDir()}/curl/CMakeLists.txt", '/NOT SYSTEMCONFIGURATION_FRAMEWORK/m', 'FALSE'); + FileSystem::replaceFileRegex("{$lib->getSourceDir()}/curl/CMakeLists.txt", '/NOT CORESERVICES_FRAMEWORK/m', 'FALSE'); + } + return true; + } + + #[BuildFor('Linux')] + #[BuildFor('Darwin')] + public function build(LibraryPackage $lib): void + { + UnixCMakeExecutor::create($lib) + ->optionalPackage('openssl', '-DCURL_USE_OPENSSL=ON -DCURL_CA_BUNDLE=OFF -DCURL_CA_PATH=OFF -DCURL_CA_FALLBACK=ON', '-DCURL_USE_OPENSSL=OFF -DCURL_ENABLE_SSL=OFF') + ->optionalPackage('brotli', ...cmake_boolean_args('CURL_BROTLI')) + ->optionalPackage('libssh2', ...cmake_boolean_args('CURL_USE_LIBSSH2')) + ->optionalPackage('nghttp2', ...cmake_boolean_args('USE_NGHTTP2')) + ->optionalPackage('nghttp3', ...cmake_boolean_args('USE_NGHTTP3')) + ->optionalPackage('ngtcp2', ...cmake_boolean_args('USE_NGTCP2')) + ->optionalPackage('ldap', ...cmake_boolean_args('CURL_DISABLE_LDAP', true)) + ->optionalPackage('zstd', ...cmake_boolean_args('CURL_ZSTD')) + ->optionalPackage('idn2', ...cmake_boolean_args('USE_LIBIDN2')) + ->optionalPackage('psl', ...cmake_boolean_args('CURL_USE_LIBPSL')) + ->optionalPackage('krb5', ...cmake_boolean_args('CURL_USE_GSSAPI')) + ->optionalPackage('idn2', ...cmake_boolean_args('CURL_USE_IDN2')) + ->optionalPackage('libcares', '-DENABLE_ARES=ON') + ->addConfigureArgs( + '-DBUILD_CURL_EXE=OFF', + '-DBUILD_LIBCURL_DOCS=OFF', + ) + ->build(); + + // patch pkgconf + $lib->patchPkgconfPrefix(['libcurl.pc']); + shell()->cd("{$lib->getLibDir()}/cmake/CURL/") + ->exec("sed -ie 's|\"/lib/libcurl.a\"|\"{$lib->getLibDir()}/libcurl.a\"|g' CURLTargets-release.cmake"); + } +} diff --git a/src/StaticPHP/Doctor/Doctor.php b/src/StaticPHP/Doctor/Doctor.php index d86e42ac2..36db37ae8 100644 --- a/src/StaticPHP/Doctor/Doctor.php +++ b/src/StaticPHP/Doctor/Doctor.php @@ -146,7 +146,8 @@ private function getValidCheckList(): iterable foreach (DoctorLoader::getDoctorItems() as [$item, $optional]) { /* @var CheckItem $item */ // optional check - if ($optional !== null && !call_user_func($optional)) { + /* @phpstan-ignore-next-line */ + if (is_callable($optional) && !call_user_func($optional)) { continue; // skip this when the optional check is false } // limit_os check From 6fdbf629dc7dac1ba9501ca8020ecd467fc63efc Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 14:00:21 +0800 Subject: [PATCH 36/99] Fix selective artifact installation detect --- src/StaticPHP/Artifact/Artifact.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/StaticPHP/Artifact/Artifact.php b/src/StaticPHP/Artifact/Artifact.php index 67876d900..b3e27371f 100644 --- a/src/StaticPHP/Artifact/Artifact.php +++ b/src/StaticPHP/Artifact/Artifact.php @@ -164,7 +164,14 @@ public function isBinaryExtracted(?string $target_os = null, bool $compare_hash // For selective mode, cannot reliably check extraction status if ($mode === 'selective') { - return false; + // check files existence + foreach ($extract_config['files'] as $target_file) { + $target_file = FileSystem::replacePathVariable($target_file); + if (!file_exists($target_file)) { + return false; + } + } + return true; } // For standalone mode, check directory or file and hash From 38f742156d614ad427bb4d9c099d2d90e675b6c8 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 14:01:41 +0800 Subject: [PATCH 37/99] Use zig toolchain by default, lint files --- config/env.ini | 24 +- config/pkg.ext.json | 534 +++++++++--------- config/pkg.target.json | 67 +-- config/pkg/lib/curl.yml | 10 +- config/pkg/lib/gettext.yml | 6 +- config/pkg/lib/idn2.yml | 8 +- config/pkg/lib/krb5.yml | 6 +- config/pkg/lib/libxml2.yml | 6 +- config/pkg/lib/nghttp2.yml | 10 +- config/pkg/lib/ngtcp2.yml | 6 +- config/pkg/target/pkg-config.yml | 9 + config/pkg/target/zig.yml | 4 + spc.registry.json | 1 + .../Command/Dev/LintConfigCommand.php | 2 +- src/StaticPHP/Doctor/Item/LinuxMuslCheck.php | 10 +- src/StaticPHP/Package/PackageInstaller.php | 2 +- .../Toolchain/ClangNativeToolchain.php | 8 +- .../Toolchain/GccNativeToolchain.php | 8 +- src/StaticPHP/Toolchain/MuslToolchain.php | 10 +- src/StaticPHP/Util/PkgConfigUtil.php | 7 +- 20 files changed, 373 insertions(+), 365 deletions(-) create mode 100644 config/pkg/target/pkg-config.yml create mode 100644 config/pkg/target/zig.yml diff --git a/config/env.ini b/config/env.ini index 718e556ff..c77f6bcd0 100644 --- a/config/env.ini +++ b/config/env.ini @@ -83,7 +83,7 @@ SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime ; - gnu-native: used for general linux distros, can build gnu target for the installed glibc version only. ; option to specify the target, superceded by SPC_TARGET if set -SPC_LIBC=musl +; SPC_LIBC=musl ; uncomment to link libc dynamically on musl ; SPC_MUSL_DYNAMIC=true @@ -94,13 +94,13 @@ SPC_LIBC=musl ; `native-native` - links against system libc dynamically ; `native-native-musl` - links against musl libc statically ; `native-native-musl -dynamic` - links against musl libc dynamically -; SPC_TARGET= +SPC_TARGET=native-native-musl -; compiler environments -CC=${SPC_LINUX_DEFAULT_CC} -CXX=${SPC_LINUX_DEFAULT_CXX} -AR=${SPC_LINUX_DEFAULT_AR} -LD=${SPC_LINUX_DEFAULT_LD} +; compiler environments (default value is defined by selected toolchain) +CC=${SPC_DEFAULT_CC} +CXX=${SPC_DEFAULT_CXX} +AR=${SPC_DEFAULT_AR} +LD=${SPC_DEFAULT_LD} ; default compiler flags, used in CMake toolchain file, openssl and pkg-config build SPC_DEFAULT_C_FLAGS="-fPIC -Os" SPC_DEFAULT_CXX_FLAGS="-fPIC -Os" @@ -132,11 +132,11 @@ OPENSSLDIR="" ; build target: macho or macho (possibly we could support macho-universal in the future) ; Currently we do not support universal and cross-compilation for macOS. SPC_TARGET=native-macos -; compiler environments -CC=clang -CXX=clang++ -AR=ar -LD=ld +; compiler environments (default value is defined by selected toolchain) +CC=${SPC_DEFAULT_CC} +CXX=${SPC_DEFAULT_CXX} +AR=${SPC_DEFAULT_AR} +LD=${SPC_DEFAULT_LD} ; default compiler flags, used in CMake toolchain file, openssl and pkg-config build SPC_DEFAULT_C_FLAGS="--target=${MAC_ARCH}-apple-darwin -Os" SPC_DEFAULT_CXX_FLAGS="--target=${MAC_ARCH}-apple-darwin -Os" diff --git a/config/pkg.ext.json b/config/pkg.ext.json index b1e819eb3..d1ca0b3f0 100644 --- a/config/pkg.ext.json +++ b/config/pkg.ext.json @@ -1,5 +1,6 @@ { "ext-amqp": { + "type": "php-extension", "artifact": "amqp", "depends": [ "librabbitmq" @@ -16,29 +17,29 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "type": "php-extension" + } }, "ext-apcu": { + "type": "php-extension", "artifact": "apcu", "license": { "type": "file", "path": "LICENSE" - }, - "type": "php-extension" + } }, "ext-ast": { + "type": "php-extension", "artifact": "ast", "license": { "type": "file", "path": "LICENSE" - }, - "type": "php-extension" + } }, "ext-bcmath": { "type": "php-extension" }, "ext-brotli": { + "type": "php-extension", "artifact": "ext-brotli", "depends": [ "brotli" @@ -49,18 +50,17 @@ }, "php-extension": { "arg-type": "enable" - }, - "type": "php-extension" + } }, "ext-bz2": { + "type": "php-extension", "depends": [ "bzip2" ], "php-extension": { "arg-type@windows": "with", "arg-type": "with-path" - }, - "type": "php-extension" + } }, "ext-calendar": { "type": "php-extension" @@ -69,6 +69,7 @@ "type": "php-extension" }, "ext-curl": { + "type": "php-extension", "depends": [ "curl" ], @@ -79,19 +80,19 @@ "php-extension": { "arg-type": "with", "notes": true - }, - "type": "php-extension" + } }, "ext-dba": { - "php-extension": { - "arg-type": "custom" - }, + "type": "php-extension", "suggests": [ "qdbm" ], - "type": "php-extension" + "php-extension": { + "arg-type": "custom" + } }, "ext-dio": { + "type": "php-extension", "artifact": "dio", "license": { "type": "file", @@ -101,10 +102,10 @@ "support": { "BSD": "wip" } - }, - "type": "php-extension" + } }, "ext-dom": { + "type": "php-extension", "depends": [ "libxml2", "zlib" @@ -118,18 +119,18 @@ }, "arg-type": "custom", "arg-type@windows": "with" - }, - "type": "php-extension" + } }, "ext-ds": { + "type": "php-extension", "artifact": "ext-ds", "license": { "type": "file", "path": "LICENSE" - }, - "type": "php-extension" + } }, "ext-enchant": { + "type": "php-extension", "php-extension": { "support": { "Windows": "wip", @@ -137,10 +138,10 @@ "Darwin": "wip", "Linux": "wip" } - }, - "type": "php-extension" + } }, "ext-ev": { + "type": "php-extension", "artifact": "ev", "depends": [ "ext-sockets" @@ -151,15 +152,18 @@ }, "php-extension": { "arg-type@windows": "with" - }, - "type": "php-extension" + } }, "ext-event": { + "type": "php-extension", "artifact": "ext-event", "depends": [ "libevent", "ext-openssl" ], + "suggests": [ + "ext-sockets" + ], "license": { "type": "file", "path": "LICENSE" @@ -171,16 +175,13 @@ }, "arg-type": "custom", "notes": true - }, - "suggests": [ - "ext-sockets" - ], - "type": "php-extension" + } }, "ext-exif": { "type": "php-extension" }, "ext-ffi": { + "type": "php-extension", "depends": [ "libffi" ], @@ -194,8 +195,7 @@ }, "arg-type": "custom", "notes": true - }, - "type": "php-extension" + } }, "ext-fileinfo": { "type": "php-extension" @@ -204,17 +204,24 @@ "type": "php-extension" }, "ext-ftp": { + "type": "php-extension", "suggests": [ "openssl" - ], - "type": "php-extension" + ] }, "ext-gd": { + "type": "php-extension", "depends": [ "zlib", "libpng", "ext-zlib" ], + "suggests": [ + "libavif", + "libwebp", + "libjpeg", + "freetype" + ], "php-extension": { "support": { "BSD": "wip" @@ -222,16 +229,10 @@ "arg-type": "custom", "arg-type@windows": "with", "notes": true - }, - "suggests": [ - "libavif", - "libwebp", - "libjpeg", - "freetype" - ], - "type": "php-extension" + } }, "ext-gettext": { + "type": "php-extension", "depends": [ "gettext" ], @@ -241,10 +242,10 @@ "BSD": "wip" }, "arg-type": "with-path" - }, - "type": "php-extension" + } }, "ext-glfw": { + "type": "php-extension", "artifact": "ext-glfw", "depends": [ "glfw" @@ -262,10 +263,10 @@ }, "arg-type": "custom", "notes": true - }, - "type": "php-extension" + } }, "ext-gmp": { + "type": "php-extension", "depends": [ "gmp" ], @@ -275,10 +276,10 @@ "BSD": "wip" }, "arg-type": "with-path" - }, - "type": "php-extension" + } }, "ext-gmssl": { + "type": "php-extension", "artifact": "ext-gmssl", "depends": [ "gmssl" @@ -291,10 +292,10 @@ "support": { "BSD": "wip" } - }, - "type": "php-extension" + } }, "ext-grpc": { + "type": "php-extension", "artifact": "grpc", "depends": [ "grpc" @@ -310,10 +311,10 @@ "BSD": "wip" }, "arg-type": "enable-path" - }, - "type": "php-extension" + } }, "ext-iconv": { + "type": "php-extension", "depends": [ "libiconv" ], @@ -326,11 +327,15 @@ }, "arg-type": "with-path", "arg-type@windows": "with" - }, - "type": "php-extension" + } }, "ext-igbinary": { + "type": "php-extension", "artifact": "igbinary", + "suggests": [ + "ext-session", + "ext-apcu" + ], "license": { "type": "file", "path": "COPYING" @@ -339,14 +344,10 @@ "support": { "BSD": "wip" } - }, - "suggests": [ - "ext-session", - "ext-apcu" - ], - "type": "php-extension" + } }, "ext-imagick": { + "type": "php-extension", "artifact": "ext-imagick", "depends": [ "imagemagick" @@ -362,14 +363,17 @@ }, "arg-type": "custom", "notes": true - }, - "type": "php-extension" + } }, "ext-imap": { + "type": "php-extension", "artifact": "ext-imap", "depends": [ "imap" ], + "suggests": [ + "ext-openssl" + ], "license": { "type": "file", "path": [ @@ -383,13 +387,10 @@ }, "arg-type": "custom", "notes": true - }, - "suggests": [ - "ext-openssl" - ], - "type": "php-extension" + } }, "ext-inotify": { + "type": "php-extension", "artifact": "inotify", "license": { "type": "file", @@ -401,10 +402,10 @@ "BSD": "wip", "Darwin": "no" } - }, - "type": "php-extension" + } }, "ext-intl": { + "type": "php-extension", "depends": [ "icu" ], @@ -415,28 +416,28 @@ "support": { "BSD": "wip" } - }, - "type": "php-extension" + } }, "ext-ldap": { + "type": "php-extension", "depends": [ "ldap" ], + "suggests": [ + "gmp", + "libsodium", + "ext-openssl" + ], "php-extension": { "support": { "Windows": "wip", "BSD": "wip" }, "arg-type": "with-path" - }, - "suggests": [ - "gmp", - "libsodium", - "ext-openssl" - ], - "type": "php-extension" + } }, "ext-libxml": { + "type": "php-extension", "depends": [ "ext-xml" ], @@ -448,10 +449,10 @@ "build-shared": false, "build-static": true, "build-with-php": true - }, - "type": "php-extension" + } }, "ext-lz4": { + "type": "php-extension", "artifact": "ext-lz4", "depends": [ "liblz4" @@ -468,10 +469,10 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "type": "php-extension" + } }, "ext-mbregex": { + "type": "php-extension", "depends": [ "onig", "ext-mbstring" @@ -480,16 +481,16 @@ "arg-type": "custom", "build-shared": false, "build-static": true - }, - "type": "php-extension" + } }, "ext-mbstring": { + "type": "php-extension", "php-extension": { "arg-type": "custom" - }, - "type": "php-extension" + } }, "ext-mcrypt": { + "type": "php-extension", "php-extension": { "support": { "Windows": "no", @@ -498,10 +499,10 @@ "Linux": "no" }, "notes": true - }, - "type": "php-extension" + } }, "ext-memcache": { + "type": "php-extension", "artifact": "ext-memcache", "depends": [ "ext-zlib", @@ -518,10 +519,10 @@ }, "arg-type": "custom", "build-with-php": true - }, - "type": "php-extension" + } }, "ext-memcached": { + "type": "php-extension", "artifact": "memcached", "depends": [ "libmemcached", @@ -529,6 +530,12 @@ "ext-session", "ext-zlib" ], + "suggests": [ + "zstd", + "ext-igbinary", + "ext-msgpack", + "ext-session" + ], "lang": "cpp", "license": { "type": "file", @@ -540,17 +547,17 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "suggests": [ - "zstd", - "ext-igbinary", - "ext-msgpack", - "ext-session" - ], - "type": "php-extension" + } }, "ext-mongodb": { + "type": "php-extension", "artifact": "mongodb", + "suggests": [ + "icu", + "openssl", + "zstd", + "zlib" + ], "frameworks": [ "CoreFoundation", "Security" @@ -565,16 +572,10 @@ "Windows": "wip" }, "arg-type": "custom" - }, - "suggests": [ - "icu", - "openssl", - "zstd", - "zlib" - ], - "type": "php-extension" + } }, "ext-msgpack": { + "type": "php-extension", "artifact": "msgpack", "depends": [ "ext-session" @@ -589,30 +590,30 @@ }, "arg-type@windows": "enable", "arg-type": "with" - }, - "type": "php-extension" + } }, "ext-mysqli": { + "type": "php-extension", "depends": [ "ext-mysqlnd" ], "php-extension": { "arg-type": "with", "build-with-php": true - }, - "type": "php-extension" + } }, "ext-mysqlnd": { + "type": "php-extension", "depends": [ "zlib" ], "php-extension": { "arg-type@windows": "with", "build-with-php": true - }, - "type": "php-extension" + } }, "ext-oci8": { + "type": "php-extension", "php-extension": { "support": { "Windows": "wip", @@ -621,10 +622,10 @@ "Linux": "no" }, "notes": true - }, - "type": "php-extension" + } }, "ext-odbc": { + "type": "php-extension", "depends": [ "unixodbc" ], @@ -634,18 +635,18 @@ "Windows": "wip" }, "arg-type": "custom" - }, - "type": "php-extension" + } }, "ext-opcache": { + "type": "php-extension", "php-extension": { "arg-type@windows": "enable", "arg-type": "custom", "zend-extension": true - }, - "type": "php-extension" + } }, "ext-openssl": { + "type": "php-extension", "depends": [ "openssl", "zlib", @@ -656,10 +657,10 @@ "arg-type@windows": "with", "build-with-php": true, "notes": true - }, - "type": "php-extension" + } }, "ext-opentelemetry": { + "type": "php-extension", "artifact": "opentelemetry", "license": { "type": "file", @@ -669,10 +670,10 @@ "support": { "BSD": "wip" } - }, - "type": "php-extension" + } }, "ext-parallel": { + "type": "php-extension", "artifact": "parallel", "depends@windows": [ "pthreads4w" @@ -687,10 +688,10 @@ }, "arg-type@windows": "with", "notes": true - }, - "type": "php-extension" + } }, "ext-password-argon2": { + "type": "php-extension", "depends": [ "libargon2", "openssl" @@ -702,31 +703,31 @@ }, "arg-type": "custom", "notes": true - }, - "type": "php-extension" + } }, "ext-pcntl": { + "type": "php-extension", "php-extension": { "support": { "Windows": "no" } - }, - "type": "php-extension" + } }, "ext-pdo": { "type": "php-extension" }, "ext-pdo_mysql": { + "type": "php-extension", "depends": [ "ext-pdo", "ext-mysqlnd" ], "php-extension": { "arg-type": "with" - }, - "type": "php-extension" + } }, "ext-pdo_odbc": { + "type": "php-extension", "depends": [ "unixodbc", "ext-pdo", @@ -737,10 +738,10 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "type": "php-extension" + } }, "ext-pdo_pgsql": { + "type": "php-extension", "depends": [ "postgresql", "ext-pdo", @@ -755,10 +756,10 @@ }, "arg-type": "with-path", "arg-type@windows": "custom" - }, - "type": "php-extension" + } }, "ext-pdo_sqlite": { + "type": "php-extension", "depends": [ "sqlite", "ext-pdo", @@ -769,10 +770,10 @@ "BSD": "wip" }, "arg-type": "with" - }, - "type": "php-extension" + } }, "ext-pdo_sqlsrv": { + "type": "php-extension", "artifact": "pdo_sqlsrv", "depends": [ "ext-pdo", @@ -787,10 +788,10 @@ "BSD": "wip" }, "arg-type": "with" - }, - "type": "php-extension" + } }, "ext-pgsql": { + "type": "php-extension", "depends": [ "postgresql" ], @@ -803,24 +804,24 @@ }, "arg-type": "custom", "notes": true - }, - "type": "php-extension" + } }, "ext-phar": { + "type": "php-extension", "depends": [ "ext-zlib" - ], - "type": "php-extension" + ] }, "ext-posix": { + "type": "php-extension", "php-extension": { "support": { "Windows": "no" } - }, - "type": "php-extension" + } }, "ext-protobuf": { + "type": "php-extension", "artifact": "protobuf", "license": { "type": "file", @@ -831,10 +832,10 @@ "Windows": "wip", "BSD": "wip" } - }, - "type": "php-extension" + } }, "ext-rar": { + "type": "php-extension", "artifact": "rar", "lang": "cpp", "license": { @@ -847,10 +848,10 @@ "Darwin": "partial" }, "notes": true - }, - "type": "php-extension" + } }, "ext-rdkafka": { + "type": "php-extension", "artifact": "ext-rdkafka", "depends": [ "librdkafka" @@ -866,10 +867,10 @@ "Windows": "wip" }, "arg-type": "custom" - }, - "type": "php-extension" + } }, "ext-readline": { + "type": "php-extension", "depends": [ "libedit" ], @@ -881,11 +882,18 @@ "arg-type": "with-path", "build-shared": false, "build-static": true - }, - "type": "php-extension" + } }, "ext-redis": { + "type": "php-extension", "artifact": "redis", + "suggests": [ + "zstd", + "liblz4", + "ext-session", + "ext-igbinary", + "ext-msgpack" + ], "license": { "type": "file", "path": [ @@ -898,38 +906,31 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "suggests": [ - "zstd", - "liblz4", - "ext-session", - "ext-igbinary", - "ext-msgpack" - ], - "type": "php-extension" + } }, "ext-session": { + "type": "php-extension", "php-extension": { "build-with-php": true - }, - "type": "php-extension" + } }, "ext-shmop": { + "type": "php-extension", "php-extension": { "build-with-php": true - }, - "type": "php-extension" + } }, "ext-simdjson": { + "type": "php-extension", "artifact": "ext-simdjson", "lang": "cpp", "license": { "type": "file", "path": "LICENSE" - }, - "type": "php-extension" + } }, "ext-simplexml": { + "type": "php-extension", "depends": [ "libxml2" ], @@ -942,14 +943,17 @@ }, "arg-type": "custom", "build-with-php": true - }, - "type": "php-extension" + } }, "ext-snappy": { + "type": "php-extension", "artifact": "ext-snappy", "depends": [ "snappy" ], + "suggests": [ + "ext-apcu" + ], "lang": "cpp", "license": { "type": "file", @@ -961,13 +965,10 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "suggests": [ - "ext-apcu" - ], - "type": "php-extension" + } }, "ext-snmp": { + "type": "php-extension", "depends": [ "net-snmp" ], @@ -978,10 +979,10 @@ }, "arg-type@windows": "with", "arg-type": "with" - }, - "type": "php-extension" + } }, "ext-soap": { + "type": "php-extension", "depends": [ "ext-libxml", "ext-session" @@ -991,13 +992,13 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "type": "php-extension" + } }, "ext-sockets": { "type": "php-extension" }, "ext-sodium": { + "type": "php-extension", "depends": [ "libsodium" ], @@ -1006,10 +1007,10 @@ "BSD": "wip" }, "arg-type": "with" - }, - "type": "php-extension" + } }, "ext-spx": { + "type": "php-extension", "artifact": "spx", "depends": [ "zlib" @@ -1025,10 +1026,10 @@ }, "arg-type": "custom", "notes": true - }, - "type": "php-extension" + } }, "ext-sqlite3": { + "type": "php-extension", "depends": [ "sqlite" ], @@ -1039,10 +1040,10 @@ "arg-type": "with-path", "arg-type@windows": "with", "build-with-php": true - }, - "type": "php-extension" + } }, "ext-sqlsrv": { + "type": "php-extension", "artifact": "sqlsrv", "depends": [ "unixodbc" @@ -1059,10 +1060,10 @@ "support": { "BSD": "wip" } - }, - "type": "php-extension" + } }, "ext-ssh2": { + "type": "php-extension", "artifact": "ext-ssh2", "depends": [ "libssh2", @@ -1079,10 +1080,10 @@ }, "arg-type": "with-path", "arg-type@windows": "with" - }, - "type": "php-extension" + } }, "ext-swoole": { + "type": "php-extension", "artifact": "swoole", "depends": [ "libcares", @@ -1092,19 +1093,6 @@ "ext-openssl", "ext-curl" ], - "lang": "cpp", - "license": { - "type": "file", - "path": "LICENSE" - }, - "php-extension": { - "support": { - "Windows": "no", - "BSD": "wip" - }, - "arg-type": "custom", - "notes": true - }, "suggests": [ "zstd", "ext-sockets", @@ -1117,15 +1105,31 @@ "zstd", "liburing" ], - "type": "php-extension" + "lang": "cpp", + "license": { + "type": "file", + "path": "LICENSE" + }, + "php-extension": { + "support": { + "Windows": "no", + "BSD": "wip" + }, + "arg-type": "custom", + "notes": true + } }, "ext-swoole-hook-mysql": { + "type": "php-extension", "depends": [ "ext-mysqlnd", "ext-pdo", "ext-pdo_mysql", "ext-swoole" ], + "suggests": [ + "ext-mysqli" + ], "php-extension": { "support": { "Windows": "no", @@ -1133,13 +1137,10 @@ }, "arg-type": "none", "notes": true - }, - "suggests": [ - "ext-mysqli" - ], - "type": "php-extension" + } }, "ext-swoole-hook-odbc": { + "type": "php-extension", "depends": [ "unixodbc", "ext-pdo", @@ -1152,10 +1153,10 @@ }, "arg-type": "none", "notes": true - }, - "type": "php-extension" + } }, "ext-swoole-hook-pgsql": { + "type": "php-extension", "depends": [ "ext-pgsql", "ext-pdo", @@ -1169,10 +1170,10 @@ }, "arg-type": "none", "notes": true - }, - "type": "php-extension" + } }, "ext-swoole-hook-sqlite": { + "type": "php-extension", "depends": [ "ext-sqlite3", "ext-pdo", @@ -1185,11 +1186,17 @@ }, "arg-type": "none", "notes": true - }, - "type": "php-extension" + } }, "ext-swow": { + "type": "php-extension", "artifact": "swow", + "suggests": [ + "openssl", + "curl", + "ext-openssl", + "ext-curl" + ], "license": { "type": "file", "path": "LICENSE" @@ -1200,42 +1207,36 @@ }, "arg-type": "custom", "notes": true - }, - "suggests": [ - "openssl", - "curl", - "ext-openssl", - "ext-curl" - ], - "type": "php-extension" + } }, "ext-sysvmsg": { + "type": "php-extension", "php-extension": { "support": { "Windows": "no", "BSD": "wip" } - }, - "type": "php-extension" + } }, "ext-sysvsem": { + "type": "php-extension", "php-extension": { "support": { "Windows": "no", "BSD": "wip" } - }, - "type": "php-extension" + } }, "ext-sysvshm": { + "type": "php-extension", "php-extension": { "support": { "BSD": "wip" } - }, - "type": "php-extension" + } }, "ext-tidy": { + "type": "php-extension", "depends": [ "tidy" ], @@ -1245,16 +1246,16 @@ "BSD": "wip" }, "arg-type": "with-path" - }, - "type": "php-extension" + } }, "ext-tokenizer": { + "type": "php-extension", "php-extension": { "build-with-php": true - }, - "type": "php-extension" + } }, "ext-trader": { + "type": "php-extension", "artifact": "ext-trader", "license": { "type": "file", @@ -1265,10 +1266,10 @@ "BSD": "wip", "Windows": "wip" } - }, - "type": "php-extension" + } }, "ext-uuid": { + "type": "php-extension", "artifact": "ext-uuid", "depends": [ "libuuid" @@ -1283,10 +1284,10 @@ "BSD": "wip" }, "arg-type": "with-path" - }, - "type": "php-extension" + } }, "ext-uv": { + "type": "php-extension", "artifact": "ext-uv", "depends": [ "libuv", @@ -1302,10 +1303,10 @@ "BSD": "wip" }, "arg-type": "with-path" - }, - "type": "php-extension" + } }, "ext-xdebug": { + "type": "php-extension", "artifact": "xdebug", "license": { "type": "file", @@ -1322,10 +1323,10 @@ "build-static": false, "notes": true, "zend-extension": true - }, - "type": "php-extension" + } }, "ext-xhprof": { + "type": "php-extension", "artifact": "xhprof", "depends": [ "ext-ctype" @@ -1341,15 +1342,18 @@ }, "build-with-php": true, "notes": true - }, - "type": "php-extension" + } }, "ext-xlswriter": { + "type": "php-extension", "artifact": "xlswriter", "depends": [ "ext-zlib", "ext-zip" ], + "suggests": [ + "openssl" + ], "license": { "type": "file", "path": "LICENSE" @@ -1359,13 +1363,10 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "suggests": [ - "openssl" - ], - "type": "php-extension" + } }, "ext-xml": { + "type": "php-extension", "depends": [ "libxml2" ], @@ -1380,10 +1381,10 @@ "arg-type@windows": "with", "build-with-php": true, "notes": true - }, - "type": "php-extension" + } }, "ext-xmlreader": { + "type": "php-extension", "depends": [ "libxml2" ], @@ -1397,10 +1398,10 @@ }, "arg-type": "custom", "build-with-php": true - }, - "type": "php-extension" + } }, "ext-xmlwriter": { + "type": "php-extension", "depends": [ "libxml2" ], @@ -1413,10 +1414,10 @@ }, "arg-type": "custom", "build-with-php": true - }, - "type": "php-extension" + } }, "ext-xsl": { + "type": "php-extension", "depends": [ "libxslt", "ext-xml", @@ -1428,10 +1429,10 @@ "BSD": "wip" }, "arg-type": "with-path" - }, - "type": "php-extension" + } }, "ext-xz": { + "type": "php-extension", "artifact": "ext-xz", "depends": [ "xz" @@ -1442,10 +1443,10 @@ }, "php-extension": { "arg-type": "with" - }, - "type": "php-extension" + } }, "ext-yac": { + "type": "php-extension", "artifact": "yac", "depends": [ "fastlz", @@ -1460,10 +1461,10 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "type": "php-extension" + } }, "ext-yaml": { + "type": "php-extension", "artifact": "yaml", "depends": [ "libyaml" @@ -1478,10 +1479,10 @@ }, "arg-type@windows": "with", "arg-type": "with-path" - }, - "type": "php-extension" + } }, "ext-zip": { + "type": "php-extension", "artifact": "ext-zip", "depends": [ "libzip" @@ -1504,10 +1505,10 @@ }, "arg-type": "custom", "arg-type@windows": "enable" - }, - "type": "php-extension" + } }, "ext-zlib": { + "type": "php-extension", "depends": [ "zlib" ], @@ -1517,10 +1518,10 @@ "build-shared": false, "build-static": true, "build-with-php": true - }, - "type": "php-extension" + } }, "ext-zstd": { + "type": "php-extension", "artifact": "ext-zstd", "depends": [ "zstd" @@ -1535,7 +1536,6 @@ "BSD": "wip" }, "arg-type": "custom" - }, - "type": "php-extension" + } } } diff --git a/config/pkg.target.json b/config/pkg.target.json index 8e04df905..a5c20b78c 100644 --- a/config/pkg.target.json +++ b/config/pkg.target.json @@ -1,5 +1,6 @@ { "frankenphp": { + "type": "virtual-target", "artifact": "frankenphp", "depends": [ "php-embed", @@ -9,90 +10,78 @@ "php-embed", "go-xcaddy", "libxml2" - ], - "type": "virtual-target" + ] }, "go-xcaddy": { + "type": "target", "artifact": "go-xcaddy", "static-bins": [ "xcaddy" - ], - "type": "target" + ] }, "musl-toolchain": { - "artifact": "musl-toolchain", - "type": "target" + "type": "target", + "artifact": "musl-toolchain" }, "nasm": { - "artifact": "nasm", - "type": "target" + "type": "target", + "artifact": "nasm" }, "php": { + "type": "target", "artifact": "php-src", "depends@macos": [ "libxml2" - ], - "type": "target" + ] }, "php-cgi": { + "type": "virtual-target", "depends": [ "php" - ], - "type": "virtual-target" + ] }, "php-cli": { + "type": "virtual-target", "depends": [ "php" - ], - "type": "virtual-target" + ] }, "php-embed": { + "type": "virtual-target", "depends": [ "php" - ], - "type": "virtual-target" + ] }, "php-fpm": { + "type": "virtual-target", "depends": [ "php" - ], - "type": "virtual-target" + ] }, "php-micro": { + "type": "virtual-target", "artifact": "micro", "depends": [ "php" - ], - "type": "virtual-target" + ] }, "php-sdk-binary-tools": { - "artifact": "php-sdk-binary-tools", - "type": "target" - }, - "pkg-config": { - "artifact": "pkg-config", - "static-bins": [ - "pkg-config" - ], - "type": "target" + "type": "target", + "artifact": "php-sdk-binary-tools" }, "strawberry-perl": { - "artifact": "strawberry-perl", - "type": "target" + "type": "target", + "artifact": "strawberry-perl" }, "upx": { - "artifact": "upx", - "type": "target" + "type": "target", + "artifact": "upx" }, "vswhere": { + "type": "target", "artifact": "vswhere", "static-bins@windows": [ "vswhere.exe" - ], - "type": "target" - }, - "zig": { - "artifact": "zig", - "type": "target" + ] } } diff --git a/config/pkg/lib/curl.yml b/config/pkg/lib/curl.yml index 38d632ce8..f183b21e0 100644 --- a/config/pkg/lib/curl.yml +++ b/config/pkg/lib/curl.yml @@ -4,15 +4,11 @@ curl: source: type: ghrel repo: curl/curl - match: 'curl.+\.tar\.xz' + match: curl.+\.tar\.xz prefer-stable: true metadata: license-files: [COPYING] license: curl - static-libs@unix: - - libcurl.a - headers: - - curl depends@unix: - openssl - zlib @@ -31,3 +27,7 @@ curl: - CoreFoundation - CoreServices - SystemConfiguration + headers: + - curl + static-libs@unix: + - libcurl.a diff --git a/config/pkg/lib/gettext.yml b/config/pkg/lib/gettext.yml index 5b1f57a63..58f541f81 100644 --- a/config/pkg/lib/gettext.yml +++ b/config/pkg/lib/gettext.yml @@ -10,10 +10,10 @@ gettext: license: LGPL-2.1-or-later depends: - libiconv + suggests: + - ncurses + - libxml2 frameworks: - CoreFoundation static-libs@unix: - libintl.a - suggests: - - ncurses - - libxml2 diff --git a/config/pkg/lib/idn2.yml b/config/pkg/lib/idn2.yml index 5d2e20439..cef2a1496 100644 --- a/config/pkg/lib/idn2.yml +++ b/config/pkg/lib/idn2.yml @@ -11,11 +11,11 @@ idn2: depends@macos: - libiconv - gettext - headers: - - idn2.h - pkg-configs: - - libidn2 suggests@unix: - libiconv - gettext - libunistring + headers: + - idn2.h + pkg-configs: + - libidn2 diff --git a/config/pkg/lib/krb5.yml b/config/pkg/lib/krb5.yml index 305a95a18..07fa3327f 100644 --- a/config/pkg/lib/krb5.yml +++ b/config/pkg/lib/krb5.yml @@ -11,6 +11,9 @@ krb5: source-root: src depends: - openssl + suggests: + - ldap + - libedit frameworks: - Kerberos headers: @@ -18,6 +21,3 @@ krb5: - gssapi/gssapi.h pkg-configs: - krb5-gssapi - suggests: - - ldap - - libedit diff --git a/config/pkg/lib/libxml2.yml b/config/pkg/lib/libxml2.yml index 8ba0f8e18..db88e8b14 100644 --- a/config/pkg/lib/libxml2.yml +++ b/config/pkg/lib/libxml2.yml @@ -10,10 +10,10 @@ libxml2: license: MIT depends@unix: - libiconv + suggests@unix: + - xz + - zlib headers: - libxml2 pkg-configs: - libxml-2.0 - suggests@unix: - - xz - - zlib diff --git a/config/pkg/lib/nghttp2.yml b/config/pkg/lib/nghttp2.yml index 20a1840db..166c33ac5 100644 --- a/config/pkg/lib/nghttp2.yml +++ b/config/pkg/lib/nghttp2.yml @@ -11,14 +11,14 @@ nghttp2: depends: - zlib - openssl + suggests: + - libxml2 + - nghttp3 + - ngtcp2 + - brotli headers: - nghttp2 pkg-configs: - libnghttp2 static-libs@unix: - libnghttp2.a - suggests: - - libxml2 - - nghttp3 - - ngtcp2 - - brotli diff --git a/config/pkg/lib/ngtcp2.yml b/config/pkg/lib/ngtcp2.yml index a609d3ca5..c864739a7 100644 --- a/config/pkg/lib/ngtcp2.yml +++ b/config/pkg/lib/ngtcp2.yml @@ -11,6 +11,9 @@ ngtcp2: license: MIT depends: - openssl + suggests: + - nghttp3 + - brotli headers: - ngtcp2 pkg-configs: @@ -19,6 +22,3 @@ ngtcp2: static-libs@unix: - libngtcp2.a - libngtcp2_crypto_ossl.a - suggests: - - nghttp3 - - brotli diff --git a/config/pkg/target/pkg-config.yml b/config/pkg/target/pkg-config.yml new file mode 100644 index 000000000..9b6d16035 --- /dev/null +++ b/config/pkg/target/pkg-config.yml @@ -0,0 +1,9 @@ +pkg-config: + type: target + artifact: + source: 'https://dl.static-php.dev/static-php-cli/deps/pkg-config/pkg-config-0.29.2.tar.gz' + binary: + linux-x86_64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-x86_64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } } + linux-aarch64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-aarch64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } } + macos-x86_64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-x86_64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } } + macos-aarch64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-aarch64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } } diff --git a/config/pkg/target/zig.yml b/config/pkg/target/zig.yml new file mode 100644 index 000000000..dda72b55b --- /dev/null +++ b/config/pkg/target/zig.yml @@ -0,0 +1,4 @@ +zig: + type: target + artifact: + binary: custom diff --git a/spc.registry.json b/spc.registry.json index e8be4b5d8..b3d34939a 100644 --- a/spc.registry.json +++ b/spc.registry.json @@ -13,6 +13,7 @@ "config": [ "config/pkg.ext.json", "config/pkg/lib", + "config/pkg/target", "config/pkg.target.json" ] }, diff --git a/src/StaticPHP/Command/Dev/LintConfigCommand.php b/src/StaticPHP/Command/Dev/LintConfigCommand.php index 1b08bb85c..8149af14d 100644 --- a/src/StaticPHP/Command/Dev/LintConfigCommand.php +++ b/src/StaticPHP/Command/Dev/LintConfigCommand.php @@ -56,7 +56,7 @@ public function artifactSortKey(string $a, string $b): int public function packageSortKey(string $a, string $b): int { // sort by predefined order, other not matching keys go to the end alphabetically - $order = ['type', 'artifact']; + $order = ['type', 'artifact', 'depends', 'suggests', 'frameworks']; // Handle suffix patterns (e.g., 'depends@unix', 'static-libs@windows') $base_a = preg_replace('/@(unix|windows|macos|linux|freebsd|bsd)$/', '', $a); diff --git a/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php b/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php index 4d7a86bee..b64df85b2 100644 --- a/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php +++ b/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php @@ -27,25 +27,25 @@ class LinuxMuslCheck public static function optionalCheck(): bool { $toolchain = ApplicationContext::get(ToolchainInterface::class); - return $toolchain instanceof MuslToolchain || $toolchain instanceof ZigToolchain && !LinuxUtil::isMuslDist(); + return $toolchain instanceof MuslToolchain || $toolchain instanceof ZigToolchain && !LinuxUtil::isMuslDist() && !str_contains(getenv('SPC_TARGET') ?: '', 'gnu'); } /** @noinspection PhpUnused */ #[CheckItem('if musl-wrapper is installed', limit_os: 'Linux', level: 800)] - public function checkMusl(): CheckResult + public function checkMusl(): ?CheckResult { $musl_wrapper_lib = sprintf('/lib/ld-musl-%s.so.1', php_uname('m')); if (file_exists($musl_wrapper_lib) && (file_exists('/usr/local/musl/lib/libc.a') || getenv('SPC_TOOLCHAIN') === ZigToolchain::class)) { - return CheckResult::ok(); + return null; } return CheckResult::fail('musl-wrapper is not installed on your system', 'fix-musl-wrapper'); } #[CheckItem('if musl-cross-make is installed', limit_os: 'Linux', level: 799)] - public function checkMuslCrossMake(): CheckResult + public function checkMuslCrossMake(): ?CheckResult { if (getenv('SPC_TOOLCHAIN') === ZigToolchain::class && !LinuxUtil::isMuslDist()) { - return CheckResult::ok(); + return null; } $arch = arch2gnu(php_uname('m')); $cross_compile_lib = "/usr/local/musl/{$arch}-linux-musl/lib/libc.a"; diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 9ceb21078..2469159af 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -259,7 +259,7 @@ public function isPackageInstalled(Package|string $package_name): bool if ($this->isBuildPackage($package)) { return $package->isInstalled(); } - if ($package instanceof LibraryPackage && $package->getArtifact()->shouldUseBinary()) { + if ($package->getArtifact() !== null && $package->getArtifact()->shouldUseBinary()) { $artifact = $package->getArtifact(); return $artifact->isBinaryExtracted(); } diff --git a/src/StaticPHP/Toolchain/ClangNativeToolchain.php b/src/StaticPHP/Toolchain/ClangNativeToolchain.php index 2513dd714..27ae2b65b 100644 --- a/src/StaticPHP/Toolchain/ClangNativeToolchain.php +++ b/src/StaticPHP/Toolchain/ClangNativeToolchain.php @@ -18,10 +18,10 @@ class ClangNativeToolchain implements UnixToolchainInterface { public function initEnv(): void { - GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=clang'); - GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=clang++'); - GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=ar'); - GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld'); + GlobalEnvManager::putenv('SPC_DEFAULT_CC=clang'); + GlobalEnvManager::putenv('SPC_DEFAULT_CXX=clang++'); + GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar'); + GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld'); } public function afterInit(): void diff --git a/src/StaticPHP/Toolchain/GccNativeToolchain.php b/src/StaticPHP/Toolchain/GccNativeToolchain.php index dbf9925ec..7c339e69e 100644 --- a/src/StaticPHP/Toolchain/GccNativeToolchain.php +++ b/src/StaticPHP/Toolchain/GccNativeToolchain.php @@ -15,10 +15,10 @@ class GccNativeToolchain implements UnixToolchainInterface { public function initEnv(): void { - GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=gcc'); - GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=g++'); - GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=ar'); - GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld'); + GlobalEnvManager::putenv('SPC_DEFAULT_CC=gcc'); + GlobalEnvManager::putenv('SPC_DEFAULT_CXX=g++'); + GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar'); + GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld'); } public function afterInit(): void diff --git a/src/StaticPHP/Toolchain/MuslToolchain.php b/src/StaticPHP/Toolchain/MuslToolchain.php index c7b39dbf6..6963a3645 100644 --- a/src/StaticPHP/Toolchain/MuslToolchain.php +++ b/src/StaticPHP/Toolchain/MuslToolchain.php @@ -14,10 +14,10 @@ public function initEnv(): void { $arch = getenv('GNU_ARCH'); // Set environment variables for musl toolchain - GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_CC={$arch}-linux-musl-gcc"); - GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_CXX={$arch}-linux-musl-g++"); - GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_AR={$arch}-linux-musl-ar"); - GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_LD={$arch}-linux-musl-ld"); + GlobalEnvManager::putenv("SPC_DEFAULT_CC={$arch}-linux-musl-gcc"); + GlobalEnvManager::putenv("SPC_DEFAULT_CXX={$arch}-linux-musl-g++"); + GlobalEnvManager::putenv("SPC_DEFAULT_AR={$arch}-linux-musl-ar"); + GlobalEnvManager::putenv("SPC_DEFAULT_LD={$arch}-linux-musl-ld"); GlobalEnvManager::addPathIfNotExists('/usr/local/musl/bin'); GlobalEnvManager::addPathIfNotExists("/usr/local/musl/{$arch}-linux-musl/bin"); @@ -40,7 +40,7 @@ public function afterInit(): void public function getCompilerInfo(): ?string { - $compiler = getenv('CC') ?: getenv('SPC_LINUX_DEFAULT_CC'); + $compiler = getenv('CC') ?: getenv('SPC_DEFAULT_CC'); $version = shell(false)->execWithResult("{$compiler} --version", false); $head = pathinfo($compiler, PATHINFO_BASENAME); if ($version[0] === 0 && preg_match('/linux-musl-cc.*(\d+.\d+.\d+)/', $version[1][0], $match)) { diff --git a/src/StaticPHP/Util/PkgConfigUtil.php b/src/StaticPHP/Util/PkgConfigUtil.php index 5efd33de9..d5b03757f 100644 --- a/src/StaticPHP/Util/PkgConfigUtil.php +++ b/src/StaticPHP/Util/PkgConfigUtil.php @@ -28,7 +28,12 @@ public static function findPkgConfig(): ?string ]; $found = null; foreach ($find_list as $file) { - if (file_exists($file) && is_executable($file)) { + $exists = file_exists($file); + $executable = is_executable($file); + if (!$exists) { + continue; + } + if (!$executable && chmod($file, 0755) || $executable) { $found = $file; break; } From b89ff3c0835ddd55a684ecb5ebfa23149af41731 Mon Sep 17 00:00:00 2001 From: Jerry Ma Date: Tue, 3 Feb 2026 19:08:19 +0800 Subject: [PATCH 38/99] Add com_dotnet extension (#1023) --- config/ext.json | 8 ++++++++ src/SPC/builder/extension/com_dotnet.php | 17 +++++++++++++++++ src/SPC/command/SwitchPhpVersionCommand.php | 6 +++--- src/globals/test-extensions.php | 16 ++++++++-------- 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 src/SPC/builder/extension/com_dotnet.php diff --git a/config/ext.json b/config/ext.json index 566974b92..4352f2e2c 100644 --- a/config/ext.json +++ b/config/ext.json @@ -43,6 +43,14 @@ "calendar": { "type": "builtin" }, + "com_dotnet": { + "support": { + "BSD": "no", + "Linux": "no", + "Darwin": "no" + }, + "type": "builtin" + }, "ctype": { "type": "builtin" }, diff --git a/src/SPC/builder/extension/com_dotnet.php b/src/SPC/builder/extension/com_dotnet.php new file mode 100644 index 000000000..7a8f6a4e4 --- /dev/null +++ b/src/SPC/builder/extension/com_dotnet.php @@ -0,0 +1,17 @@ +addArgument( 'php-major-version', InputArgument::REQUIRED, - 'PHP major version (supported: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4)', + 'PHP major version (supported: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5)', null, - fn () => ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + fn () => ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] ); $this->no_motd = true; @@ -32,7 +32,7 @@ public function configure() public function handle(): int { $php_ver = $this->input->getArgument('php-major-version'); - if (!in_array($php_ver, ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'])) { + if (!in_array($php_ver, ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'])) { // match x.y.z preg_match('/^\d+\.\d+\.\d+$/', $php_ver, $matches); if (!$matches) { diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index e2186c8e4..8b22658ca 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -13,7 +13,7 @@ // test php version (8.1 ~ 8.4 available, multiple for matrix) $test_php_version = [ - // '8.1', + '8.1', '8.2', '8.3', '8.4', @@ -23,15 +23,15 @@ // test os (macos-15-intel, macos-15, ubuntu-latest, windows-latest are available) $test_os = [ - 'macos-15-intel', // bin/spc for x86_64 - 'macos-15', // bin/spc for arm64 - 'ubuntu-latest', // bin/spc-alpine-docker for x86_64 - 'ubuntu-22.04', // bin/spc-gnu-docker for x86_64 - 'ubuntu-24.04', // bin/spc for x86_64 + // 'macos-15-intel', // bin/spc for x86_64 + // 'macos-15', // bin/spc for arm64 + // 'ubuntu-latest', // bin/spc-alpine-docker for x86_64 + // 'ubuntu-22.04', // bin/spc-gnu-docker for x86_64 + // 'ubuntu-24.04', // bin/spc for x86_64 // 'ubuntu-22.04-arm', // bin/spc-gnu-docker for arm64 // 'ubuntu-24.04-arm', // bin/spc for arm64 // 'windows-2022', // .\bin\spc.ps1 - // 'windows-2025', + 'windows-2025', ]; // whether enable thread safe @@ -51,7 +51,7 @@ // If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`). $extensions = match (PHP_OS_FAMILY) { 'Linux', 'Darwin' => 'mysqli,gmp', - 'Windows' => 'bcmath', + 'Windows' => 'com_dotnet', }; // If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`). From 0d4cde79fac478a460c66fe181c8aa32c65d3546 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 20:23:32 +0800 Subject: [PATCH 39/99] Add download options for build:libs command --- src/StaticPHP/Command/BuildLibsCommand.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/StaticPHP/Command/BuildLibsCommand.php b/src/StaticPHP/Command/BuildLibsCommand.php index a415ca3e1..a637fe7ce 100644 --- a/src/StaticPHP/Command/BuildLibsCommand.php +++ b/src/StaticPHP/Command/BuildLibsCommand.php @@ -4,6 +4,7 @@ namespace StaticPHP\Command; +use StaticPHP\Artifact\DownloaderOptions; use StaticPHP\Package\PackageInstaller; use StaticPHP\Util\V2CompatLayer; use Symfony\Component\Console\Attribute\AsCommand; @@ -23,6 +24,8 @@ public function configure(): void new InputOption('no-download', null, null, 'Skip downloading artifacts (use existing cached files)'), ...V2CompatLayer::getLegacyBuildOptions(), ]); + // Downloader options (with 'dl-' prefix to avoid conflicts) + $this->getDefinition()->addOptions(DownloaderOptions::getConsoleOptions('dl')); } public function handle(): int From a02f287d9706cffce5d194c19d20d0daf4c0a452 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 3 Feb 2026 20:23:59 +0800 Subject: [PATCH 40/99] Fix macOS wrong patch file directory --- src/Package/Library/curl.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Package/Library/curl.php b/src/Package/Library/curl.php index a2856ad4b..283c765a1 100644 --- a/src/Package/Library/curl.php +++ b/src/Package/Library/curl.php @@ -22,9 +22,9 @@ public function patchBeforeBuild(LibraryPackage $lib): bool { shell()->cd($lib->getSourceDir())->exec('sed -i.save s@\${CMAKE_C_IMPLICIT_LINK_LIBRARIES}@@ ./CMakeLists.txt'); if (SystemTarget::getTargetOS() === 'Darwin') { - FileSystem::replaceFileRegex("{$lib->getSourceDir()}/curl/CMakeLists.txt", '/NOT COREFOUNDATION_FRAMEWORK/m', 'FALSE'); - FileSystem::replaceFileRegex("{$lib->getSourceDir()}/curl/CMakeLists.txt", '/NOT SYSTEMCONFIGURATION_FRAMEWORK/m', 'FALSE'); - FileSystem::replaceFileRegex("{$lib->getSourceDir()}/curl/CMakeLists.txt", '/NOT CORESERVICES_FRAMEWORK/m', 'FALSE'); + FileSystem::replaceFileRegex("{$lib->getSourceDir()}/CMakeLists.txt", '/NOT COREFOUNDATION_FRAMEWORK/m', 'FALSE'); + FileSystem::replaceFileRegex("{$lib->getSourceDir()}/CMakeLists.txt", '/NOT SYSTEMCONFIGURATION_FRAMEWORK/m', 'FALSE'); + FileSystem::replaceFileRegex("{$lib->getSourceDir()}/CMakeLists.txt", '/NOT CORESERVICES_FRAMEWORK/m', 'FALSE'); } return true; } From b5c14d6f26555ac7695687c34475080429a393ec Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 15:12:19 +0800 Subject: [PATCH 41/99] Fix golang download website hash match pattern --- src/Package/Artifact/go_xcaddy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Package/Artifact/go_xcaddy.php b/src/Package/Artifact/go_xcaddy.php index 293f7c62b..5fa7327e3 100644 --- a/src/Package/Artifact/go_xcaddy.php +++ b/src/Package/Artifact/go_xcaddy.php @@ -47,7 +47,7 @@ public function downBinary(ArtifactDownloader $downloader): DownloadResult } $version_regex = str_replace('.', '\.', $version); - $pattern = "/href=\"\\/dl\\/{$version_regex}\\.{$os}-{$arch}\\.tar\\.gz\">.*?([a-f0-9]{64})<\\/tt>/s"; + $pattern = "/class=\"download\" href=\"\\/dl\\/{$version_regex}\\.{$os}-{$arch}\\.tar\\.gz\">.*?([a-f0-9]{64})<\\/tt>/s"; if (preg_match($pattern, $page, $matches)) { $hash = $matches[1]; } else { From c40eaeef5d82768e205fb4934db672fc72d71020 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 15:14:30 +0800 Subject: [PATCH 42/99] Fix custom artifact binary download `is-installed` check --- src/StaticPHP/Artifact/Artifact.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/StaticPHP/Artifact/Artifact.php b/src/StaticPHP/Artifact/Artifact.php index b3e27371f..dc602538b 100644 --- a/src/StaticPHP/Artifact/Artifact.php +++ b/src/StaticPHP/Artifact/Artifact.php @@ -131,7 +131,9 @@ public function isSourceExtracted(bool $compare_hash = false): bool public function isBinaryExtracted(?string $target_os = null, bool $compare_hash = false): bool { $target_os = $target_os ?? SystemTarget::getCurrentPlatformString(); - $extract_config = $this->getBinaryExtractConfig(); + // Get cache info first for custom binary support (extract path may be stored in cache) + $cache_info = ApplicationContext::get(ArtifactCache::class)->getBinaryInfo($this->name, $target_os); + $extract_config = $this->getBinaryExtractConfig($cache_info ?? []); $mode = $extract_config['mode']; // For merge mode, check marker file From 08d20205a70349e0ff9f9e0269724f01bd4e03a2 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 15:14:44 +0800 Subject: [PATCH 43/99] Allow all artifact configs --- src/StaticPHP/Config/ArtifactConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StaticPHP/Config/ArtifactConfig.php b/src/StaticPHP/Config/ArtifactConfig.php index 49bfec233..83f079202 100644 --- a/src/StaticPHP/Config/ArtifactConfig.php +++ b/src/StaticPHP/Config/ArtifactConfig.php @@ -18,7 +18,7 @@ public static function loadFromDir(string $dir, string $registry_name): array throw new WrongUsageException("Directory {$dir} does not exist, cannot load artifact config."); } $loaded = []; - $files = glob("{$dir}/artifact.*.json"); + $files = glob("{$dir}/*"); if (is_array($files)) { foreach ($files as $file) { self::loadFromFile($file, $registry_name); From e2011e1c18e39cda7153408d4bed9ec9cf31e48c Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 15:14:57 +0800 Subject: [PATCH 44/99] Verbose message --- src/StaticPHP/Artifact/ArtifactDownloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StaticPHP/Artifact/ArtifactDownloader.php b/src/StaticPHP/Artifact/ArtifactDownloader.php index b07c56617..27cd6adc5 100644 --- a/src/StaticPHP/Artifact/ArtifactDownloader.php +++ b/src/StaticPHP/Artifact/ArtifactDownloader.php @@ -438,7 +438,7 @@ private function downloadWithType(Artifact $artifact, int $current, int $total, break; } } - $vvv = !ApplicationContext::isDebug() ? "\nIf the problem persists, consider using `-vvv` to enable verbose mode, or disable parallel downloading for more details." : ''; + $vvv = !ApplicationContext::isDebug() ? "\nIf the problem persists, consider using `-v`, `-vv` or `-vvv` to enable verbose mode, or disable parallel downloading for more details." : ''; throw new DownloaderException("Download artifact '{$artifact->getName()}' failed. Please check your internet connection and try again.{$vvv}"); } From 0afa1dd80c2395af3ab2f3b138216f836847c728 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 15:15:20 +0800 Subject: [PATCH 45/99] Use new brand name --- config/env.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/env.ini b/config/env.ini index c77f6bcd0..9e295d796 100644 --- a/config/env.ini +++ b/config/env.ini @@ -48,7 +48,7 @@ SPC_SKIP_DOCTOR_CHECK_ITEMS="" ; extra modules that xcaddy will include in the FrankenPHP build SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" ; The display message for php version output (PHP >= 8.4 available) -PHP_BUILD_PROVIDER="static-php-cli ${SPC_VERSION}" +PHP_BUILD_PROVIDER="StaticPHP ${SPC_VERSION}" ; Whether to enable log file (if you are using vendor mode) SPC_ENABLE_LOG_FILE="yes" ; The LOG DIR for spc logs From e9c27dee1fe44029b16d78aca2d8aa4d63f903fe Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 15:28:10 +0800 Subject: [PATCH 46/99] Add go-xcaddy,musl-toolchain,php,upx, and also glfw linux support --- config/artifact/glfw.yml | 9 +++ config/artifact/php-src.yml | 7 ++ config/pkg.ext.json | 21 ------ config/pkg.target.json | 66 ---------------- config/pkg/ext/ext-glfw.yml | 5 ++ config/pkg/lib/glfw.yml | 6 ++ config/pkg/target/go-xcaddy.yml | 6 ++ config/pkg/target/musl-toolchain.yml | 6 ++ config/pkg/target/php.yml | 44 +++++++++++ config/pkg/target/upx.yml | 7 ++ spc.registry.json | 8 +- src/Package/Artifact/php_src.php | 75 +++++++++++++++++++ src/Package/Extension/glfw.php | 59 +++++++++++++++ src/Package/Library/glfw.php | 100 +++++++++++++++++++++++++ src/Package/Target/php/unix.php | 42 +++++++++-- src/StaticPHP/Doctor/Item/ZigCheck.php | 6 +- 16 files changed, 365 insertions(+), 102 deletions(-) create mode 100644 config/artifact/glfw.yml create mode 100644 config/artifact/php-src.yml create mode 100644 config/pkg/ext/ext-glfw.yml create mode 100644 config/pkg/lib/glfw.yml create mode 100644 config/pkg/target/go-xcaddy.yml create mode 100644 config/pkg/target/musl-toolchain.yml create mode 100644 config/pkg/target/php.yml create mode 100644 config/pkg/target/upx.yml create mode 100644 src/Package/Artifact/php_src.php create mode 100644 src/Package/Extension/glfw.php create mode 100644 src/Package/Library/glfw.php diff --git a/config/artifact/glfw.yml b/config/artifact/glfw.yml new file mode 100644 index 000000000..05e9f18d4 --- /dev/null +++ b/config/artifact/glfw.yml @@ -0,0 +1,9 @@ +glfw: + metadata: + license-files: + - LICENSE + license: MIT + source: + type: git + url: 'https://github.com/mario-deluna/php-glfw' + rev: master diff --git a/config/artifact/php-src.yml b/config/artifact/php-src.yml new file mode 100644 index 000000000..32bcb6cf0 --- /dev/null +++ b/config/artifact/php-src.yml @@ -0,0 +1,7 @@ +php-src: + metadata: + license-files: + - LICENSE + license: PHP-3.01 + source: + type: php-release diff --git a/config/pkg.ext.json b/config/pkg.ext.json index d1ca0b3f0..cbcf5ad58 100644 --- a/config/pkg.ext.json +++ b/config/pkg.ext.json @@ -244,27 +244,6 @@ "arg-type": "with-path" } }, - "ext-glfw": { - "type": "php-extension", - "artifact": "ext-glfw", - "depends": [ - "glfw" - ], - "depends@windows": [], - "license": { - "type": "file", - "path": "LICENSE" - }, - "php-extension": { - "support": { - "Windows": "wip", - "BSD": "no", - "Linux": "no" - }, - "arg-type": "custom", - "notes": true - } - }, "ext-gmp": { "type": "php-extension", "depends": [ diff --git a/config/pkg.target.json b/config/pkg.target.json index a5c20b78c..59c6c65ac 100644 --- a/config/pkg.target.json +++ b/config/pkg.target.json @@ -1,70 +1,8 @@ { - "frankenphp": { - "type": "virtual-target", - "artifact": "frankenphp", - "depends": [ - "php-embed", - "go-xcaddy" - ], - "depends@macos": [ - "php-embed", - "go-xcaddy", - "libxml2" - ] - }, - "go-xcaddy": { - "type": "target", - "artifact": "go-xcaddy", - "static-bins": [ - "xcaddy" - ] - }, - "musl-toolchain": { - "type": "target", - "artifact": "musl-toolchain" - }, "nasm": { "type": "target", "artifact": "nasm" }, - "php": { - "type": "target", - "artifact": "php-src", - "depends@macos": [ - "libxml2" - ] - }, - "php-cgi": { - "type": "virtual-target", - "depends": [ - "php" - ] - }, - "php-cli": { - "type": "virtual-target", - "depends": [ - "php" - ] - }, - "php-embed": { - "type": "virtual-target", - "depends": [ - "php" - ] - }, - "php-fpm": { - "type": "virtual-target", - "depends": [ - "php" - ] - }, - "php-micro": { - "type": "virtual-target", - "artifact": "micro", - "depends": [ - "php" - ] - }, "php-sdk-binary-tools": { "type": "target", "artifact": "php-sdk-binary-tools" @@ -73,10 +11,6 @@ "type": "target", "artifact": "strawberry-perl" }, - "upx": { - "type": "target", - "artifact": "upx" - }, "vswhere": { "type": "target", "artifact": "vswhere", diff --git a/config/pkg/ext/ext-glfw.yml b/config/pkg/ext/ext-glfw.yml new file mode 100644 index 000000000..dc8844a8f --- /dev/null +++ b/config/pkg/ext/ext-glfw.yml @@ -0,0 +1,5 @@ +ext-glfw: + type: php-extension + artifact: glfw + depends: + - glfw diff --git a/config/pkg/lib/glfw.yml b/config/pkg/lib/glfw.yml new file mode 100644 index 000000000..13fba596e --- /dev/null +++ b/config/pkg/lib/glfw.yml @@ -0,0 +1,6 @@ +glfw: + type: library + artifact: glfw + lang: cpp + static-libs@unix: + - libglfw3.a diff --git a/config/pkg/target/go-xcaddy.yml b/config/pkg/target/go-xcaddy.yml new file mode 100644 index 000000000..89cb4cd02 --- /dev/null +++ b/config/pkg/target/go-xcaddy.yml @@ -0,0 +1,6 @@ +go-xcaddy: + type: target + artifact: + binary: custom + static-bins: + - xcaddy diff --git a/config/pkg/target/musl-toolchain.yml b/config/pkg/target/musl-toolchain.yml new file mode 100644 index 000000000..038059e27 --- /dev/null +++ b/config/pkg/target/musl-toolchain.yml @@ -0,0 +1,6 @@ +musl-toolchain: + type: target + artifact: + binary: + linux-x86_64: { type: url, url: 'https://dl.static-php.dev/static-php-cli/deps/musl-toolchain/x86_64-musl-toolchain.tgz', extract: '{pkg_root_path}/musl-toolchain' } + linux-aarch64: { type: url, url: 'https://dl.static-php.dev/static-php-cli/deps/musl-toolchain/aarch64-musl-toolchain.tgz', extract: '{pkg_root_path}/musl-toolchain' } diff --git a/config/pkg/target/php.yml b/config/pkg/target/php.yml new file mode 100644 index 000000000..8f88cb581 --- /dev/null +++ b/config/pkg/target/php.yml @@ -0,0 +1,44 @@ +frankenphp: + type: virtual-target + artifact: + source: + type: ghtar + repo: php/frankenphp + prefer-stable: true + metadata: + license-files: [LICENSE] + license: MIT + depends: + - php-embed + - go-xcaddy +php: + type: target + artifact: php-src + depends@macos: + - libxml2 +php-cgi: + type: virtual-target + depends: + - php +php-cli: + type: virtual-target + depends: + - php +php-embed: + type: virtual-target + depends: + - php +php-fpm: + type: virtual-target + depends: + - php +php-micro: + type: virtual-target + artifact: + source: + type: git + extract: php-src/sapi/micro + rev: master + url: 'https://github.com/static-php/phpmicro' + depends: + - php diff --git a/config/pkg/target/upx.yml b/config/pkg/target/upx.yml new file mode 100644 index 000000000..29506c39a --- /dev/null +++ b/config/pkg/target/upx.yml @@ -0,0 +1,7 @@ +upx: + type: target + artifact: + binary: + linux-x86_64: { type: ghrel, repo: upx/upx, match: upx.+-amd64_linux\.tar\.xz, extract: { upx: '{pkg_root_path}/bin/upx' } } + linux-aarch64: { type: ghrel, repo: upx/upx, match: upx.+-arm64_linux\.tar\.xz, extract: { upx: '{pkg_root_path}/bin/upx' } } + windows-x86_64: { type: ghrel, repo: upx/upx, match: upx.+-win64\.zip, extract: { upx.exe: '{pkg_root_path}/bin/upx.exe' } } diff --git a/spc.registry.json b/spc.registry.json index b3d34939a..b731249ba 100644 --- a/spc.registry.json +++ b/spc.registry.json @@ -12,12 +12,16 @@ }, "config": [ "config/pkg.ext.json", - "config/pkg/lib", - "config/pkg/target", + "config/pkg/lib/", + "config/pkg/target/", + "config/pkg/ext/", "config/pkg.target.json" ] }, "artifact": { + "config": [ + "config/artifact/" + ], "psr-4": { "Package\\Artifact": "src/Package/Artifact" } diff --git a/src/Package/Artifact/php_src.php b/src/Package/Artifact/php_src.php new file mode 100644 index 000000000..498abc66e --- /dev/null +++ b/src/Package/Artifact/php_src.php @@ -0,0 +1,75 @@ += 80100 ? file_get_contents(ROOT_DIR . '/src/globals/extra/gd_config_81.w32') : file_get_contents(ROOT_DIR . '/src/globals/extra/gd_config_80.w32'); + file_put_contents(SOURCE_PATH . '/php-src/ext/gd/config.w32.bak', file_get_contents(SOURCE_PATH . '/php-src/ext/gd/config.w32')); + file_put_contents(SOURCE_PATH . '/php-src/ext/gd/config.w32', $origin); + } + } + + #[AfterSourceExtract('php-src')] + #[PatchDescription('Patch FFI extension on CentOS 7 with -O3 optimization (strncmp issue)')] + public function patchFfiCentos7FixO3strncmp(): void + { + spc_skip_if(!($ver = SystemTarget::getLibcVersion()) || version_compare($ver, '2.17', '>')); + spc_skip_if(!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')); + $file = file_get_contents(SOURCE_PATH . '/php-src/main/php_version.h'); + spc_skip_if(preg_match('/PHP_VERSION_ID (\d+)/', $file, $match) !== 0 && intval($match[1]) < 80316); + SourcePatcher::patchFile('ffi_centos7_fix_O3_strncmp.patch', SOURCE_PATH . '/php-src'); + } + + #[AfterSourceExtract('php-src')] + #[PatchDescription('Add LICENSE file to IMAP extension if missing')] + public function patchImapLicense(): void + { + if (!file_exists(SOURCE_PATH . '/php-src/ext/imap/LICENSE') && is_dir(SOURCE_PATH . '/php-src/ext/imap')) { + file_put_contents(SOURCE_PATH . '/php-src/ext/imap/LICENSE', file_get_contents(ROOT_DIR . '/src/globals/extra/Apache_LICENSE')); + } + } +} diff --git a/src/Package/Extension/glfw.php b/src/Package/Extension/glfw.php new file mode 100644 index 000000000..61d722e14 --- /dev/null +++ b/src/Package/Extension/glfw.php @@ -0,0 +1,59 @@ +getSourceDir(), SOURCE_PATH . '/php-src/ext/glfw'); + } + } + + #[BeforeStage('php', [php::class, 'configureForUnix'], 'ext-glfw')] + #[PatchDescription('Patch glfw extension before configure')] + public function patchBeforeConfigure(): void + { + FileSystem::replaceFileStr( + SOURCE_PATH . '/php-src/configure', + '-lglfw ', + '-lglfw3 ' + ); + + // add X11 shared libs for linux + if (SystemTarget::getTargetOS() === 'Linux') { + $extra_libs = getenv('SPC_EXTRA_LIBS') ?: ''; + $extra_libs .= ' -lX11 -lXrandr -lXinerama -lXcursor -lXi'; + putenv('SPC_EXTRA_LIBS=' . trim($extra_libs)); + $extra_cflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') ?: ''; + + $extra_cflags .= ' -idirafter /usr/include'; + putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . trim($extra_cflags)); + $extra_ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: ''; + $extra_ldflags .= ' -L/usr/lib/' . SystemTarget::getTargetArch() . '-linux-gnu '; + putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS=' . $extra_ldflags); + } + } + + #[CustomPhpConfigureArg('Darwin')] + #[CustomPhpConfigureArg('Linux')] + public function getUnixConfigureArg(bool $shared = false): string + { + return '--enable-glfw --with-glfw-dir=' . BUILD_ROOT_PATH; + } +} diff --git a/src/Package/Library/glfw.php b/src/Package/Library/glfw.php new file mode 100644 index 000000000..9348489cd --- /dev/null +++ b/src/Package/Library/glfw.php @@ -0,0 +1,100 @@ +isStatic()) { + throw new ValidationException('glfw library does not support full-static linking on Linux, please build with dynamic target specified.'); + } + // detect X11 dev packages + $required_headers = [ + '/usr/include/X11', + '/usr/include/X11/extensions/Xrandr.h', + '/usr/include/X11/extensions/Xinerama.h', + '/usr/include/X11/Xcursor/Xcursor.h', + ]; + foreach ($required_headers as $header) { + if (!file_exists($header)) { + throw new ValidationException("glfw requires X11 development headers. Missing: {$header}. Please confirm that your system has the necessary X11 packages installed."); + } + } + } + } + + #[BuildFor('Linux')] + public function buildForLinux(LibraryPackage $lib): void + { + $x11_lib_find = [ + '/usr/lib/' . SystemTarget::getTargetArch() . '-linux-gnu/libX11.so', + '/usr/lib64/libX11.so', + '/usr/lib/libX11.so', + ]; + $found = false; + foreach ($x11_lib_find as $path) { + if (file_exists($path)) { + $found = $path; + break; + } + } + if (!$found) { + throw new BuildFailureException('Cannot find X11 library files in standard locations. Please ensure that the X11 development libraries are installed.'); + } + $base_path = pathinfo($found, PATHINFO_DIRNAME); + UnixCMakeExecutor::create($lib) + ->setBuildDir("{$lib->getSourceDir()}/vendor/glfw") + ->setReset(false) + ->addConfigureArgs( + '-DGLFW_BUILD_EXAMPLES=OFF', + '-DGLFW_BUILD_TESTS=OFF', + '-DGLFW_BUILD_DOCS=OFF', + '-DX11_X11_INCLUDE_PATH=/usr/include', + '-DX11_Xrandr_INCLUDE_PATH=/usr/include/X11/extensions', + '-DX11_Xinerama_INCLUDE_PATH=/usr/include/X11/extensions', + '-DX11_Xkb_INCLUDE_PATH=/usr/include/X11', + '-DX11_Xcursor_INCLUDE_PATH=/usr/include/X11/Xcursor', + '-DX11_Xi_INCLUDE_PATH=/usr/include/X11/extensions', + "-DX11_X11_LIB={$base_path}/libX11.so", + "-DX11_Xrandr_LIB={$base_path}/libXrandr.so", + "-DX11_Xinerama_LIB={$base_path}/libXinerama.so", + "-DX11_Xcursor_LIB={$base_path}/libXcursor.so", + "-DX11_Xi_LIB={$base_path}/libXi.so" + ) + ->build('.'); + // patch pkgconf + $lib->patchPkgconfPrefix(['glfw3.pc']); + } + + #[BuildFor('Darwin')] + public function buildForMac(LibraryPackage $lib): void + { + UnixCMakeExecutor::create($lib) + ->setBuildDir("{$lib->getSourceDir()}/vendor/glfw") + ->setReset(false) + ->addConfigureArgs( + '-DGLFW_BUILD_EXAMPLES=OFF', + '-DGLFW_BUILD_TESTS=OFF', + '-DGLFW_BUILD_DOCS=OFF', + ) + ->build('.'); + // patch pkgconf + $lib->patchPkgconfPrefix(['glfw3.pc']); + } +} diff --git a/src/Package/Target/php/unix.php b/src/Package/Target/php/unix.php index 1ce2ff95a..edbc6dd23 100644 --- a/src/Package/Target/php/unix.php +++ b/src/Package/Target/php/unix.php @@ -134,9 +134,11 @@ public function makeCliForUnix(TargetPackage $package, PackageInstaller $install { InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cli')); $concurrency = $builder->concurrency; + $vars = $this->makeVars($installer); + $makeArgs = $this->makeVarsToArgs($vars); shell()->cd($package->getSourceDir()) - ->setEnv($this->makeVars($installer)) - ->exec("make -j{$concurrency} cli"); + ->setEnv($vars) + ->exec("make -j{$concurrency} {$makeArgs} cli"); $builder->deployBinary("{$package->getSourceDir()}/sapi/cli/php", BUILD_BIN_PATH . '/php'); $package->setOutput('Binary path for cli SAPI', BUILD_BIN_PATH . '/php'); @@ -147,9 +149,11 @@ public function makeCgiForUnix(TargetPackage $package, PackageInstaller $install { InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cgi')); $concurrency = $builder->concurrency; + $vars = $this->makeVars($installer); + $makeArgs = $this->makeVarsToArgs($vars); shell()->cd($package->getSourceDir()) - ->setEnv($this->makeVars($installer)) - ->exec("make -j{$concurrency} cgi"); + ->setEnv($vars) + ->exec("make -j{$concurrency} {$makeArgs} cgi"); $builder->deployBinary("{$package->getSourceDir()}/sapi/cgi/php-cgi", BUILD_BIN_PATH . '/php-cgi'); $package->setOutput('Binary path for cgi SAPI', BUILD_BIN_PATH . '/php-cgi'); @@ -160,9 +164,11 @@ public function makeFpmForUnix(TargetPackage $package, PackageInstaller $install { InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make fpm')); $concurrency = $builder->concurrency; + $vars = $this->makeVars($installer); + $makeArgs = $this->makeVarsToArgs($vars); shell()->cd($package->getSourceDir()) - ->setEnv($this->makeVars($installer)) - ->exec("make -j{$concurrency} fpm"); + ->setEnv($vars) + ->exec("make -j{$concurrency} {$makeArgs} fpm"); $builder->deployBinary("{$package->getSourceDir()}/sapi/fpm/php-fpm", BUILD_BIN_PATH . '/php-fpm'); $package->setOutput('Binary path for fpm SAPI', BUILD_BIN_PATH . '/php-fpm'); @@ -182,10 +188,11 @@ public function makeMicroForUnix(TargetPackage $package, PackageInstaller $insta // apply --with-micro-fake-cli option $vars = $this->makeVars($installer); $vars['EXTRA_CFLAGS'] .= $package->getBuildOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; + $makeArgs = $this->makeVarsToArgs($vars); // build shell()->cd($package->getSourceDir()) ->setEnv($vars) - ->exec("make -j{$builder->concurrency} micro"); + ->exec("make -j{$builder->concurrency} {$makeArgs} micro"); $builder->deployBinary($package->getSourceDir() . '/sapi/micro/micro.sfx', BUILD_BIN_PATH . '/micro.sfx'); $package->setOutput('Binary path for micro SAPI', BUILD_BIN_PATH . '/micro.sfx'); @@ -440,11 +447,30 @@ private function makeVars(PackageInstaller $installer): array $static = ApplicationContext::get(ToolchainInterface::class)->isStatic() ? '-all-static' : ''; $pie = SystemTarget::getTargetOS() === 'Linux' ? '-pie' : ''; + // Append SPC_EXTRA_LIBS to libs for dynamic linking support (e.g., X11) + $extra_libs = getenv('SPC_EXTRA_LIBS') ?: ''; + $libs = trim($config['libs'] . ' ' . $extra_libs); + return array_filter([ 'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), 'EXTRA_LDFLAGS_PROGRAM' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') . "{$config['ldflags']} {$static} {$pie}", 'EXTRA_LDFLAGS' => $config['ldflags'], - 'EXTRA_LIBS' => $config['libs'], + 'EXTRA_LIBS' => $libs, ]); } + + /** + * Convert make variables array to command line argument string. + * This is needed because make command line arguments have higher priority than environment variables. + */ + private function makeVarsToArgs(array $vars): string + { + $args = []; + foreach ($vars as $key => $value) { + if (trim($value) !== '') { + $args[] = $key . '=' . escapeshellarg($value); + } + } + return implode(' ', $args); + } } diff --git a/src/StaticPHP/Doctor/Item/ZigCheck.php b/src/StaticPHP/Doctor/Item/ZigCheck.php index 4157e9d60..c3a6aa9f3 100644 --- a/src/StaticPHP/Doctor/Item/ZigCheck.php +++ b/src/StaticPHP/Doctor/Item/ZigCheck.php @@ -25,11 +25,7 @@ public static function optionalCheck(): bool #[CheckItem('if zig is installed', level: 800)] public function checkZig(): CheckResult { - $installer = new PackageInstaller(); - $package = 'zig'; - $installer->addInstallPackage($package); - $installed = $installer->isPackageInstalled($package); - if ($installed) { + if (new PackageInstaller()->addInstallPackage('zig')->isPackageInstalled('zig')) { return CheckResult::ok(); } return CheckResult::fail('zig is not installed', 'install-zig'); From 0652d4aa034550b512560493ea207e5fe2f450ba Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 15:31:05 +0800 Subject: [PATCH 47/99] Just in case source dir have not been created --- src/StaticPHP/Package/PackageInstaller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 2469159af..7fff8a144 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -325,6 +325,7 @@ public function getArtifacts(): array */ public function extractSourceArtifacts(bool $interactive = true): void { + FileSystem::createDir(SOURCE_PATH); $packages = array_values($this->packages); $cache = ApplicationContext::get(ArtifactCache::class); From 3fa2d69813662e495af801e9f3b51cee87c6634a Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 16:24:57 +0800 Subject: [PATCH 48/99] Add ext-mbstring,ext-mbregex,onig --- config/pkg/ext/ext-mbregex.yml | 9 +++++++++ config/pkg/ext/ext-mbstring.yml | 4 ++++ config/pkg/lib/onig.yml | 15 +++++++++++++++ src/Package/Extension/mbregex.php | 19 +++++++++++++++++++ src/Package/Extension/mbstring.php | 22 ++++++++++++++++++++++ src/Package/Library/onig.php | 9 +++++++++ 6 files changed, 78 insertions(+) create mode 100644 config/pkg/ext/ext-mbregex.yml create mode 100644 config/pkg/ext/ext-mbstring.yml create mode 100644 config/pkg/lib/onig.yml create mode 100644 src/Package/Extension/mbregex.php create mode 100644 src/Package/Extension/mbstring.php diff --git a/config/pkg/ext/ext-mbregex.yml b/config/pkg/ext/ext-mbregex.yml new file mode 100644 index 000000000..eaa0481a4 --- /dev/null +++ b/config/pkg/ext/ext-mbregex.yml @@ -0,0 +1,9 @@ +ext-mbregex: + type: php-extension + depends: + - onig + - ext-mbstring + php-extension: + arg-type: custom + build-shared: false + build-static: true diff --git a/config/pkg/ext/ext-mbstring.yml b/config/pkg/ext/ext-mbstring.yml new file mode 100644 index 000000000..6583ca616 --- /dev/null +++ b/config/pkg/ext/ext-mbstring.yml @@ -0,0 +1,4 @@ +ext-mbstring: + type: php-extension + php-extension: + arg-type: custom diff --git a/config/pkg/lib/onig.yml b/config/pkg/lib/onig.yml new file mode 100644 index 000000000..fa06524dc --- /dev/null +++ b/config/pkg/lib/onig.yml @@ -0,0 +1,15 @@ +onig: + type: library + artifact: + source: + type: ghrel + repo: kkos/oniguruma + match: onig-.+\.tar\.gz + metadata: + license-files: [COPYING] + license: Custom + headers: + - oniggnu.h + - oniguruma.h + static-libs@unix: + - libonig.a diff --git a/src/Package/Extension/mbregex.php b/src/Package/Extension/mbregex.php new file mode 100644 index 000000000..f01c7c787 --- /dev/null +++ b/src/Package/Extension/mbregex.php @@ -0,0 +1,19 @@ +isPackageResolved('ext-mbregex') === false ? ' --disable-mbregex' : ' --enable-mbregex'; + return $arg; + } +} diff --git a/src/Package/Library/onig.php b/src/Package/Library/onig.php index 2cea572bf..af289636f 100644 --- a/src/Package/Library/onig.php +++ b/src/Package/Library/onig.php @@ -7,6 +7,7 @@ use StaticPHP\Attribute\Package\BuildFor; use StaticPHP\Attribute\Package\Library; use StaticPHP\Package\LibraryPackage; +use StaticPHP\Runtime\Executor\UnixAutoconfExecutor; use StaticPHP\Runtime\Executor\WindowsCMakeExecutor; use StaticPHP\Util\FileSystem; @@ -21,4 +22,12 @@ public function buildWin(LibraryPackage $package): void ->build(); FileSystem::copy("{$package->getLibDir()}\\onig.lib", "{$package->getLibDir()}\\onig_a.lib"); } + + #[BuildFor('Linux')] + #[BuildFor('Darwin')] + public function buildUnix(LibraryPackage $lib): void + { + UnixAutoconfExecutor::create($lib)->configure()->make(); + $lib->patchPkgconfPrefix(['oniguruma.pc']); + } } From 16f94466fdef21f720fdeea58878214dd76bbe89 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 16:25:23 +0800 Subject: [PATCH 49/99] Add artifact name suggestions for download and install commands --- src/StaticPHP/Command/DownloadCommand.php | 14 +++++++++++++- .../Command/InstallPackageCommand.php | 19 +++++++++++++++++-- src/StaticPHP/Registry/ArtifactLoader.php | 11 +++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/StaticPHP/Command/DownloadCommand.php b/src/StaticPHP/Command/DownloadCommand.php index 277585e51..270f55385 100644 --- a/src/StaticPHP/Command/DownloadCommand.php +++ b/src/StaticPHP/Command/DownloadCommand.php @@ -6,11 +6,13 @@ use StaticPHP\Artifact\ArtifactDownloader; use StaticPHP\Artifact\DownloaderOptions; +use StaticPHP\Registry\ArtifactLoader; use StaticPHP\Registry\PackageLoader; use StaticPHP\Util\DependencyResolver; use StaticPHP\Util\FileSystem; use StaticPHP\Util\InteractiveTerm; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -19,7 +21,17 @@ class DownloadCommand extends BaseCommand { public function configure(): void { - $this->addArgument('artifacts', InputArgument::OPTIONAL, 'Specific artifacts to download, comma separated, e.g "php-src,openssl,curl"'); + $this->addArgument( + 'artifacts', + InputArgument::OPTIONAL, + 'Specific artifacts to download, comma separated, e.g "php-src,openssl,curl"', + suggestedValues: function (CompletionInput $input) { + $input_val = $input->getCompletionValue(); + $all_names = ArtifactLoader::getLoadedArtifactNames(); + // filter by input value + return array_filter($all_names, fn ($name) => str_starts_with($name, $input_val)); + }, + ); // 2.x compatible options $this->addOption('shallow-clone', null, null, '(deprecated) Clone shallowly repositories when downloading sources'); diff --git a/src/StaticPHP/Command/InstallPackageCommand.php b/src/StaticPHP/Command/InstallPackageCommand.php index 89814f013..230322618 100644 --- a/src/StaticPHP/Command/InstallPackageCommand.php +++ b/src/StaticPHP/Command/InstallPackageCommand.php @@ -6,14 +6,29 @@ use StaticPHP\DI\ApplicationContext; use StaticPHP\Package\PackageInstaller; +use StaticPHP\Registry\PackageLoader; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Input\InputArgument; #[AsCommand('install-pkg', 'Install additional package', ['i', 'install-package'])] class InstallPackageCommand extends BaseCommand { - public function configure() + public function configure(): void { - $this->addArgument('package', null, 'The package to install (name or path)'); + $this->addArgument( + 'package', + InputArgument::REQUIRED, + 'The package to install (name or path)', + suggestedValues: function (CompletionInput $input) { + $packages = []; + foreach (PackageLoader::getPackages(['target', 'virtual-target']) as $name => $_) { + $packages[] = $name; + } + $val = $input->getCompletionValue(); + return array_filter($packages, fn ($name) => str_starts_with($name, $val)); + } + ); } public function handle(): int diff --git a/src/StaticPHP/Registry/ArtifactLoader.php b/src/StaticPHP/Registry/ArtifactLoader.php index 22942452f..d53d9be04 100644 --- a/src/StaticPHP/Registry/ArtifactLoader.php +++ b/src/StaticPHP/Registry/ArtifactLoader.php @@ -69,6 +69,17 @@ public static function loadFromClass(string $class): void } } + /** + * Get names of all loaded artifacts. + * + * @return string[] + */ + public static function getLoadedArtifactNames(): array + { + self::initArtifactInstances(); + return array_keys(self::$artifacts ?? []); + } + /** * Process #[CustomSource] attribute. */ From b9af9ba05677186c24473f9230642f716f7a7d22 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 4 Feb 2026 16:25:34 +0800 Subject: [PATCH 50/99] Chore --- src/StaticPHP/Package/PackageBuilder.php | 3 +++ src/StaticPHP/Package/PackageInstaller.php | 3 +-- src/bootstrap.php | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/StaticPHP/Package/PackageBuilder.php b/src/StaticPHP/Package/PackageBuilder.php index 94c55a66e..42bf5c6f7 100644 --- a/src/StaticPHP/Package/PackageBuilder.php +++ b/src/StaticPHP/Package/PackageBuilder.php @@ -100,6 +100,9 @@ public function deployBinary(string $src, string $dst, bool $executable = true): // ignore copy to self if (realpath($src) !== realpath($dst)) { FileSystem::copy($src, $dst); + if ($executable) { + chmod($dst, 0755); + } } // file exist diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 7fff8a144..b9a3debb7 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -526,8 +526,7 @@ private function handlePhpTargetPackage(TargetPackage $package): void { // process 'php' target if ($package->getName() === 'php') { - logger()->warning("Building 'php' target is deprecated, please use specific targets like 'build:php-cli' instead."); - + // logger()->warning("Building 'php' target is deprecated, please use specific targets like 'build:php-cli' instead."); $added = false; if ($package->getBuildOption('build-all') || $package->getBuildOption('build-cli')) { diff --git a/src/bootstrap.php b/src/bootstrap.php index 6af814e8f..15e30b593 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -58,7 +58,7 @@ }); } -// load internal registry +// load core registry Registry::loadRegistry(ROOT_DIR . '/spc.registry.json'); // load registries from environment variable SPC_REGISTRIES Registry::loadFromEnvOrOption(); From 4d4b1a334fe050fa3f46af7da74fb40928052d19 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 16:10:16 +0800 Subject: [PATCH 51/99] Add ext-readline,freetype,gmssl,grpc,icu --- config/pkg/ext/ext-mbregex.yml | 1 + config/pkg/ext/ext-readline.yml | 11 ++++++ config/pkg/lib/attr.yml | 8 +--- config/pkg/lib/freetype.yml | 22 +++++++++++ config/pkg/lib/gmssl.yml | 15 +++++++ config/pkg/lib/grpc.yml | 19 +++++++++ config/pkg/lib/icu.yml | 16 ++++++++ spc.registry.json | 4 +- src/Package/Library/freetype.php | 36 +++++++++++++++++ src/Package/Library/gmssl.php | 21 ++++++++++ src/Package/Library/grpc.php | 68 ++++++++++++++++++++++++++++++++ src/Package/Library/icu.php | 66 +++++++++++++++++++++++++++++++ 12 files changed, 280 insertions(+), 7 deletions(-) create mode 100644 config/pkg/ext/ext-readline.yml create mode 100644 config/pkg/lib/freetype.yml create mode 100644 config/pkg/lib/gmssl.yml create mode 100644 config/pkg/lib/grpc.yml create mode 100644 config/pkg/lib/icu.yml create mode 100644 src/Package/Library/freetype.php create mode 100644 src/Package/Library/gmssl.php create mode 100644 src/Package/Library/grpc.php create mode 100644 src/Package/Library/icu.php diff --git a/config/pkg/ext/ext-mbregex.yml b/config/pkg/ext/ext-mbregex.yml index eaa0481a4..ae59f0235 100644 --- a/config/pkg/ext/ext-mbregex.yml +++ b/config/pkg/ext/ext-mbregex.yml @@ -7,3 +7,4 @@ ext-mbregex: arg-type: custom build-shared: false build-static: true + display-name: mbstring diff --git a/config/pkg/ext/ext-readline.yml b/config/pkg/ext/ext-readline.yml new file mode 100644 index 000000000..19b1886c8 --- /dev/null +++ b/config/pkg/ext/ext-readline.yml @@ -0,0 +1,11 @@ +ext-readline: + type: php-extension + depends: + - libedit + php-extension: + support: + Windows: wip + BSD: wip + arg-type: with-path + build-shared: false + build-static: true diff --git a/config/pkg/lib/attr.yml b/config/pkg/lib/attr.yml index 289bcd67b..cbf181c9d 100644 --- a/config/pkg/lib/attr.yml +++ b/config/pkg/lib/attr.yml @@ -1,12 +1,8 @@ attr: type: library artifact: - source: - type: url - url: 'https://download.savannah.nongnu.org/releases/attr/attr-2.5.2.tar.gz' - source-mirror: - type: url - url: 'https://mirror.souseiseki.middlendian.com/nongnu/attr/attr-2.5.2.tar.gz' + source: 'https://download.savannah.nongnu.org/releases/attr/attr-2.5.2.tar.gz' + source-mirror: 'https://mirror.souseiseki.middlendian.com/nongnu/attr/attr-2.5.2.tar.gz' metadata: license-files: [doc/COPYING.LGPL] license: LGPL-2.1-or-later diff --git a/config/pkg/lib/freetype.yml b/config/pkg/lib/freetype.yml new file mode 100644 index 000000000..c101a174b --- /dev/null +++ b/config/pkg/lib/freetype.yml @@ -0,0 +1,22 @@ +freetype: + type: library + artifact: + source: + type: ghtagtar + repo: freetype/freetype + match: VER-2-\d+-\d+ + metadata: + license-files: [LICENSE.TXT] + license: FTL + depends: + - zlib + suggests: + - bzip2 + - brotli + headers@unix: + - freetype2/freetype/freetype.h + - freetype2/ft2build.h + static-libs@unix: + - libfreetype.a + static-libs@windows: + - libfreetype_a.lib diff --git a/config/pkg/lib/gmssl.yml b/config/pkg/lib/gmssl.yml new file mode 100644 index 000000000..076234353 --- /dev/null +++ b/config/pkg/lib/gmssl.yml @@ -0,0 +1,15 @@ +gmssl: + type: library + artifact: + source: + type: ghtar + repo: guanzhi/GmSSL + metadata: + license-files: [LICENSE] + license: Apache-2.0 + frameworks: + - Security + static-libs@unix: + - libgmssl.a + static-libs@windows: + - gmssl.lib diff --git a/config/pkg/lib/grpc.yml b/config/pkg/lib/grpc.yml new file mode 100644 index 000000000..feb6068bb --- /dev/null +++ b/config/pkg/lib/grpc.yml @@ -0,0 +1,19 @@ +grpc: + type: library + artifact: + source: + type: git + rev: v1.75.x + url: 'https://github.com/grpc/grpc.git' + metadata: + license-files: [LICENSE] + license: Apache-2.0 + depends: + - zlib + - openssl + - libcares + frameworks: + - CoreFoundation + lang: cpp + pkg-configs: + - grpc diff --git a/config/pkg/lib/icu.yml b/config/pkg/lib/icu.yml new file mode 100644 index 000000000..43328228d --- /dev/null +++ b/config/pkg/lib/icu.yml @@ -0,0 +1,16 @@ +icu: + type: library + artifact: + source: + type: ghrel + repo: unicode-org/icu + match: icu4c.+-src\.tgz + prefer-stable: true + metadata: + license-files: [LICENSE] + license: ICU + lang: cpp + pkg-configs: + - icu-uc + - icu-i18n + - icu-io diff --git a/spc.registry.json b/spc.registry.json index b731249ba..cf49c6c83 100644 --- a/spc.registry.json +++ b/spc.registry.json @@ -1,6 +1,9 @@ { "name": "core", "autoload": "vendor/autoload.php", + "scripts": [ + "ext/" + ], "doctor": { "psr-4": { "StaticPHP\\Doctor\\Item": "src/StaticPHP/Doctor/Item" @@ -11,7 +14,6 @@ "Package": "src/Package" }, "config": [ - "config/pkg.ext.json", "config/pkg/lib/", "config/pkg/target/", "config/pkg/ext/", diff --git a/src/Package/Library/freetype.php b/src/Package/Library/freetype.php new file mode 100644 index 000000000..6cb05a90a --- /dev/null +++ b/src/Package/Library/freetype.php @@ -0,0 +1,36 @@ +optionalPackage('libpng', ...cmake_boolean_args('FT_DISABLE_PNG', true)) + ->optionalPackage('bzip2', ...cmake_boolean_args('FT_DISABLE_BZIP2', true)) + ->optionalPackage('brotli', ...cmake_boolean_args('FT_DISABLE_BROTLI', true)) + ->addConfigureArgs('-DFT_DISABLE_HARFBUZZ=ON'); + + // fix cmake 4.0 compatibility + if (version_compare(get_cmake_version(), '4.0.0', '>=')) { + $cmake->addConfigureArgs('-DCMAKE_POLICY_VERSION_MINIMUM=3.12'); + } + + $cmake->build(); + + $lib->patchPkgconfPrefix(['freetype2.pc']); + FileSystem::replaceFileStr("{$lib->getBuildRootPath()}/lib/pkgconfig/freetype2.pc", ' -L/lib ', " -L{$lib->getBuildRootPath()}/lib "); + } +} diff --git a/src/Package/Library/gmssl.php b/src/Package/Library/gmssl.php new file mode 100644 index 000000000..21b4ea668 --- /dev/null +++ b/src/Package/Library/gmssl.php @@ -0,0 +1,21 @@ +build(); + } +} diff --git a/src/Package/Library/grpc.php b/src/Package/Library/grpc.php new file mode 100644 index 000000000..86cddcc06 --- /dev/null +++ b/src/Package/Library/grpc.php @@ -0,0 +1,68 @@ +getSourceDir()}/third_party/re2/util/pcre.h", + ["#define UTIL_PCRE_H_\n#include ", '#define UTIL_PCRE_H_'], + ['#define UTIL_PCRE_H_', "#define UTIL_PCRE_H_\n#include "], + ); + return true; + } + + #[BuildFor('Linux')] + #[BuildFor('Darwin')] + public function buildUnix(ToolchainInterface $toolchain, LibraryPackage $lib): void + { + $cmake = UnixCMakeExecutor::create($lib) + ->setBuildDir("{$lib->getSourceDir()}/avoid_BUILD_file_conflict") + ->addConfigureArgs( + "-DgRPC_INSTALL_BINDIR={$lib->getBinDir()}", + "-DgRPC_INSTALL_LIBDIR={$lib->getLibDir()}", + "-DgRPC_INSTALL_SHAREDIR={$lib->getBuildRootPath()}/share/grpc", + "-DCMAKE_C_FLAGS=\"-DGRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK -L{$lib->getLibDir()} -I{$lib->getIncludeDir()}\"", + "-DCMAKE_CXX_FLAGS=\"-DGRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK -L{$lib->getLibDir()} -I{$lib->getIncludeDir()}\"", + '-DgRPC_BUILD_CODEGEN=OFF', + '-DgRPC_DOWNLOAD_ARCHIVES=OFF', + '-DgRPC_BUILD_TESTS=OFF', + // providers + '-DgRPC_ZLIB_PROVIDER=package', + '-DgRPC_CARES_PROVIDER=package', + '-DgRPC_SSL_PROVIDER=package', + ); + + if (PHP_OS_FAMILY === 'Linux' && $toolchain->isStatic() && !LinuxUtil::isMuslDist()) { + $cmake->addConfigureArgs( + '-DCMAKE_EXE_LINKER_FLAGS="-static-libgcc -static-libstdc++"', + '-DCMAKE_SHARED_LINKER_FLAGS="-static-libgcc -static-libstdc++"', + '-DCMAKE_CXX_STANDARD_LIBRARIES="-static-libgcc -static-libstdc++"', + ); + } + + $cmake->build(); + + $re2Content = file_get_contents("{$lib->getSourceDir()}/third_party/re2/re2.pc"); + $re2Content = "prefix={$lib->getBuildRootPath()}\nexec_prefix=\${prefix}\n{$re2Content}"; + file_put_contents("{$lib->getLibDir()}/pkgconfig/re2.pc", $re2Content); + $lib->patchPkgconfPrefix(['grpc++.pc', 'grpc.pc', 'grpc++_unsecure.pc', 'grpc_unsecure.pc', 're2.pc']); + } +} diff --git a/src/Package/Library/icu.php b/src/Package/Library/icu.php new file mode 100644 index 000000000..7364e94ab --- /dev/null +++ b/src/Package/Library/icu.php @@ -0,0 +1,66 @@ +getBinDir()}/icu-config", '/default_prefix=.*/m', 'default_prefix="{BUILD_ROOT_PATH}"'); + } + + #[BuildFor('Linux')] + public function buildLinux(LibraryPackage $lib, ToolchainInterface $toolchain, PackageBuilder $builder): void + { + $cppflags = 'CPPFLAGS="-DU_CHARSET_IS_UTF8=1 -DU_USING_ICU_NAMESPACE=1 -DU_STATIC_IMPLEMENTATION=1 -DPIC -fPIC"'; + $cxxflags = 'CXXFLAGS="-std=c++17 -DPIC -fPIC -fno-ident"'; + $ldflags = $toolchain->isStatic() ? 'LDFLAGS="-static"' : ''; + shell()->cd($lib->getSourceDir() . '/source')->initializeEnv($lib) + ->exec( + "{$cppflags} {$cxxflags} {$ldflags} " . + './runConfigureICU Linux ' . + '--enable-static ' . + '--disable-shared ' . + '--with-data-packaging=static ' . + '--enable-release=yes ' . + '--enable-extras=no ' . + '--enable-icuio=yes ' . + '--enable-dyload=no ' . + '--enable-tools=yes ' . + '--enable-tests=no ' . + '--enable-samples=no ' . + '--prefix=' . $lib->getBuildRootPath() + ) + ->exec('make clean') + ->exec("make -j{$builder->concurrency}") + ->exec('make install'); + + $lib->patchPkgconfPrefix(patch_option: PKGCONF_PATCH_PREFIX); + FileSystem::removeDir("{$lib->getLibDir()}/icu"); + } + + #[BuildFor('Darwin')] + public function buildDarwin(LibraryPackage $lib, PackageBuilder $builder): void + { + shell()->cd($lib->getSourceDir() . '/source') + ->exec("./runConfigureICU MacOSX --enable-static --disable-shared --disable-extras --disable-samples --disable-tests --prefix={$lib->getBuildRootPath()}") + ->exec('make clean') + ->exec("make -j{$builder->concurrency}") + ->exec('make install'); + + $lib->patchPkgconfPrefix(patch_option: PKGCONF_PATCH_PREFIX); + FileSystem::removeDir("{$lib->getLibDir()}/icu"); + } +} From 2a4959d9736405b245411f6390a3ae1f56f01ccf Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 16:11:16 +0800 Subject: [PATCH 52/99] Chore --- src/StaticPHP/Config/ConfigValidator.php | 2 + src/StaticPHP/Package/PackageBuilder.php | 2 +- .../Package/PackageCallbacksTrait.php | 2 +- src/StaticPHP/Registry/PackageLoader.php | 4 +- src/StaticPHP/Registry/Registry.php | 51 +++++++++++++++++++ src/StaticPHP/Util/DependencyResolver.php | 8 +-- 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/StaticPHP/Config/ConfigValidator.php b/src/StaticPHP/Config/ConfigValidator.php index 1f3d5b9d9..0c3966d1e 100644 --- a/src/StaticPHP/Config/ConfigValidator.php +++ b/src/StaticPHP/Config/ConfigValidator.php @@ -32,6 +32,7 @@ class ConfigValidator 'build-static' => ConfigType::BOOL, 'build-with-php' => ConfigType::BOOL, 'notes' => ConfigType::BOOL, + 'display-name' => ConfigType::STRING, // library and target fields 'headers' => ConfigType::LIST_ARRAY, // @ @@ -75,6 +76,7 @@ class ConfigValidator 'build-static' => false, 'build-with-php' => false, 'notes' => false, + 'display-name' => false, ]; public const array ARTIFACT_TYPE_FIELDS = [ // [required_fields, optional_fields] diff --git a/src/StaticPHP/Package/PackageBuilder.php b/src/StaticPHP/Package/PackageBuilder.php index 42bf5c6f7..5d603625c 100644 --- a/src/StaticPHP/Package/PackageBuilder.php +++ b/src/StaticPHP/Package/PackageBuilder.php @@ -58,7 +58,7 @@ public function buildPackage(Package $package, bool $force = false): int if ($package->getType() !== 'virtual-target') { // patch before build - $package->patchBeforeBuild(); + $package->emitPatchBeforeBuild(); } // build diff --git a/src/StaticPHP/Package/PackageCallbacksTrait.php b/src/StaticPHP/Package/PackageCallbacksTrait.php index 6c6b87382..9556a4b41 100644 --- a/src/StaticPHP/Package/PackageCallbacksTrait.php +++ b/src/StaticPHP/Package/PackageCallbacksTrait.php @@ -53,7 +53,7 @@ public function addPatchBeforeBuildCallback(callable $callback): void $this->patch_before_build_callbacks[] = $callback; } - public function patchBeforeBuild(): void + public function emitPatchBeforeBuild(): void { if (file_exists("{$this->getSourceDir()}/.spc-patched")) { return; diff --git a/src/StaticPHP/Registry/PackageLoader.php b/src/StaticPHP/Registry/PackageLoader.php index 424363eb3..ca195ff0e 100644 --- a/src/StaticPHP/Registry/PackageLoader.php +++ b/src/StaticPHP/Registry/PackageLoader.php @@ -159,7 +159,7 @@ public static function loadFromClass(mixed $class): void } $package_type = PackageConfig::get($attribute_instance->name, 'type'); if ($package_type === null) { - throw new RegistryException("Package [{$attribute_instance->name}] not defined in config, please check your config files."); + throw new RegistryException("Package [{$attribute_instance->name}] not defined in config, but referenced from class {$class}, please check your config files."); } // if class has parent class and matches the attribute instance, use custom class @@ -277,6 +277,8 @@ public static function registerAllDefaultStages(): void foreach (self::$packages as $pkg) { if ($pkg instanceof PhpExtensionPackage) { $pkg->registerDefaultStages(); + } elseif ($pkg instanceof LibraryPackage) { + $pkg->registerDefaultStages(); } } } diff --git a/src/StaticPHP/Registry/Registry.php b/src/StaticPHP/Registry/Registry.php index 68aa766d2..a753c122c 100644 --- a/src/StaticPHP/Registry/Registry.php +++ b/src/StaticPHP/Registry/Registry.php @@ -237,6 +237,9 @@ public static function resolve(): void // check BeforeStage, AfterStage is valid PackageLoader::checkLoadedStageEvents(); + + // Validate package dependencies + self::validatePackageDependencies(); } /** @@ -310,6 +313,54 @@ public static function getCurrentRegistryName(): ?string return self::$current_registry_name; } + /** + * Validate package dependencies to ensure all referenced dependencies exist. + * This helps catch configuration errors early in the registry loading process. + * + * @throws RegistryException + */ + private static function validatePackageDependencies(): void + { + $all_packages = PackageConfig::getAll(); + $errors = []; + + foreach ($all_packages as $pkg_name => $pkg_config) { + // Check depends field + $depends = PackageConfig::get($pkg_name, 'depends', []); + if (!is_array($depends)) { + $errors[] = "Package '{$pkg_name}' has invalid 'depends' field (expected array, got " . gettype($depends) . ')'; + continue; + } + + foreach ($depends as $dep) { + if (!isset($all_packages[$dep])) { + $config_info = self::getPackageConfigInfo($pkg_name); + $location = $config_info ? " (defined in {$config_info['config']})" : ''; + $errors[] = "Package '{$pkg_name}'{$location} depends on '{$dep}' which does not exist in any loaded registry"; + } + } + + // Check suggests field + $suggests = PackageConfig::get($pkg_name, 'suggests', []); + if (!is_array($suggests)) { + $errors[] = "Package '{$pkg_name}' has invalid 'suggests' field (expected array, got " . gettype($suggests) . ')'; + continue; + } + + foreach ($suggests as $suggest) { + if (!isset($all_packages[$suggest])) { + $config_info = self::getPackageConfigInfo($pkg_name); + $location = $config_info ? " (defined in {$config_info['config']})" : ''; + $errors[] = "Package '{$pkg_name}'{$location} suggests '{$suggest}' which does not exist in any loaded registry"; + } + } + } + + if (!empty($errors)) { + throw new RegistryException("Package dependency validation failed:\n - " . implode("\n - ", $errors)); + } + } + /** * Parse a class entry from the classes array. * Supports two formats: diff --git a/src/StaticPHP/Util/DependencyResolver.php b/src/StaticPHP/Util/DependencyResolver.php index b03f80892..2db74abd5 100644 --- a/src/StaticPHP/Util/DependencyResolver.php +++ b/src/StaticPHP/Util/DependencyResolver.php @@ -219,8 +219,8 @@ private static function visitPlatAllDeps(string $pkg_name, array $dep_list, arra return; } $visited[$pkg_name] = true; - // 遍历该依赖的所有依赖(此处的 getLib 如果检测到当前库不存在的话,会抛出异常) - foreach (array_merge($dep_list[$pkg_name]['depends'], $dep_list[$pkg_name]['suggests']) as $dep) { + // 遍历该依赖的所有依赖 + foreach (array_merge($dep_list[$pkg_name]['depends'] ?? [], $dep_list[$pkg_name]['suggests'] ?? []) as $dep) { self::visitPlatAllDeps($dep, $dep_list, $visited, $sorted); } $sorted[] = $pkg_name; @@ -233,11 +233,11 @@ private static function visitPlatDeps(string $pkg_name, array $dep_list, array & return; } $visited[$pkg_name] = true; - // 遍历该依赖的所有依赖(此处的 getLib 如果检测到当前库不存在的话,会抛出异常) + // 遍历该依赖的所有依赖 if (!isset($dep_list[$pkg_name])) { throw new WrongUsageException("{$pkg_name} not exist !"); } - foreach ($dep_list[$pkg_name]['depends'] as $dep) { + foreach ($dep_list[$pkg_name]['depends'] ?? [] as $dep) { self::visitPlatDeps($dep, $dep_list, $visited, $sorted); } $sorted[] = $pkg_name; From 9f2132c0018ac3eeea6e95df05d5f47efe022c22 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 16:11:28 +0800 Subject: [PATCH 53/99] Add pack lib command --- src/StaticPHP/Command/Dev/PackLibCommand.php | 33 +++++ src/StaticPHP/ConsoleApplication.php | 2 + src/StaticPHP/Package/LibraryPackage.php | 140 +++++++++++++++++++ src/StaticPHP/Package/PackageInstaller.php | 12 ++ 4 files changed, 187 insertions(+) create mode 100644 src/StaticPHP/Command/Dev/PackLibCommand.php diff --git a/src/StaticPHP/Command/Dev/PackLibCommand.php b/src/StaticPHP/Command/Dev/PackLibCommand.php new file mode 100644 index 000000000..6f69cb053 --- /dev/null +++ b/src/StaticPHP/Command/Dev/PackLibCommand.php @@ -0,0 +1,33 @@ +addArgument('library', InputArgument::REQUIRED, 'The library will be compiled'); + $this->addOption('show-libc-ver', null, null); + } + + public function handle(): int + { + $library = $this->getArgument('library'); + $show_libc_ver = $this->getOption('show-libc-ver'); + + $installer = new PackageInstaller(['pack-mode' => true]); + $installer->addBuildPackage($library); + + $installer->run(); + + return static::SUCCESS; + } +} diff --git a/src/StaticPHP/ConsoleApplication.php b/src/StaticPHP/ConsoleApplication.php index e65617053..22404b6d3 100644 --- a/src/StaticPHP/ConsoleApplication.php +++ b/src/StaticPHP/ConsoleApplication.php @@ -9,6 +9,7 @@ use StaticPHP\Command\Dev\EnvCommand; use StaticPHP\Command\Dev\IsInstalledCommand; use StaticPHP\Command\Dev\LintConfigCommand; +use StaticPHP\Command\Dev\PackLibCommand; use StaticPHP\Command\Dev\ShellCommand; use StaticPHP\Command\DoctorCommand; use StaticPHP\Command\DownloadCommand; @@ -63,6 +64,7 @@ public function __construct() new IsInstalledCommand(), new EnvCommand(), new LintConfigCommand(), + new PackLibCommand(), ]); // add additional commands from registries diff --git a/src/StaticPHP/Package/LibraryPackage.php b/src/StaticPHP/Package/LibraryPackage.php index 1cdd229e0..8a96f85ed 100644 --- a/src/StaticPHP/Package/LibraryPackage.php +++ b/src/StaticPHP/Package/LibraryPackage.php @@ -5,7 +5,13 @@ namespace StaticPHP\Package; use StaticPHP\Config\PackageConfig; +use StaticPHP\DI\ApplicationContext; use StaticPHP\Exception\PatchException; +use StaticPHP\Exception\SPCInternalException; +use StaticPHP\Exception\ValidationException; +use StaticPHP\Runtime\SystemTarget; +use StaticPHP\Util\DependencyResolver; +use StaticPHP\Util\DirDiff; use StaticPHP\Util\FileSystem; use StaticPHP\Util\SPCConfigUtil; @@ -160,6 +166,114 @@ public function patchPkgconfPrefix(array $files = [], int $patch_option = PKGCON } } + /** + * Register default stages if not already defined by attributes. + * This is called after all attributes have been loaded. + * + * @internal Called by PackageLoader after loading attributes + */ + public function registerDefaultStages(): void + { + if (!$this->hasStage('packPrebuilt')) { + $this->addStage('packPrebuilt', [$this, 'packPrebuilt']); + } + // counting files before build stage + } + + /** + * Pack the prebuilt library into an archive. + * + * @internal this function is intended to be called by the dev:pack-lib command only + */ + public function packPrebuilt(): void + { + $target_dir = WORKING_DIR . '/dist'; + $placeholder_file = BUILD_ROOT_PATH . '/.spc-extract-placeholder.json'; + + if (!ApplicationContext::has(DirDiff::class)) { + throw new SPCInternalException('pack-dirdiff context not found for packPrebuilt stage. You cannot call "packPrebuilt" function manually.'); + } + // check whether this library has correctly installed files + if (!$this->isInstalled()) { + throw new ValidationException("Cannot pack prebuilt library [{$this->getName()}] because it is not fully installed."); + } + // get after-build buildroot file list + $increase_files = ApplicationContext::get(DirDiff::class)->getIncrementFiles(true); + + FileSystem::createDir($target_dir); + + // before pack, check if the dependency tree contains lib-suggests + $libraries = DependencyResolver::resolve([$this], include_suggests: true); + foreach ($libraries as $lib) { + if (PackageConfig::get($lib, 'suggests', []) !== []) { + throw new ValidationException("The library {$lib} has lib-suggests, packing [{$this->name}] is not safe, abort !"); + } + } + + $origin_files = []; + + // get pack placehoder defines + $placehoder = get_pack_replace(); + + // patch pkg-config and la files with absolute path + foreach ($increase_files as $file) { + if (str_ends_with($file, '.pc') || str_ends_with($file, '.la')) { + $content = FileSystem::readFile(BUILD_ROOT_PATH . '/' . $file); + $origin_files[$file] = $content; + // replace relative paths with absolute paths + $content = str_replace( + array_keys($placehoder), + array_values($placehoder), + $content + ); + FileSystem::writeFile(BUILD_ROOT_PATH . '/' . $file, $content); + } + } + + // add .spc-extract-placeholder.json in BUILD_ROOT_PATH + file_put_contents($placeholder_file, json_encode(array_keys($origin_files), JSON_PRETTY_PRINT)); + $increase_files[] = '.spc-extract-placeholder.json'; + + // every file mapped with BUILD_ROOT_PATH + // get BUILD_ROOT_PATH last dir part + $buildroot_part = basename(BUILD_ROOT_PATH); + $increase_files = array_map(fn ($file) => $buildroot_part . '/' . $file, $increase_files); + // write list to packlib_files.txt + FileSystem::writeFile(WORKING_DIR . '/packlib_files.txt', implode("\n", $increase_files)); + // pack + $filename = match (SystemTarget::getTargetOS()) { + 'Windows' => '{name}-{arch}-{os}.tgz', + 'Darwin' => '{name}-{arch}-{os}.txz', + 'Linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz', + }; + $replace = [ + '{name}' => $this->getName(), + '{arch}' => arch2gnu(php_uname('m')), + '{os}' => strtolower(PHP_OS_FAMILY), + '{libc}' => SystemTarget::getLibc() ?? 'default', + '{libcver}' => SystemTarget::getLibcVersion() ?? 'default', + ]; + // detect suffix, for proper tar option + $tar_option = $this->getTarOptionFromSuffix($filename); + $filename = str_replace(array_keys($replace), array_values($replace), $filename); + $filename = $target_dir . '/' . $filename; + f_passthru("tar {$tar_option} {$filename} -T " . WORKING_DIR . '/packlib_files.txt'); + logger()->info('Pack library ' . $this->getName() . ' to ' . $filename . ' complete.'); + + // remove temp files + unlink($placeholder_file); + + foreach ($origin_files as $file => $content) { + // restore original files + if (file_exists(BUILD_ROOT_PATH . '/' . $file)) { + FileSystem::writeFile(BUILD_ROOT_PATH . '/' . $file, $content); + } + } + + // remove dirdiff + ApplicationContext::set(DirDiff::class, null); + } + /** * Get static library files for current package and its dependencies. */ @@ -215,4 +329,30 @@ public function getBinDir(): string { return BUILD_BIN_PATH; } + + /** + * Get tar compress options from suffix + * + * @param string $name Package file name + * @return string Tar options for packaging libs + */ + private function getTarOptionFromSuffix(string $name): string + { + if (str_ends_with($name, '.tar')) { + return '-cf'; + } + if (str_ends_with($name, '.tar.gz') || str_ends_with($name, '.tgz')) { + return '-czf'; + } + if (str_ends_with($name, '.tar.bz2') || str_ends_with($name, '.tbz2')) { + return '-cjf'; + } + if (str_ends_with($name, '.tar.xz') || str_ends_with($name, '.txz')) { + return '-cJf'; + } + if (str_ends_with($name, '.tar.lz') || str_ends_with($name, '.tlz')) { + return '-c --lzma -f'; + } + return '-cf'; + } } diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index b9a3debb7..a01d29fcd 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -14,6 +14,7 @@ use StaticPHP\Registry\PackageLoader; use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\DependencyResolver; +use StaticPHP\Util\DirDiff; use StaticPHP\Util\FileSystem; use StaticPHP\Util\GlobalEnvManager; use StaticPHP\Util\InteractiveTerm; @@ -71,6 +72,9 @@ public function addBuildPackage(LibraryPackage|string|TargetPackage $package): s if (!$package->hasStage('build')) { throw new WrongUsageException("Target package '{$package->getName()}' does not define build process for current OS: " . PHP_OS_FAMILY . '.'); } + if (($this->options['pack-mode'] ?? false) === true && !empty($this->build_packages)) { + throw new WrongUsageException("In 'pack-mode', only one package can be built at a time. Cannot add package '{$package->getName()}' to build list."); + } $this->build_packages[$package->getName()] = $package; return $this; } @@ -195,8 +199,16 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): InteractiveTerm::indicateProgress('Building package: ' . ConsoleColor::yellow($package->getName())); } try { + if ($is_to_build && ($this->options['pack-mode'] ?? false) === true) { + $dirdiff = new DirDiff(BUILD_ROOT_PATH, false); + ApplicationContext::set(DirDiff::class, $dirdiff); + } /** @var LibraryPackage $package */ $status = $builder->buildPackage($package, $this->isBuildPackage($package)); + + if ($is_to_build && ($this->options['pack-mode'] ?? false) === true) { + $package->runStage('packPrebuilt'); + } } catch (\Throwable $e) { if ($interactive) { InteractiveTerm::finish('Building package failed: ' . ConsoleColor::red($package->getName()), false); From 81ce777bf2f8a9c00173bf9fc871d3defb29cd7a Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 16:30:58 +0800 Subject: [PATCH 54/99] phpstan fix --- src/StaticPHP/Package/LibraryPackage.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/StaticPHP/Package/LibraryPackage.php b/src/StaticPHP/Package/LibraryPackage.php index 8a96f85ed..610e3550d 100644 --- a/src/StaticPHP/Package/LibraryPackage.php +++ b/src/StaticPHP/Package/LibraryPackage.php @@ -9,6 +9,7 @@ use StaticPHP\Exception\PatchException; use StaticPHP\Exception\SPCInternalException; use StaticPHP\Exception\ValidationException; +use StaticPHP\Exception\WrongUsageException; use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\DependencyResolver; use StaticPHP\Util\DirDiff; @@ -245,6 +246,7 @@ public function packPrebuilt(): void 'Windows' => '{name}-{arch}-{os}.tgz', 'Darwin' => '{name}-{arch}-{os}.txz', 'Linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz', + default => throw new WrongUsageException('Unsupported OS for packing prebuilt library: ' . SystemTarget::getTargetOS()), }; $replace = [ '{name}' => $this->getName(), From 0d32b7bfdbb967da427d39438ae4b8795b689d68 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 16:42:49 +0800 Subject: [PATCH 55/99] Refactor lib packing to v3 postinstall action --- src/StaticPHP/Package/LibraryPackage.php | 39 ++++++++++++++++-------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/StaticPHP/Package/LibraryPackage.php b/src/StaticPHP/Package/LibraryPackage.php index 610e3550d..0567f92b8 100644 --- a/src/StaticPHP/Package/LibraryPackage.php +++ b/src/StaticPHP/Package/LibraryPackage.php @@ -189,7 +189,7 @@ public function registerDefaultStages(): void public function packPrebuilt(): void { $target_dir = WORKING_DIR . '/dist'; - $placeholder_file = BUILD_ROOT_PATH . '/.spc-extract-placeholder.json'; + $postinstall_file = BUILD_ROOT_PATH . '/.package.' . $this->getName() . '.postinstall.json'; if (!ApplicationContext::has(DirDiff::class)) { throw new SPCInternalException('pack-dirdiff context not found for packPrebuilt stage. You cannot call "packPrebuilt" function manually.'); @@ -212,28 +212,39 @@ public function packPrebuilt(): void } $origin_files = []; + $postinstall_files = []; - // get pack placehoder defines - $placehoder = get_pack_replace(); + // get pack placeholder defines + $placeholder = get_pack_replace(); - // patch pkg-config and la files with absolute path + // patch pkg-config and la files with placeholder paths foreach ($increase_files as $file) { if (str_ends_with($file, '.pc') || str_ends_with($file, '.la')) { $content = FileSystem::readFile(BUILD_ROOT_PATH . '/' . $file); $origin_files[$file] = $content; - // replace relative paths with absolute paths + // replace actual paths with placeholders $content = str_replace( - array_keys($placehoder), - array_values($placehoder), + array_keys($placeholder), + array_values($placeholder), $content ); FileSystem::writeFile(BUILD_ROOT_PATH . '/' . $file, $content); + // record files that need postinstall path replacement + $postinstall_files[] = $file; } } - // add .spc-extract-placeholder.json in BUILD_ROOT_PATH - file_put_contents($placeholder_file, json_encode(array_keys($origin_files), JSON_PRETTY_PRINT)); - $increase_files[] = '.spc-extract-placeholder.json'; + // generate postinstall action file if there are files to process + if ($postinstall_files !== []) { + $postinstall_actions = [ + [ + 'action' => 'replace-path', + 'files' => $postinstall_files, + ], + ]; + FileSystem::writeFile($postinstall_file, json_encode($postinstall_actions, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + $increase_files[] = '.package.' . $this->getName() . '.postinstall.json'; + } // every file mapped with BUILD_ROOT_PATH // get BUILD_ROOT_PATH last dir part @@ -262,11 +273,13 @@ public function packPrebuilt(): void f_passthru("tar {$tar_option} {$filename} -T " . WORKING_DIR . '/packlib_files.txt'); logger()->info('Pack library ' . $this->getName() . ' to ' . $filename . ' complete.'); - // remove temp files - unlink($placeholder_file); + // remove postinstall temp file + if (file_exists($postinstall_file)) { + unlink($postinstall_file); + } + // restore original files foreach ($origin_files as $file => $content) { - // restore original files if (file_exists(BUILD_ROOT_PATH . '/' . $file)) { FileSystem::writeFile(BUILD_ROOT_PATH . '/' . $file, $content); } From b3bbe0a75146a7d65ae2ae64ca8d0f98451cb46e Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 19:19:09 +0800 Subject: [PATCH 56/99] Add libjpeg,libpng --- config/pkg/lib/libjpeg.yml | 12 +++++++++ config/pkg/lib/libpng.yml | 16 +++++++++++ src/Package/Library/libjpeg.php | 28 ++++++++++++++++++++ src/Package/Library/libpng.php | 47 +++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 config/pkg/lib/libjpeg.yml create mode 100644 config/pkg/lib/libpng.yml create mode 100644 src/Package/Library/libjpeg.php create mode 100644 src/Package/Library/libpng.php diff --git a/config/pkg/lib/libjpeg.yml b/config/pkg/lib/libjpeg.yml new file mode 100644 index 000000000..9171ad388 --- /dev/null +++ b/config/pkg/lib/libjpeg.yml @@ -0,0 +1,12 @@ +libjpeg: + type: library + artifact: + source: + type: ghtar + repo: libjpeg-turbo/libjpeg-turbo + metadata: + license-files: [LICENSE.md] + license: IJG + static-libs@unix: + - libjpeg.a + - libturbojpeg.a diff --git a/config/pkg/lib/libpng.yml b/config/pkg/lib/libpng.yml new file mode 100644 index 000000000..e8831d60c --- /dev/null +++ b/config/pkg/lib/libpng.yml @@ -0,0 +1,16 @@ +libpng: + type: library + artifact: + source: + type: ghtagtar + repo: pnggroup/libpng + match: v1\.6\.\d+ + query: '?per_page=150' + binary: hosted + metadata: + license-files: [LICENSE] + license: PNG + depends: + - zlib + static-libs@unix: + - libpng16.a diff --git a/src/Package/Library/libjpeg.php b/src/Package/Library/libjpeg.php new file mode 100644 index 000000000..04f4fd254 --- /dev/null +++ b/src/Package/Library/libjpeg.php @@ -0,0 +1,28 @@ +addConfigureArgs( + '-DENABLE_STATIC=ON', + '-DENABLE_SHARED=OFF', + ) + ->build(); + // patch pkgconfig + $lib->patchPkgconfPrefix(['libjpeg.pc', 'libturbojpeg.pc']); + } +} diff --git a/src/Package/Library/libpng.php b/src/Package/Library/libpng.php new file mode 100644 index 000000000..1d02fdd69 --- /dev/null +++ b/src/Package/Library/libpng.php @@ -0,0 +1,47 @@ +getBuildRootPath()}", + ]; + + // Enable architecture-specific optimizations + match (getenv('SPC_ARCH')) { + 'x86_64' => $args[] = '--enable-intel-sse', + 'aarch64' => $args[] = '--enable-arm-neon', + default => null, + }; + + UnixAutoconfExecutor::create($lib) + ->exec('chmod +x ./configure') + ->exec('chmod +x ./install-sh') + ->appendEnv(['LDFLAGS' => "-L{$lib->getLibDir()}"]) + ->addConfigureArgs(...$args) + ->configure() + ->make( + 'libpng16.la', + 'install-libLTLIBRARIES install-data-am', + after_env_vars: ['DEFAULT_INCLUDES' => "-I{$lib->getSourceDir()} -I{$lib->getIncludeDir()}"] + ); + + // patch pkgconfig + $lib->patchPkgconfPrefix(['libpng16.pc']); + $lib->patchLaDependencyPrefix(); + } +} From 8fc2da9acff46a6e495b5a98124b566315619112 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 19:19:26 +0800 Subject: [PATCH 57/99] Use OS release definition for openssl --- src/Package/Library/openssl.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Package/Library/openssl.php b/src/Package/Library/openssl.php index e0ee9edc3..541b6145f 100644 --- a/src/Package/Library/openssl.php +++ b/src/Package/Library/openssl.php @@ -8,6 +8,7 @@ use StaticPHP\Attribute\Package\Library; use StaticPHP\Package\LibraryPackage; use StaticPHP\Util\FileSystem; +use StaticPHP\Util\System\LinuxUtil; #[Library('openssl')] class openssl @@ -48,8 +49,7 @@ public function build(LibraryPackage $lib): void '--with-zlib-lib=' . BUILD_LIB_PATH . ' '; $openssl_dir = getenv('OPENSSLDIR') ?: null; - // TODO: in v3 use the following: $openssl_dir ??= SystemUtil::getOSRelease()['dist'] === 'redhat' ? '/etc/pki/tls' : '/etc/ssl'; - $openssl_dir ??= '/etc/ssl'; + $openssl_dir ??= LinuxUtil::getOSRelease()['dist'] === 'redhat' ? '/etc/pki/tls' : '/etc/ssl'; $ex_lib = trim($ex_lib); shell()->cd($lib->getSourceDir())->initializeEnv($lib) From 97634b009f9c81e40d2fbd08c486192d5b007934 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 19:21:13 +0800 Subject: [PATCH 58/99] Forward-port #1006 changes --- src/Package/Artifact/zig.php | 6 +++-- .../Downloader/Type/GitHubRelease.php | 8 +++--- .../Downloader/Type/GitHubTarball.php | 10 ++++--- src/StaticPHP/Config/ConfigValidator.php | 6 ++--- src/StaticPHP/Toolchain/ZigToolchain.php | 3 ++- src/StaticPHP/Util/FileSystem.php | 12 ++++----- src/StaticPHP/Util/SPCConfigUtil.php | 26 ++++++++++++++++--- 7 files changed, 49 insertions(+), 22 deletions(-) diff --git a/src/Package/Artifact/zig.php b/src/Package/Artifact/zig.php index 2ac7b454b..9a1430637 100644 --- a/src/Package/Artifact/zig.php +++ b/src/Package/Artifact/zig.php @@ -25,8 +25,10 @@ public function downBinary(ArtifactDownloader $downloader): DownloadResult $index_json = json_decode($index_json ?: '', true); $latest_version = null; foreach ($index_json as $version => $data) { - $latest_version = $version; - break; + if ($version !== 'master') { + $latest_version = $version; + break; + } } if (!$latest_version) { diff --git a/src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php b/src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php index 731e8297e..7b0412886 100644 --- a/src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php +++ b/src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php @@ -21,10 +21,11 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface private ?string $version = null; - public function getGitHubReleases(string $name, string $repo, bool $prefer_stable = true): array + public function getGitHubReleases(string $name, string $repo, bool $prefer_stable = true, ?string $query = null): array { logger()->debug("Fetching {$name} GitHub releases from {$repo}"); $url = str_replace('{repo}', $repo, self::API_URL); + $url .= ($query ?? ''); $headers = $this->getGitHubTokenHeaders(); $data2 = default_shell()->executeCurl($url, headers: $headers); $data = json_decode($data2 ?: '', true); @@ -45,9 +46,10 @@ public function getGitHubReleases(string $name, string $repo, bool $prefer_stabl * Get the latest GitHub release assets for a given repository. * match_asset is provided, only return the asset that matches the regex. */ - public function getLatestGitHubRelease(string $name, string $repo, bool $prefer_stable, string $match_asset): array + public function getLatestGitHubRelease(string $name, string $repo, bool $prefer_stable, string $match_asset, ?string $query = null): array { $url = str_replace('{repo}', $repo, self::API_URL); + $url .= ($query ?? ''); $headers = $this->getGitHubTokenHeaders(); $data2 = default_shell()->executeCurl($url, headers: $headers); $data = json_decode($data2 ?: '', true); @@ -81,7 +83,7 @@ public function download(string $name, array $config, ArtifactDownloader $downlo if (!isset($config['match'])) { throw new DownloaderException("GitHubRelease downloader requires 'match' config for {$name}"); } - $rel = $this->getLatestGitHubRelease($name, $config['repo'], $config['prefer-stable'] ?? true, $config['match']); + $rel = $this->getLatestGitHubRelease($name, $config['repo'], $config['prefer-stable'] ?? true, $config['match'], $config['query'] ?? null); // download file using curl $asset_url = str_replace(['{repo}', '{id}'], [$config['repo'], $rel['id']], self::ASSET_URL); diff --git a/src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php b/src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php index 7917e4c01..8aa1ac694 100644 --- a/src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php +++ b/src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php @@ -23,9 +23,10 @@ class GitHubTarball implements DownloadTypeInterface * If match_url is provided, only return the tarball that matches the regex. * Otherwise, return the first tarball found. */ - public function getGitHubTarballInfo(string $name, string $repo, string $rel_type, bool $prefer_stable = true, ?string $match_url = null, ?string $basename = null): array + public function getGitHubTarballInfo(string $name, string $repo, string $rel_type, bool $prefer_stable = true, ?string $match_url = null, ?string $basename = null, ?string $query = null): array { $url = str_replace(['{repo}', '{rel_type}'], [$repo, $rel_type], self::API_URL); + $url .= ($query ?? ''); $data = default_shell()->executeCurl($url, headers: $this->getGitHubTokenHeaders()); $data = json_decode($data ?: '', true); if (!is_array($data)) { @@ -33,7 +34,10 @@ public function getGitHubTarballInfo(string $name, string $repo, string $rel_typ } $url = null; foreach ($data as $rel) { - if (($rel['prerelease'] ?? false) === true && $prefer_stable) { + $prerelease = $rel['prerelease'] ?? false; + $draft = $rel['draft'] ?? false; + $tarball_url = $rel['tarball_url'] ?? null; + if ($prerelease && $prefer_stable || $draft && $prefer_stable || !$tarball_url) { continue; } if ($match_url === null) { @@ -70,7 +74,7 @@ public function download(string $name, array $config, ArtifactDownloader $downlo 'ghtagtar' => 'tags', default => throw new DownloaderException("Invalid GitHubTarball type for {$name}"), }; - [$url, $filename] = $this->getGitHubTarballInfo($name, $config['repo'], $rel_type, $config['prefer-stable'] ?? true, $config['match'] ?? null, $name); + [$url, $filename] = $this->getGitHubTarballInfo($name, $config['repo'], $rel_type, $config['prefer-stable'] ?? true, $config['match'] ?? null, $name, $config['query'] ?? null); $path = DOWNLOAD_PATH . "/{$filename}"; default_shell()->executeCurlDownload($url, $path, headers: $this->getGitHubTokenHeaders()); return DownloadResult::archive($filename, $config, $config['extract'] ?? null, version: $this->version); diff --git a/src/StaticPHP/Config/ConfigValidator.php b/src/StaticPHP/Config/ConfigValidator.php index 0c3966d1e..fbf883213 100644 --- a/src/StaticPHP/Config/ConfigValidator.php +++ b/src/StaticPHP/Config/ConfigValidator.php @@ -82,9 +82,9 @@ class ConfigValidator public const array ARTIFACT_TYPE_FIELDS = [ // [required_fields, optional_fields] 'filelist' => [['url', 'regex'], ['extract']], 'git' => [['url'], ['extract', 'submodules', 'rev', 'regex']], - 'ghtagtar' => [['repo'], ['extract', 'prefer-stable', 'match']], - 'ghtar' => [['repo'], ['extract', 'prefer-stable', 'match']], - 'ghrel' => [['repo', 'match'], ['extract', 'prefer-stable']], + 'ghtagtar' => [['repo'], ['extract', 'prefer-stable', 'match', 'query']], + 'ghtar' => [['repo'], ['extract', 'prefer-stable', 'match', 'query']], + 'ghrel' => [['repo', 'match'], ['extract', 'prefer-stable', 'query']], 'url' => [['url'], ['filename', 'extract', 'version']], 'bitbuckettag' => [['repo'], ['extract']], 'local' => [['dirname'], ['extract']], diff --git a/src/StaticPHP/Toolchain/ZigToolchain.php b/src/StaticPHP/Toolchain/ZigToolchain.php index 2d7c71b0c..344ce3e9c 100644 --- a/src/StaticPHP/Toolchain/ZigToolchain.php +++ b/src/StaticPHP/Toolchain/ZigToolchain.php @@ -64,7 +64,8 @@ public function afterInit(): void $cflags = getenv('SPC_DEFAULT_C_FLAGS') ?: getenv('CFLAGS') ?: ''; $has_avx512 = str_contains($cflags, '-mavx512') || str_contains($cflags, '-march=x86-64-v4'); if (!$has_avx512) { - GlobalEnvManager::putenv('SPC_EXTRA_PHP_VARS=php_cv_have_avx512=no php_cv_have_avx512vbmi=no'); + $extra_vars = getenv('SPC_EXTRA_PHP_VARS') ?: ''; + GlobalEnvManager::putenv("SPC_EXTRA_PHP_VARS=php_cv_have_avx512=no php_cv_have_avx512vbmi=no {$extra_vars}"); } } diff --git a/src/StaticPHP/Util/FileSystem.php b/src/StaticPHP/Util/FileSystem.php index 1c21a92b5..c8da5353d 100644 --- a/src/StaticPHP/Util/FileSystem.php +++ b/src/StaticPHP/Util/FileSystem.php @@ -314,12 +314,7 @@ public static function removeDir(string $dir): bool continue; } $sub_file = self::convertPath($dir . '/' . $v); - if (is_dir($sub_file)) { - # 如果是 目录 且 递推 , 则递推添加下级文件 - if (!self::removeDir($sub_file)) { - return false; - } - } elseif (is_link($sub_file) || is_file($sub_file)) { + if (is_link($sub_file) || is_file($sub_file)) { if (!unlink($sub_file)) { $cmd = PHP_OS_FAMILY === 'Windows' ? 'del /f /q' : 'rm -f'; f_exec("{$cmd} " . escapeshellarg($sub_file), $out, $ret); @@ -328,6 +323,11 @@ public static function removeDir(string $dir): bool return false; } } + } elseif (is_dir($sub_file)) { + # 如果是 目录 且 递推 , 则递推添加下级文件 + if (!self::removeDir($sub_file)) { + return false; + } } } if (is_link($dir)) { diff --git a/src/StaticPHP/Util/SPCConfigUtil.php b/src/StaticPHP/Util/SPCConfigUtil.php index a31b771f8..32ef3bc64 100644 --- a/src/StaticPHP/Util/SPCConfigUtil.php +++ b/src/StaticPHP/Util/SPCConfigUtil.php @@ -291,9 +291,18 @@ private function getIncludesString(array $packages): string // parse pkg-configs foreach ($packages as $package) { $pc = PackageConfig::get($package, 'pkg-configs', []); + $pkg_config_path = getenv('PKG_CONFIG_PATH') ?: ''; + $search_paths = array_filter(explode(SystemTarget::isUnix() ? ':' : ';', $pkg_config_path)); foreach ($pc as $file) { - if (!file_exists(BUILD_LIB_PATH . "/pkgconfig/{$file}.pc")) { - throw new WrongUsageException("pkg-config file '{$file}.pc' for lib [{$package}] does not exist in '" . BUILD_LIB_PATH . "/pkgconfig'. Please build it first."); + $found = false; + foreach ($search_paths as $path) { + if (file_exists($path . "/{$file}.pc")) { + $found = true; + break; + } + } + if (!$found) { + throw new WrongUsageException("pkg-config file '{$file}.pc' for lib [{$package}] does not exist. Please build it first."); } } $pc_cflags = implode(' ', $pc); @@ -324,9 +333,18 @@ private function getLibsString(array $packages, bool $use_short_libs = true): st if (SystemTarget::isUnix()) { // add pkg-configs libs $pkg_configs = PackageConfig::get($package, 'pkg-configs', []); + $pkg_config_path = getenv('PKG_CONFIG_PATH') ?: ''; + $search_paths = array_filter(explode(':', $pkg_config_path)); foreach ($pkg_configs as $pkg_config) { - if (!file_exists(BUILD_LIB_PATH . "/pkgconfig/{$pkg_config}.pc")) { - throw new WrongUsageException("pkg-config file '{$pkg_config}.pc' for lib [{$package}] does not exist in '" . BUILD_LIB_PATH . "/pkgconfig'. Please build it first."); + $found = false; + foreach ($search_paths as $path) { + if (file_exists($path . "/{$pkg_config}.pc")) { + $found = true; + break; + } + } + if (!$found) { + throw new WrongUsageException("pkg-config file '{$pkg_config}.pc' for lib [{$package}] does not exist. Please build it first."); } } $pkg_configs = implode(' ', $pkg_configs); From a75060e5f6f4478660b71446cd81a1a881fa4747 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 20:56:25 +0800 Subject: [PATCH 59/99] Update exit code in ArtifactDownloader to reflect termination signal --- src/StaticPHP/Artifact/ArtifactDownloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StaticPHP/Artifact/ArtifactDownloader.php b/src/StaticPHP/Artifact/ArtifactDownloader.php index 27cd6adc5..b0cbfeb83 100644 --- a/src/StaticPHP/Artifact/ArtifactDownloader.php +++ b/src/StaticPHP/Artifact/ArtifactDownloader.php @@ -294,7 +294,7 @@ public function download(bool $interactive = true): void FileSystem::removeFileIfExists($path); } } - exit(2); + exit(130); }); } From a07265787b5fc9f9f560bca538af043178aaf022 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 20:56:34 +0800 Subject: [PATCH 60/99] Update license file path for bzip2 in configuration --- config/pkg/lib/bzip2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/pkg/lib/bzip2.yml b/config/pkg/lib/bzip2.yml index 132a24ffa..1cd36bd7e 100644 --- a/config/pkg/lib/bzip2.yml +++ b/config/pkg/lib/bzip2.yml @@ -10,7 +10,7 @@ bzip2: regex: '/href="(?bzip2-(?[^"]+)\.tar\.gz)"/' binary: hosted metadata: - license-files: ['{registry_root}/src/globals/licenses/bzip2.txt'] + license-files: ['@/bzip2.txt'] license: bzip2-1.0.6 headers: - bzlib.h From 807b90b1828cc70acee439713c61321e6f2b8977 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 20:56:50 +0800 Subject: [PATCH 61/99] Fix incorrect variable name for working directory in submodule update command --- src/StaticPHP/Runtime/Shell/DefaultShell.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StaticPHP/Runtime/Shell/DefaultShell.php b/src/StaticPHP/Runtime/Shell/DefaultShell.php index a6421bdb9..5b50d1528 100644 --- a/src/StaticPHP/Runtime/Shell/DefaultShell.php +++ b/src/StaticPHP/Runtime/Shell/DefaultShell.php @@ -104,7 +104,7 @@ public function executeGitClone(string $url, string $branch, string $path, bool $submodule_cmd = clean_spaces("{$git} submodule update --init {$depth_flag} {$submodule}"); $this->logCommandInfo($submodule_cmd); logger()->debug("[GIT SUBMODULE] {$submodule_cmd}"); - $this->passthru($submodule_cmd, $this->console_putput, cwd: $path_arg); + $this->passthru($submodule_cmd, $this->console_putput, cwd: $path); } } } From 7ae16e5be8a440c6476f52b562e470935d5d5fa7 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 5 Feb 2026 20:59:23 +0800 Subject: [PATCH 62/99] Add imagemagick,jbig,lerc,libaom,libde265,libheif,libjxl,libtiff,libwebp,libzip --- config/pkg/lib/imagemagick.yml | 32 +++++++++++ config/pkg/lib/jbig.yml | 15 ++++++ config/pkg/lib/lerc.yml | 12 +++++ config/pkg/lib/libaom.yml | 12 +++++ config/pkg/lib/libde265.yml | 13 +++++ config/pkg/lib/libheif.yml | 18 +++++++ config/pkg/lib/libjxl.yml | 21 ++++++++ config/pkg/lib/libtiff.yml | 21 ++++++++ config/pkg/lib/libwebp.yml | 16 ++++++ config/pkg/lib/libzip.yml | 23 ++++++++ src/Package/Library/imagemagick.php | 83 +++++++++++++++++++++++++++++ src/Package/Library/jbig.php | 46 ++++++++++++++++ src/Package/Library/lerc.php | 21 ++++++++ src/Package/Library/libaom.php | 33 ++++++++++++ src/Package/Library/libde265.php | 24 +++++++++ src/Package/Library/libheif.php | 45 ++++++++++++++++ src/Package/Library/libjxl.php | 52 ++++++++++++++++++ src/Package/Library/libtiff.php | 50 +++++++++++++++++ src/Package/Library/libwebp.php | 44 +++++++++++++++ src/Package/Library/libzip.php | 36 +++++++++++++ 20 files changed, 617 insertions(+) create mode 100644 config/pkg/lib/imagemagick.yml create mode 100644 config/pkg/lib/jbig.yml create mode 100644 config/pkg/lib/lerc.yml create mode 100644 config/pkg/lib/libaom.yml create mode 100644 config/pkg/lib/libde265.yml create mode 100644 config/pkg/lib/libheif.yml create mode 100644 config/pkg/lib/libjxl.yml create mode 100644 config/pkg/lib/libtiff.yml create mode 100644 config/pkg/lib/libwebp.yml create mode 100644 config/pkg/lib/libzip.yml create mode 100644 src/Package/Library/imagemagick.php create mode 100644 src/Package/Library/jbig.php create mode 100644 src/Package/Library/lerc.php create mode 100644 src/Package/Library/libaom.php create mode 100644 src/Package/Library/libde265.php create mode 100644 src/Package/Library/libheif.php create mode 100644 src/Package/Library/libjxl.php create mode 100644 src/Package/Library/libtiff.php create mode 100644 src/Package/Library/libwebp.php create mode 100644 src/Package/Library/libzip.php diff --git a/config/pkg/lib/imagemagick.yml b/config/pkg/lib/imagemagick.yml new file mode 100644 index 000000000..17fc03e81 --- /dev/null +++ b/config/pkg/lib/imagemagick.yml @@ -0,0 +1,32 @@ +imagemagick: + type: library + artifact: + source: { + "type": "ghtar", + "repo": "ImageMagick/ImageMagick" + } + metadata: + license-files: [LICENSE] + lang: cpp + pkg-configs: [ + "Magick++-7.Q16HDRI", + "MagickCore-7.Q16HDRI", + "MagickWand-7.Q16HDRI" + ] + depends: + - zlib + - libjpeg + - libjxl + - libpng + - libwebp + - freetype + - libtiff + - libheif + - bzip2 + suggests: + - zstd + - xz + - libzip + - libxml2 + + diff --git a/config/pkg/lib/jbig.yml b/config/pkg/lib/jbig.yml new file mode 100644 index 000000000..f96ecf461 --- /dev/null +++ b/config/pkg/lib/jbig.yml @@ -0,0 +1,15 @@ +jbig: + type: library + artifact: + source: 'https://dl.static-php.dev/static-php-cli/deps/jbig/jbigkit-2.1.tar.gz' + source-mirror: 'https://www.cl.cam.ac.uk/~mgk25/jbigkit/download/jbigkit-2.1.tar.gz' + metadata: + license-files: [COPYING] + license: GPL-2.0-or-later + headers: + - jbig.h + - jbig85.h + - jbig_ar.h + static-libs@unix: + - libjbig.a + - libjbig85.a diff --git a/config/pkg/lib/lerc.yml b/config/pkg/lib/lerc.yml new file mode 100644 index 000000000..330ca7952 --- /dev/null +++ b/config/pkg/lib/lerc.yml @@ -0,0 +1,12 @@ +lerc: + type: library + artifact: + source: + type: ghtar + repo: Esri/lerc + prefer-stable: true + metadata: + license-files: [LICENSE] + lang: cpp + static-libs@unix: + - libLerc.a diff --git a/config/pkg/lib/libaom.yml b/config/pkg/lib/libaom.yml new file mode 100644 index 000000000..6a2dbe3cb --- /dev/null +++ b/config/pkg/lib/libaom.yml @@ -0,0 +1,12 @@ +libaom: + type: library + artifact: + source: + type: git + rev: main + url: 'https://aomedia.googlesource.com/aom' + metadata: + license-files: [LICENSE] + lang: cpp + static-libs@unix: + - libaom.a diff --git a/config/pkg/lib/libde265.yml b/config/pkg/lib/libde265.yml new file mode 100644 index 000000000..679c875a4 --- /dev/null +++ b/config/pkg/lib/libde265.yml @@ -0,0 +1,13 @@ +libde265: + type: library + artifact: + source: + type: ghrel + repo: strukturag/libde265 + match: libde265-.+\.tar\.gz + prefer-stable: true + metadata: + license-files: [COPYING] + lang: cpp + static-libs@unix: + - libde265.a diff --git a/config/pkg/lib/libheif.yml b/config/pkg/lib/libheif.yml new file mode 100644 index 000000000..4265f2c2e --- /dev/null +++ b/config/pkg/lib/libheif.yml @@ -0,0 +1,18 @@ +libheif: + type: library + artifact: + source: + type: ghrel + repo: strukturag/libheif + match: libheif-.+\.tar\.gz + prefer-stable: true + metadata: + license-files: [COPYING] + depends: + - libde265 + - libwebp + - libaom + - zlib + - brotli + static-libs@unix: + - libheif.a diff --git a/config/pkg/lib/libjxl.yml b/config/pkg/lib/libjxl.yml new file mode 100644 index 000000000..f2f3d5516 --- /dev/null +++ b/config/pkg/lib/libjxl.yml @@ -0,0 +1,21 @@ +libjxl: + type: library + artifact: + source: + type: git + url: 'https://github.com/libjxl/libjxl' + rev: main + submodules: [third_party/highway, third_party/libjpeg-turbo, third_party/sjpeg, third_party/skcms] + metadata: + license-files: [LICENSE] + license: BSD-3-Clause + depends: + - brotli + - libjpeg + - libpng + - libwebp + pkg-configs: + - libjxl + - libjxl_cms + - libjxl_threads + - libhwy diff --git a/config/pkg/lib/libtiff.yml b/config/pkg/lib/libtiff.yml new file mode 100644 index 000000000..e3f345f13 --- /dev/null +++ b/config/pkg/lib/libtiff.yml @@ -0,0 +1,21 @@ +libtiff: + type: library + artifact: + source: + type: filelist + url: 'https://download.osgeo.org/libtiff/' + regex: '/href="(?tiff-(?[^"]+)\.tar\.xz)"/' + metadata: + license-files: [LICENSE.md] + license: libtiff + depends: + - zlib + - libjpeg + suggests@unix: + - lerc + - libwebp + - jbig + - xz + - zstd + static-libs@unix: + - libtiff.a diff --git a/config/pkg/lib/libwebp.yml b/config/pkg/lib/libwebp.yml new file mode 100644 index 000000000..62ddddc13 --- /dev/null +++ b/config/pkg/lib/libwebp.yml @@ -0,0 +1,16 @@ +libwebp: + type: library + artifact: + source: + type: ghtagtar + repo: webmproject/libwebp + match: v1\.\d+\.\d+$ + metadata: + license-files: [COPYING] + license: BSD-3-Clause + pkg-configs: + - libwebp + - libwebpdecoder + - libwebpdemux + - libwebpmux + - libsharpyuv diff --git a/config/pkg/lib/libzip.yml b/config/pkg/lib/libzip.yml new file mode 100644 index 000000000..3d8c02f8f --- /dev/null +++ b/config/pkg/lib/libzip.yml @@ -0,0 +1,23 @@ +libzip: + type: library + artifact: + source: { + "type": "ghrel", + "repo": "nih-at/libzip", + "match": "libzip.+\\.tar\\.xz", + "prefer-stable": true + } + metadata: + license-files: [LICENSE] + static-libs@unix: + - libzip.a + headers: + - zip.h + - zipconf.h + depends@unix: + - zlib + suggests@unix: + - bzip2 + - xz + - zstd + - openssl diff --git a/src/Package/Library/imagemagick.php b/src/Package/Library/imagemagick.php new file mode 100644 index 000000000..941f1580e --- /dev/null +++ b/src/Package/Library/imagemagick.php @@ -0,0 +1,83 @@ +optionalPackage('libzip', ...ac_with_args('zip')) + ->optionalPackage('libjpeg', ...ac_with_args('jpeg')) + ->optionalPackage('libpng', ...ac_with_args('png')) + ->optionalPackage('libwebp', ...ac_with_args('webp')) + ->optionalPackage('libxml2', ...ac_with_args('xml')) + ->optionalPackage('libheif', ...ac_with_args('heic')) + ->optionalPackage('zlib', ...ac_with_args('zlib')) + ->optionalPackage('xz', ...ac_with_args('lzma')) + ->optionalPackage('zstd', ...ac_with_args('zstd')) + ->optionalPackage('freetype', ...ac_with_args('freetype')) + ->optionalPackage('bzip2', ...ac_with_args('bzlib')) + ->optionalPackage('libjxl', ...ac_with_args('jxl')) + ->optionalPackage('jbig', ...ac_with_args('jbig')) + ->addConfigureArgs( + '--disable-openmp', + '--without-x', + ); + + // special: linux-static target needs `-static` + $ldflags = $toolchain->isStatic() ? '-static -ldl' : '-ldl'; + + // special: macOS needs -iconv + $libs = SystemTarget::getTargetOS() === 'Darwin' ? '-liconv' : ''; + + $ac->appendEnv([ + 'LDFLAGS' => $ldflags, + 'LIBS' => $libs, + 'PKG_CONFIG' => '$PKG_CONFIG --static', + ]); + + $ac->configure()->make(); + + f_putenv("SPC_DEFAULT_LD_FLAGS={$original_ldflags}"); + + $filelist = [ + 'ImageMagick.pc', + 'ImageMagick-7.Q16HDRI.pc', + 'Magick++.pc', + 'Magick++-7.Q16HDRI.pc', + 'MagickCore.pc', + 'MagickCore-7.Q16HDRI.pc', + 'MagickWand.pc', + 'MagickWand-7.Q16HDRI.pc', + ]; + $lib->patchPkgconfPrefix($filelist); + foreach ($filelist as $file) { + FileSystem::replaceFileRegex( + "{$lib->getLibDir()}/pkgconfig/{$file}", + '#includearchdir=/include/ImageMagick-7#m', + 'includearchdir=${prefix}/include/ImageMagick-7' + ); + } + $lib->patchLaDependencyPrefix(); + } +} diff --git a/src/Package/Library/jbig.php b/src/Package/Library/jbig.php new file mode 100644 index 000000000..1cfe60b74 --- /dev/null +++ b/src/Package/Library/jbig.php @@ -0,0 +1,46 @@ +getSourceDir() . '/Makefile', 'CFLAGS = -O2 -W -Wno-unused-result', 'CFLAGS = -O2 -W -Wno-unused-result -fPIC'); + } + + #[BuildFor('Darwin')] + #[BuildFor('Linux')] + public function buildUnix(LibraryPackage $lib, PackageBuilder $builder): void + { + $ccenv = [ + 'CC' => getenv('CC'), + 'CXX' => getenv('CXX'), + 'AR' => getenv('AR'), + 'LD' => getenv('LD'), + ]; + $env = []; + foreach ($ccenv as $k => $v) { + $env[] = "{$k}={$v}"; + } + $env_str = implode(' ', $env); + shell()->cd($lib->getSourceDir())->initializeEnv($lib) + ->exec("make -j{$builder->concurrency} {$env_str} lib") + ->exec("cp libjbig/libjbig.a {$lib->getLibDir()}") + ->exec("cp libjbig/libjbig85.a {$lib->getLibDir()}") + ->exec("cp libjbig/jbig.h {$lib->getIncludeDir()}") + ->exec("cp libjbig/jbig85.h {$lib->getIncludeDir()}") + ->exec("cp libjbig/jbig_ar.h {$lib->getIncludeDir()}"); + } +} diff --git a/src/Package/Library/lerc.php b/src/Package/Library/lerc.php new file mode 100644 index 000000000..fc1e8bada --- /dev/null +++ b/src/Package/Library/lerc.php @@ -0,0 +1,21 @@ +build(); + } +} diff --git a/src/Package/Library/libaom.php b/src/Package/Library/libaom.php new file mode 100644 index 000000000..167ef0766 --- /dev/null +++ b/src/Package/Library/libaom.php @@ -0,0 +1,33 @@ +setBuildDir("{$this->getSourceDir()}/builddir") + ->addConfigureArgs('-DAOM_TARGET_CPU=generic') + ->build(); + f_putenv("SPC_COMPILER_EXTRA={$extra}"); + $this->patchPkgconfPrefix(['aom.pc']); + } +} diff --git a/src/Package/Library/libde265.php b/src/Package/Library/libde265.php new file mode 100644 index 000000000..b3e8f62f8 --- /dev/null +++ b/src/Package/Library/libde265.php @@ -0,0 +1,24 @@ +addConfigureArgs('-DENABLE_SDL=OFF') + ->build(); + $this->patchPkgconfPrefix(['libde265.pc']); + } +} diff --git a/src/Package/Library/libheif.php b/src/Package/Library/libheif.php new file mode 100644 index 000000000..65545f365 --- /dev/null +++ b/src/Package/Library/libheif.php @@ -0,0 +1,45 @@ +getSourceDir() . '/CMakeLists.txt'), 'libbrotlienc')) { + FileSystem::replaceFileStr( + $lib->getSourceDir() . '/CMakeLists.txt', + 'list(APPEND REQUIRES_PRIVATE "libbrotlidec")', + 'list(APPEND REQUIRES_PRIVATE "libbrotlidec")' . "\n" . ' list(APPEND REQUIRES_PRIVATE "libbrotlienc")' + ); + } + } + + #[BuildFor('Darwin')] + #[BuildFor('Linux')] + public function buildUnix(LibraryPackage $lib): void + { + UnixCMakeExecutor::create($lib) + ->addConfigureArgs( + '--preset=release', + '-DWITH_EXAMPLES=OFF', + '-DWITH_GDK_PIXBUF=OFF', + '-DBUILD_TESTING=OFF', + '-DWITH_LIBSHARPYUV=ON', // optional: libwebp + '-DENABLE_PLUGIN_LOADING=OFF', + ) + ->build(); + $lib->patchPkgconfPrefix(['libheif.pc']); + } +} diff --git a/src/Package/Library/libjxl.php b/src/Package/Library/libjxl.php new file mode 100644 index 000000000..48e9a239a --- /dev/null +++ b/src/Package/Library/libjxl.php @@ -0,0 +1,52 @@ +addConfigureArgs( + '-DJPEGXL_ENABLE_TOOLS=OFF', + '-DJPEGXL_ENABLE_EXAMPLES=OFF', + '-DJPEGXL_ENABLE_MANPAGES=OFF', + '-DJPEGXL_ENABLE_BENCHMARK=OFF', + '-DJPEGXL_ENABLE_PLUGINS=OFF', + '-DJPEGXL_ENABLE_SJPEG=ON', + '-DJPEGXL_ENABLE_JNI=OFF', + '-DJPEGXL_ENABLE_TRANSCODE_JPEG=ON', + '-DJPEGXL_STATIC=' . ($toolchain->isStatic() ? 'ON' : 'OFF'), + '-DJPEGXL_FORCE_SYSTEM_BROTLI=ON', + '-DBUILD_TESTING=OFF' + ); + + if ($toolchain instanceof ZigToolchain) { + $cflags = getenv('SPC_DEFAULT_C_FLAGS') ?: getenv('CFLAGS') ?: ''; + $has_avx512 = str_contains($cflags, '-mavx512') || str_contains($cflags, '-march=x86-64-v4'); + if (!$has_avx512) { + $cmake->addConfigureArgs( + '-DCXX_MAVX512F_SUPPORTED:BOOL=FALSE', + '-DCXX_MAVX512DQ_SUPPORTED:BOOL=FALSE', + '-DCXX_MAVX512CD_SUPPORTED:BOOL=FALSE', + '-DCXX_MAVX512BW_SUPPORTED:BOOL=FALSE', + '-DCXX_MAVX512VL_SUPPORTED:BOOL=FALSE' + ); + } + } + + $cmake->build(); + } +} diff --git a/src/Package/Library/libtiff.php b/src/Package/Library/libtiff.php new file mode 100644 index 000000000..385ff3ca4 --- /dev/null +++ b/src/Package/Library/libtiff.php @@ -0,0 +1,50 @@ +getSourceDir()}/configure", '-lwebp', '-lwebp -lsharpyuv'); + FileSystem::replaceFileStr("{$lib->getSourceDir()}/configure", '-l"$lerc_lib_name"', "-l\"\$lerc_lib_name\" {$libcpp}"); + UnixAutoconfExecutor::create($lib) + ->optionalPackage('lerc', '--enable-lerc', '--disable-lerc') + ->optionalPackage('zstd', '--enable-zstd', '--disable-zstd') + ->optionalPackage('libwebp', '--enable-webp', '--disable-webp') + ->optionalPackage('xz', '--enable-lzma', '--disable-lzma') + ->optionalPackage('jbig', '--enable-jbig', '--disable-jbig') + ->configure( + // zlib deps + '--enable-zlib', + "--with-zlib-include-dir={$lib->getIncludeDir()}", + "--with-zlib-lib-dir={$lib->getLibDir()}", + // libjpeg deps + '--enable-jpeg', + "--with-jpeg-include-dir={$lib->getIncludeDir()}", + "--with-jpeg-lib-dir={$lib->getLibDir()}", + '--disable-old-jpeg', + '--disable-jpeg12', + '--disable-libdeflate', + '--disable-tools', + '--disable-contrib', + '--disable-cxx', + '--without-x', + ) + ->make(); + $lib->patchPkgconfPrefix(['libtiff-4.pc']); + } +} diff --git a/src/Package/Library/libwebp.php b/src/Package/Library/libwebp.php new file mode 100644 index 000000000..0ee4028d5 --- /dev/null +++ b/src/Package/Library/libwebp.php @@ -0,0 +1,44 @@ + +int main() { return _mm256_cvtsi256_si32(_mm256_setzero_si256()); }'; + $cc = getenv('CC') ?: 'gcc'; + [$ret] = shell()->execWithResult("printf '%s' '{$code}' | {$cc} -x c -mavx2 -o /dev/null - 2>&1"); + $disableAvx2 = $ret !== 0 && GNU_ARCH === 'x86_64' && PHP_OS_FAMILY === 'Linux'; + + UnixCMakeExecutor::create($this) + ->addConfigureArgs( + '-DWEBP_BUILD_EXTRAS=OFF', + '-DWEBP_BUILD_ANIM_UTILS=OFF', + '-DWEBP_BUILD_CWEBP=OFF', + '-DWEBP_BUILD_DWEBP=OFF', + '-DWEBP_BUILD_GIF2WEBP=OFF', + '-DWEBP_BUILD_IMG2WEBP=OFF', + '-DWEBP_BUILD_VWEBP=OFF', + '-DWEBP_BUILD_WEBPINFO=OFF', + '-DWEBP_BUILD_WEBPMUX=OFF', + '-DWEBP_BUILD_FUZZTEST=OFF', + $disableAvx2 ? '-DWEBP_ENABLE_SIMD=OFF' : '' + ) + ->build(); + // patch pkgconfig + $this->patchPkgconfPrefix(patch_option: PKGCONF_PATCH_PREFIX | PKGCONF_PATCH_LIBDIR); + $this->patchPkgconfPrefix(['libsharpyuv.pc'], PKGCONF_PATCH_CUSTOM, ['/^includedir=.*$/m', 'includedir=${prefix}/include/webp']); + } +} diff --git a/src/Package/Library/libzip.php b/src/Package/Library/libzip.php new file mode 100644 index 000000000..f6ebdfea7 --- /dev/null +++ b/src/Package/Library/libzip.php @@ -0,0 +1,36 @@ +optionalPackage('bzip2', ...cmake_boolean_args('ENABLE_BZIP2')) + ->optionalPackage('xz', ...cmake_boolean_args('ENABLE_LZMA')) + ->optionalPackage('openssl', ...cmake_boolean_args('ENABLE_OPENSSL')) + ->optionalPackage('zstd', ...cmake_boolean_args('ENABLE_ZSTD')) + ->addConfigureArgs( + '-DENABLE_GNUTLS=OFF', + '-DENABLE_MBEDTLS=OFF', + '-DBUILD_DOC=OFF', + '-DBUILD_EXAMPLES=OFF', + '-DBUILD_REGRESS=OFF', + '-DBUILD_TOOLS=OFF', + '-DBUILD_OSSFUZZ=OFF', + ) + ->build(); + $lib->patchPkgconfPrefix(['libzip.pc'], PKGCONF_PATCH_PREFIX); + } +} From 8f798c9006553479722658c5bbf330d6bb9d1217 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 09:48:51 +0800 Subject: [PATCH 63/99] Add imap and BuildRootTracker --- config/pkg/lib/imap.yml | 14 ++ src/Package/Library/imap.php | 92 ++++++++++ src/StaticPHP/Package/PackageInstaller.php | 33 ++++ src/StaticPHP/Util/BuildRootTracker.php | 189 +++++++++++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 config/pkg/lib/imap.yml create mode 100644 src/StaticPHP/Util/BuildRootTracker.php diff --git a/config/pkg/lib/imap.yml b/config/pkg/lib/imap.yml new file mode 100644 index 000000000..cb2ec4106 --- /dev/null +++ b/config/pkg/lib/imap.yml @@ -0,0 +1,14 @@ +imap: + type: library + artifact: + source: { + "type": "git", + "url": "https://github.com/static-php/imap.git", + "rev": "master" + } + metadata: + license-files: [LICENSE] + static-libs@unix: + - libc-client.a + suggests@unix: + - openssl diff --git a/src/Package/Library/imap.php b/src/Package/Library/imap.php index 69e6c8820..607d78ee2 100644 --- a/src/Package/Library/imap.php +++ b/src/Package/Library/imap.php @@ -6,10 +6,15 @@ use Package\Target\php; use StaticPHP\Attribute\Package\AfterStage; +use StaticPHP\Attribute\Package\BuildFor; use StaticPHP\Attribute\Package\Library; +use StaticPHP\Attribute\Package\PatchBeforeBuild; use StaticPHP\Attribute\PatchDescription; +use StaticPHP\Package\LibraryPackage; +use StaticPHP\Package\PackageInstaller; use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\FileSystem; +use StaticPHP\Util\SourcePatcher; #[Library('imap')] class imap @@ -22,4 +27,91 @@ public function afterPatchScripts(): void FileSystem::replaceFileRegex(BUILD_BIN_PATH . '/php-config', '/^libs="(.*)"$/m', 'libs="$1 -lcrypt"'); } } + + #[PatchBeforeBuild] + #[PatchDescription('Patch imap build system for Linux and macOS compatibility')] + public function patchBeforeBuild(LibraryPackage $lib): void + { + if (SystemTarget::getTargetOS() === 'Linux') { + $cc = getenv('CC') ?: 'gcc'; + // FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', '-DMAC_OSX_KLUDGE=1', ''); + FileSystem::replaceFileStr("{$lib->getSourceDir()}/src/osdep/unix/Makefile", 'CC=cc', "CC={$cc}"); + /* FileSystem::replaceFileStr($lib->getSourceDir() . '/src/osdep/unix/Makefile', '-lcrypto -lz', '-lcrypto'); + FileSystem::replaceFileStr($lib->getSourceDir() . '/src/osdep/unix/Makefile', '-lcrypto', '-lcrypto -lz'); + FileSystem::replaceFileStr( + $lib->getSourceDir() . '/src/osdep/unix/ssl_unix.c', + "#include \n#include ", + "#include \n#include " + ); + // SourcePatcher::patchFile('1006_openssl1.1_autoverify.patch', $lib->getSourceDir()); + SourcePatcher::patchFile('2014_openssl1.1.1_sni.patch', $lib->getSourceDir()); */ + FileSystem::replaceFileStr("{$lib->getSourceDir()}/Makefile", 'SSLINCLUDE=/usr/include/openssl', "SSLINCLUDE={$lib->getIncludeDir()}"); + FileSystem::replaceFileStr("{$lib->getSourceDir()}/Makefile", 'SSLLIB=/usr/lib', "SSLLIB={$lib->getLibDir()}"); + } elseif (SystemTarget::getTargetOS() === 'Darwin') { + $cc = getenv('CC') ?: 'clang'; + SourcePatcher::patchFile('0001_imap_macos.patch', $lib->getSourceDir()); + FileSystem::replaceFileStr($lib->getSourceDir() . '/src/osdep/unix/Makefile', 'CC=cc', "CC={$cc}"); + FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'SSLINCLUDE=/usr/include/openssl', 'SSLINCLUDE=' . $lib->getIncludeDir()); + FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'SSLLIB=/usr/lib', 'SSLLIB=' . $lib->getLibDir()); + } + } + + #[BuildFor('Linux')] + public function buildLinux(LibraryPackage $lib, PackageInstaller $installer): void + { + if ($installer->isPackageResolved('openssl')) { + $ssl_options = "SPECIALAUTHENTICATORS=ssl SSLTYPE=unix.nopwd SSLINCLUDE={$lib->getIncludeDir()} SSLLIB={$lib->getLibDir()}"; + } else { + $ssl_options = 'SSLTYPE=none'; + } + $libcVer = SystemTarget::getLibcVersion(); + $extraLibs = $libcVer && version_compare($libcVer, '2.17', '<=') ? 'EXTRALDFLAGS="-ldl -lrt -lpthread"' : ''; + shell()->cd($lib->getSourceDir()) + ->exec('make clean') + ->exec('touch ip6') + ->exec('chmod +x tools/an') + ->exec('chmod +x tools/ua') + ->exec('chmod +x src/osdep/unix/drivers') + ->exec('chmod +x src/osdep/unix/mkauths') + ->exec("yes | make slx {$ssl_options} EXTRACFLAGS='-fPIC -Wno-implicit-function-declaration -Wno-incompatible-function-pointer-types' {$extraLibs}"); + try { + shell() + ->exec("cp -rf {$lib->getSourceDir()}/c-client/c-client.a {$lib->getLibDir()}/libc-client.a") + ->exec("cp -rf {$lib->getSourceDir()}/c-client/*.c {$lib->getLibDir()}/") + ->exec("cp -rf {$lib->getSourceDir()}/c-client/*.h {$lib->getIncludeDir()}/") + ->exec("cp -rf {$lib->getSourceDir()}/src/osdep/unix/*.h {$lib->getIncludeDir()}/"); + } catch (\Throwable) { + // last command throws an exception, no idea why since it works + } + } + + #[BuildFor('Darwin')] + public function buildDarwin(LibraryPackage $lib, PackageInstaller $installer): void + { + if ($installer->isPackageResolved('openssl')) { + $ssl_options = "SPECIALAUTHENTICATORS=ssl SSLTYPE=unix.nopwd SSLINCLUDE={$lib->getIncludeDir()} SSLLIB={$lib->getLibDir()}"; + } else { + $ssl_options = 'SSLTYPE=none'; + } + $out = shell()->execWithResult('echo "-include $(xcrun --show-sdk-path)/usr/include/poll.h -include $(xcrun --show-sdk-path)/usr/include/time.h -include $(xcrun --show-sdk-path)/usr/include/utime.h"')[1][0]; + shell()->cd($lib->getSourceDir()) + ->exec('make clean') + ->exec('touch ip6') + ->exec('chmod +x tools/an') + ->exec('chmod +x tools/ua') + ->exec('chmod +x src/osdep/unix/drivers') + ->exec('chmod +x src/osdep/unix/mkauths') + ->exec( + "echo y | make osx {$ssl_options} EXTRACFLAGS='-Wno-implicit-function-declaration -Wno-incompatible-function-pointer-types {$out}'" + ); + try { + shell() + ->exec("cp -rf {$lib->getSourceDir()}/c-client/c-client.a {$lib->getLibDir()}/libc-client.a") + ->exec("cp -rf {$lib->getSourceDir()}/c-client/*.c {$lib->getLibDir()}/") + ->exec("cp -rf {$lib->getSourceDir()}/c-client/*.h {$lib->getIncludeDir()}/") + ->exec("cp -rf {$lib->getSourceDir()}/src/osdep/unix/*.h {$lib->getIncludeDir()}/"); + } catch (\Throwable) { + // last command throws an exception, no idea why since it works + } + } } diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index a01d29fcd..80ea1aef7 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -13,6 +13,7 @@ use StaticPHP\Exception\WrongUsageException; use StaticPHP\Registry\PackageLoader; use StaticPHP\Runtime\SystemTarget; +use StaticPHP\Util\BuildRootTracker; use StaticPHP\Util\DependencyResolver; use StaticPHP\Util\DirDiff; use StaticPHP\Util\FileSystem; @@ -42,6 +43,9 @@ class PackageInstaller /** @var bool Whether to download missing sources automatically */ protected bool $download = true; + /** @var null|BuildRootTracker buildroot file tracker for debugging purpose */ + protected ?BuildRootTracker $tracker = null; + public function __construct(protected array $options = []) { ApplicationContext::set(PackageInstaller::class, $this); @@ -53,6 +57,11 @@ public function __construct(protected array $options = []) if (!empty($options['no-download'])) { $this->download = false; } + + // Initialize BuildRootTracker if tracking is enabled (default: enabled unless --no-tracker) + if (empty($options['no-tracker'])) { + $this->tracker = new BuildRootTracker(); + } } /** @@ -111,6 +120,16 @@ public function setDownload(bool $download = true): static return $this; } + /** + * Get the BuildRootTracker instance. + * + * @return null|BuildRootTracker The tracker instance or null if tracking is disabled + */ + public function getTracker(): ?BuildRootTracker + { + return $this->tracker; + } + public function printBuildPackageOutputs(): void { foreach ($this->build_packages as $package) { @@ -183,8 +202,14 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): InteractiveTerm::indicateProgress('Installing package: ' . ConsoleColor::yellow($package->getName())); } try { + // Start tracking for binary installation + $this->tracker?->startTracking($package, 'install'); $status = $this->installBinary($package); + // Stop tracking and record changes + $this->tracker?->stopTracking(); } catch (\Throwable $e) { + // Stop tracking on error + $this->tracker?->stopTracking(); if ($interactive) { InteractiveTerm::finish('Installing binary package failed: ' . ConsoleColor::red($package->getName()), false); echo PHP_EOL; @@ -199,6 +224,9 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): InteractiveTerm::indicateProgress('Building package: ' . ConsoleColor::yellow($package->getName())); } try { + // Start tracking for build + $this->tracker?->startTracking($package, 'build'); + if ($is_to_build && ($this->options['pack-mode'] ?? false) === true) { $dirdiff = new DirDiff(BUILD_ROOT_PATH, false); ApplicationContext::set(DirDiff::class, $dirdiff); @@ -209,7 +237,12 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): if ($is_to_build && ($this->options['pack-mode'] ?? false) === true) { $package->runStage('packPrebuilt'); } + + // Stop tracking and record changes + $this->tracker?->stopTracking(); } catch (\Throwable $e) { + // Stop tracking on error + $this->tracker?->stopTracking(); if ($interactive) { InteractiveTerm::finish('Building package failed: ' . ConsoleColor::red($package->getName()), false); echo PHP_EOL; diff --git a/src/StaticPHP/Util/BuildRootTracker.php b/src/StaticPHP/Util/BuildRootTracker.php new file mode 100644 index 000000000..306bf90ca --- /dev/null +++ b/src/StaticPHP/Util/BuildRootTracker.php @@ -0,0 +1,189 @@ +}> Tracking data */ + protected array $tracking_data = []; + + protected static string $tracker_file = BUILD_ROOT_PATH . '/.spc-tracker.json'; + + protected ?DirDiff $current_diff = null; + + protected ?string $current_package = null; + + protected ?string $current_type = null; + + public function __construct() + { + $this->loadTrackingData(); + } + + /** + * Start tracking for a package. + * + * @param Package $package The package to track + * @param string $type The operation type: 'build' or 'install' + */ + public function startTracking(Package $package, string $type = 'build'): void + { + $this->current_package = $package->getName(); + $this->current_type = $type; + $this->current_diff = new DirDiff(BUILD_ROOT_PATH, false); + } + + /** + * Stop tracking and record the changes. + */ + public function stopTracking(): void + { + if ($this->current_diff === null || $this->current_package === null) { + return; + } + + $increment_files = $this->current_diff->getIncrementFiles(true); + + if ($increment_files !== []) { + // Remove buildroot prefix if exists and normalize paths + $normalized_files = array_map(function ($file) { + // Remove leading slashes + return ltrim($file, '/\\'); + }, $increment_files); + + $this->tracking_data[$this->current_package] = [ + 'package' => $this->current_package, + 'type' => $this->current_type, + 'files' => array_values($normalized_files), + 'time' => date('Y-m-d H:i:s'), + ]; + + $this->saveTrackingData(); + } + + $this->current_diff = null; + $this->current_package = null; + $this->current_type = null; + } + + /** + * Get tracking data for a specific package. + * + * @param string $package_name Package name + * @return null|array Tracking data or null if not found + */ + public function getPackageTracking(string $package_name): ?array + { + return $this->tracking_data[$package_name] ?? null; + } + + /** + * Get all tracking data. + * + * @return array All tracking data + */ + public function getAllTracking(): array + { + return $this->tracking_data; + } + + /** + * Find which package introduced a specific file. + * + * @param string $file File path (relative to buildroot) + * @return null|string Package name or null if not found + */ + public function findFileSource(string $file): ?string + { + $file = ltrim($file, '/\\'); + foreach ($this->tracking_data as $package_name => $data) { + if (in_array($file, $data['files'], true)) { + return $package_name; + } + } + return null; + } + + /** + * Clear tracking data for a specific package. + * + * @param string $package_name Package name + */ + public function clearPackageTracking(string $package_name): void + { + unset($this->tracking_data[$package_name]); + $this->saveTrackingData(); + } + + /** + * Clear all tracking data. + */ + public function clearAllTracking(): void + { + $this->tracking_data = []; + $this->saveTrackingData(); + } + + /** + * Get tracking statistics. + * + * @return array{total_packages: int, total_files: int, by_type: array} + */ + public function getStatistics(): array + { + $total_files = 0; + $by_type = []; + + foreach ($this->tracking_data as $data) { + $total_files += count($data['files']); + $type = $data['type']; + $by_type[$type] = ($by_type[$type] ?? 0) + 1; + } + + return [ + 'total_packages' => count($this->tracking_data), + 'total_files' => $total_files, + 'by_type' => $by_type, + ]; + } + + /** + * Get the tracker file path. + */ + public static function getTrackerFilePath(): string + { + return self::$tracker_file; + } + + /** + * Load tracking data from file. + */ + protected function loadTrackingData(): void + { + if (is_file(self::$tracker_file)) { + $content = file_get_contents(self::$tracker_file); + $data = json_decode($content, true); + if (is_array($data)) { + $this->tracking_data = $data; + } + } + } + + /** + * Save tracking data to file. + */ + protected function saveTrackingData(): void + { + FileSystem::createDir(dirname(self::$tracker_file)); + $content = json_encode($this->tracking_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + FileSystem::writeFile(self::$tracker_file, $content); + } +} From 1eec88fd6c66bb67008ca0320d8001ac1dccbd79 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 09:59:29 +0800 Subject: [PATCH 64/99] Add reset command --- src/StaticPHP/Command/ResetCommand.php | 106 +++++++++++++++++++++++++ src/StaticPHP/ConsoleApplication.php | 2 + 2 files changed, 108 insertions(+) create mode 100644 src/StaticPHP/Command/ResetCommand.php diff --git a/src/StaticPHP/Command/ResetCommand.php b/src/StaticPHP/Command/ResetCommand.php new file mode 100644 index 000000000..4a55f792a --- /dev/null +++ b/src/StaticPHP/Command/ResetCommand.php @@ -0,0 +1,106 @@ +setDescription('Reset and clean build directories') + ->addOption('with-pkgroot', null, InputOption::VALUE_NONE, 'Also remove pkgroot directory') + ->addOption('with-download', null, InputOption::VALUE_NONE, 'Also remove downloads directory') + ->addOption('yes', 'y', InputOption::VALUE_NONE, 'Skip confirmation prompt'); + } + + public function handle(): int + { + $dirs_to_remove = [ + 'buildroot' => BUILD_ROOT_PATH, + 'source' => SOURCE_PATH, + ]; + + if ($this->input->getOption('with-pkgroot')) { + $dirs_to_remove['pkgroot'] = PKG_ROOT_PATH; + } + + if ($this->input->getOption('with-download')) { + $dirs_to_remove['downloads'] = DOWNLOAD_PATH; + } + + // Show warning + InteractiveTerm::notice('You are doing some operations that are not recoverable:'); + foreach ($dirs_to_remove as $name => $path) { + InteractiveTerm::notice("- Removing directory: {$path}"); + } + + // Confirm with user unless --yes is specified + if (!$this->input->getOption('yes')) { + if (!confirm('Are you sure you want to continue?', false)) { + InteractiveTerm::error(message: 'Reset operation cancelled.'); + return static::SUCCESS; + } + } + + // Remove directories + foreach ($dirs_to_remove as $name => $path) { + if (!is_dir($path)) { + InteractiveTerm::notice("Directory {$name} does not exist, skipping: {$path}"); + continue; + } + + InteractiveTerm::indicateProgress("Removing: {$path}"); + + if (PHP_OS_FAMILY === 'Windows') { + // Force delete on Windows to handle git directories + $this->removeDirectoryWindows($path); + } else { + // Use FileSystem::removeDir for Unix systems + FileSystem::removeDir($path); + } + + InteractiveTerm::finish("Removed: {$path}"); + } + + InteractiveTerm::notice('Reset completed.'); + return static::SUCCESS; + } + + /** + * Force remove directory on Windows + * Uses PowerShell to handle git directories and other problematic files + * + * @param string $path Directory path to remove + */ + private function removeDirectoryWindows(string $path): void + { + $path = FileSystem::convertPath($path); + + // Try using PowerShell for force deletion + $escaped_path = escapeshellarg($path); + + // Use PowerShell Remove-Item with -Force and -Recurse + $ps_cmd = "powershell -Command \"Remove-Item -Path {$escaped_path} -Recurse -Force -ErrorAction SilentlyContinue\""; + f_exec($ps_cmd, $output, $ret_code); + + // If PowerShell fails or directory still exists, try cmd rmdir + if ($ret_code !== 0 || is_dir($path)) { + $cmd_command = "rmdir /s /q {$escaped_path}"; + f_exec($cmd_command, $output, $ret_code); + } + + // Final fallback: use FileSystem::removeDir + if (is_dir($path)) { + FileSystem::removeDir($path); + } + } +} diff --git a/src/StaticPHP/ConsoleApplication.php b/src/StaticPHP/ConsoleApplication.php index 22404b6d3..8608f7617 100644 --- a/src/StaticPHP/ConsoleApplication.php +++ b/src/StaticPHP/ConsoleApplication.php @@ -16,6 +16,7 @@ use StaticPHP\Command\DumpLicenseCommand; use StaticPHP\Command\ExtractCommand; use StaticPHP\Command\InstallPackageCommand; +use StaticPHP\Command\ResetCommand; use StaticPHP\Command\SPCConfigCommand; use StaticPHP\Package\TargetPackage; use StaticPHP\Registry\PackageLoader; @@ -58,6 +59,7 @@ public function __construct() new ExtractCommand(), new SPCConfigCommand(), new DumpLicenseCommand(), + new ResetCommand(), // dev commands new ShellCommand(), From 3cfab10f8517ebfe0c267d0258b0d7e8a61c2a09 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 10:05:35 +0800 Subject: [PATCH 65/99] Add libacl --- config/pkg/lib/libacl.yml | 12 ++++++++++ src/Package/Library/libacl.php | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 config/pkg/lib/libacl.yml create mode 100644 src/Package/Library/libacl.php diff --git a/config/pkg/lib/libacl.yml b/config/pkg/lib/libacl.yml new file mode 100644 index 000000000..16051c464 --- /dev/null +++ b/config/pkg/lib/libacl.yml @@ -0,0 +1,12 @@ +libacl: + type: library + artifact: + source: 'https://download.savannah.nongnu.org/releases/acl/acl-2.3.2.tar.gz' + source-mirror: 'https://mirror.souseiseki.middlendian.com/nongnu/acl/acl-2.3.2.tar.gz' + metadata: + license-files: [doc/COPYING.LGPL] + license: LGPL-2.1-or-later + static-libs@unix: + - libacl.a + depends: + - attr diff --git a/src/Package/Library/libacl.php b/src/Package/Library/libacl.php new file mode 100644 index 000000000..a74cb2d43 --- /dev/null +++ b/src/Package/Library/libacl.php @@ -0,0 +1,40 @@ +exec('libtoolize --force --copy') + ->exec('./autogen.sh || autoreconf -if') + ->configure('--disable-nls', '--disable-tests') + ->make('install-acl_h install-libacl_h install-data install-libLTLIBRARIES install-pkgincludeHEADERS install-sysincludeHEADERS install-pkgconfDATA', with_install: false); + $lib->patchPkgconfPrefix(['libacl.pc'], PKGCONF_PATCH_PREFIX); + } +} From 39a207076e51f35c9a67d56fdd32c699e67326ff Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 10:11:24 +0800 Subject: [PATCH 66/99] Add libargon2 --- config/pkg/lib/imagemagick.yml | 20 ++++++------- config/pkg/lib/imap.yml | 13 ++++----- config/pkg/lib/libacl.yml | 4 +-- config/pkg/lib/libargon2.yml | 14 +++++++++ config/pkg/lib/libzip.yml | 21 +++++++------- src/Package/Library/libargon2.php | 48 +++++++++++++++++++++++++++++++ 6 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 config/pkg/lib/libargon2.yml create mode 100644 src/Package/Library/libargon2.php diff --git a/config/pkg/lib/imagemagick.yml b/config/pkg/lib/imagemagick.yml index 17fc03e81..4c4a8e1c1 100644 --- a/config/pkg/lib/imagemagick.yml +++ b/config/pkg/lib/imagemagick.yml @@ -1,18 +1,11 @@ imagemagick: type: library artifact: - source: { - "type": "ghtar", - "repo": "ImageMagick/ImageMagick" - } + source: + type: ghtar + repo: ImageMagick/ImageMagick metadata: license-files: [LICENSE] - lang: cpp - pkg-configs: [ - "Magick++-7.Q16HDRI", - "MagickCore-7.Q16HDRI", - "MagickWand-7.Q16HDRI" - ] depends: - zlib - libjpeg @@ -28,5 +21,8 @@ imagemagick: - xz - libzip - libxml2 - - + lang: cpp + pkg-configs: + - Magick++-7.Q16HDRI + - MagickCore-7.Q16HDRI + - MagickWand-7.Q16HDRI diff --git a/config/pkg/lib/imap.yml b/config/pkg/lib/imap.yml index cb2ec4106..31e6c7e71 100644 --- a/config/pkg/lib/imap.yml +++ b/config/pkg/lib/imap.yml @@ -1,14 +1,13 @@ imap: type: library artifact: - source: { - "type": "git", - "url": "https://github.com/static-php/imap.git", - "rev": "master" - } + source: + type: git + url: 'https://github.com/static-php/imap.git' + rev: master metadata: license-files: [LICENSE] - static-libs@unix: - - libc-client.a suggests@unix: - openssl + static-libs@unix: + - libc-client.a diff --git a/config/pkg/lib/libacl.yml b/config/pkg/lib/libacl.yml index 16051c464..e67a33eca 100644 --- a/config/pkg/lib/libacl.yml +++ b/config/pkg/lib/libacl.yml @@ -6,7 +6,7 @@ libacl: metadata: license-files: [doc/COPYING.LGPL] license: LGPL-2.1-or-later - static-libs@unix: - - libacl.a depends: - attr + static-libs@unix: + - libacl.a diff --git a/config/pkg/lib/libargon2.yml b/config/pkg/lib/libargon2.yml new file mode 100644 index 000000000..dcd388819 --- /dev/null +++ b/config/pkg/lib/libargon2.yml @@ -0,0 +1,14 @@ +libargon2: + type: library + artifact: + source: + type: git + rev: master + url: 'https://github.com/static-php/phc-winner-argon2' + metadata: + license-files: [LICENSE] + license: BSD-2-Clause + suggests: + - libsodium + static-libs@unix: + - libargon2.a diff --git a/config/pkg/lib/libzip.yml b/config/pkg/lib/libzip.yml index 3d8c02f8f..2de69d424 100644 --- a/config/pkg/lib/libzip.yml +++ b/config/pkg/lib/libzip.yml @@ -1,19 +1,13 @@ libzip: type: library artifact: - source: { - "type": "ghrel", - "repo": "nih-at/libzip", - "match": "libzip.+\\.tar\\.xz", - "prefer-stable": true - } + source: + type: ghrel + repo: nih-at/libzip + match: libzip.+\.tar\.xz + prefer-stable: true metadata: license-files: [LICENSE] - static-libs@unix: - - libzip.a - headers: - - zip.h - - zipconf.h depends@unix: - zlib suggests@unix: @@ -21,3 +15,8 @@ libzip: - xz - zstd - openssl + headers: + - zip.h + - zipconf.h + static-libs@unix: + - libzip.a diff --git a/src/Package/Library/libargon2.php b/src/Package/Library/libargon2.php new file mode 100644 index 000000000..49afceeb7 --- /dev/null +++ b/src/Package/Library/libargon2.php @@ -0,0 +1,48 @@ +getSourceDir()}/Makefile", 'LIBRARY_REL ?= lib/x86_64-linux-gnu', 'LIBRARY_REL ?= lib'); + } + + #[BuildFor('Darwin')] + #[BuildFor('Linux')] + public function buildUnix(LibraryPackage $lib, PackageBuilder $builder): void + { + shell()->cd($lib->getSourceDir())->initializeEnv($lib) + ->exec("make PREFIX='' clean") + ->exec("make -j{$builder->concurrency} PREFIX=''") + ->exec("make install PREFIX='' DESTDIR={$lib->getBuildRootPath()}"); + + $lib->patchPkgconfPrefix(['libargon2.pc']); + + foreach (FileSystem::scanDirFiles("{$lib->getBuildRootPath()}/lib/", false, true) as $filename) { + if (str_starts_with($filename, 'libargon2') && (str_contains($filename, '.so') || str_ends_with($filename, '.dylib'))) { + unlink("{$lib->getBuildRootPath()}/lib/{$filename}"); + } + } + + if (file_exists("{$lib->getBinDir()}/argon2")) { + unlink("{$lib->getBinDir()}/argon2"); + } + } +} From fba2676d809b052b58706cee6017b536a1f053d7 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 10:17:58 +0800 Subject: [PATCH 67/99] Add lint-config command to check and sort configuration files --- captainhook.json | 97 ++++++++++--------- composer.json | 1 + .../Command/Dev/LintConfigCommand.php | 50 ++++++++-- 3 files changed, 94 insertions(+), 54 deletions(-) diff --git a/captainhook.json b/captainhook.json index 233e387eb..1057cb292 100644 --- a/captainhook.json +++ b/captainhook.json @@ -1,44 +1,53 @@ -{ - "pre-push": { - "enabled": true, - "actions": [ - { - "action": "php vendor/bin/phpstan analyse --memory-limit 300M" - } - ] - }, - "pre-commit": { - "enabled": true, - "actions": [ - { - "action": "php vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --dry-run --diff {$STAGED_FILES|of-type:php} --sequential", - "conditions": [ - { - "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType", - "args": ["php"] - } - ] - } - ] - }, - "post-change": { - "enabled": true, - "actions": [ - { - "action": "composer install", - "options": [], - "conditions": [ - { - "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileChanged\\Any", - "args": [ - [ - "composer.json", - "composer.lock" - ] - ] - } - ] - } - ] - } -} +{ + "pre-push": { + "enabled": true, + "actions": [ + { + "action": "php vendor/bin/phpstan analyse --memory-limit 300M" + } + ] + }, + "pre-commit": { + "enabled": true, + "actions": [ + { + "action": "php vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --dry-run --diff {$STAGED_FILES|of-type:php} --sequential", + "conditions": [ + { + "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType", + "args": ["php"] + } + ] + }, + { + "action": "bin/spc dev:lint-config --check", + "conditions": [ + { + "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\InDirectory", + "args": ["config"] + } + ] + } + ] + }, + "post-change": { + "enabled": true, + "actions": [ + { + "action": "composer install", + "options": [], + "conditions": [ + { + "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileChanged\\Any", + "args": [ + [ + "composer.json", + "composer.lock" + ] + ] + } + ] + } + ] + } +} diff --git a/composer.json b/composer.json index fbdbc94b8..c5470b54b 100644 --- a/composer.json +++ b/composer.json @@ -50,6 +50,7 @@ "scripts": { "analyse": "phpstan analyse --memory-limit 300M", "cs-fix": "php-cs-fixer fix", + "lint-config": "bin/spc dev:lint-config", "test": "vendor/bin/phpunit tests/ --no-coverage", "build:phar": "vendor/bin/box compile" }, diff --git a/src/StaticPHP/Command/Dev/LintConfigCommand.php b/src/StaticPHP/Command/Dev/LintConfigCommand.php index 8149af14d..d0e4cfa1a 100644 --- a/src/StaticPHP/Command/Dev/LintConfigCommand.php +++ b/src/StaticPHP/Command/Dev/LintConfigCommand.php @@ -7,6 +7,7 @@ use StaticPHP\Command\BaseCommand; use StaticPHP\Registry\Registry; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Yaml\Yaml; #[AsCommand('dev:lint-config', 'Lint configuration file format', ['dev:sort-config'])] @@ -14,15 +15,28 @@ class LintConfigCommand extends BaseCommand { public function handle(): int { + $checkOnly = $this->input->getOption('check'); + $hasChanges = false; + // get loaded configs $loded_configs = Registry::getLoadedArtifactConfigs(); foreach ($loded_configs as $file) { - $this->sortConfigFile($file, 'artifact'); + if ($this->sortConfigFile($file, 'artifact', $checkOnly)) { + $hasChanges = true; + } } $loaded_pkg_configs = Registry::getLoadedPackageConfigs(); foreach ($loaded_pkg_configs as $file) { - $this->sortConfigFile($file, 'package'); + if ($this->sortConfigFile($file, 'package', $checkOnly)) { + $hasChanges = true; + } } + + if ($checkOnly && $hasChanges) { + $this->output->writeln('Some config files need sorting. Run "bin/spc dev:lint-config" to fix them.'); + return static::FAILURE; + } + return static::SUCCESS; } @@ -88,22 +102,27 @@ public function packageSortKey(string $a, string $b): int return $a <=> $b; } - private function sortConfigFile(mixed $file, string $config_type): void + protected function configure(): void + { + $this->addOption('check', null, InputOption::VALUE_NONE, 'Check if config files need sorting without modifying them'); + } + + private function sortConfigFile(mixed $file, string $config_type, bool $checkOnly): bool { // read file content with different extensions $content = file_get_contents($file); if ($content === false) { - $this->output->writeln("Failed to read artifact config file: {$file}"); - return; + $this->output->writeln("Failed to read config file: {$file}"); + return false; } $data = match (pathinfo($file, PATHINFO_EXTENSION)) { 'json' => json_decode($content, true), - 'yml', 'yaml' => Yaml::parse($content), // skip yaml files for now + 'yml', 'yaml' => Yaml::parse($content), default => null, }; if (!is_array($data)) { - $this->output->writeln("Invalid JSON format in artifact config file: {$file}"); - return; + $this->output->writeln("Invalid format in config file: {$file}"); + return false; } ksort($data); foreach ($data as $artifact_name => &$config) { @@ -115,7 +134,18 @@ private function sortConfigFile(mixed $file, string $config_type): void 'yml', 'yaml' => Yaml::dump($data, 4, 2), default => null, }; - file_put_contents($file, $new_content); - $this->output->writeln("Sorted artifact config file: {$file}"); + + // Check if content has changed + if ($content !== $new_content) { + if ($checkOnly) { + $this->output->writeln("File needs sorting: {$file}"); + return true; + } + file_put_contents($file, $new_content); + $this->output->writeln("Sorted config file: {$file}"); + return true; + } + + return false; } } From d999bfcd11dcfaf81d9ec192858eef14a704e482 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 10:25:43 +0800 Subject: [PATCH 68/99] Add libavif --- config/pkg/lib/libavif.yml | 11 +++++++++++ src/Package/Library/libavif.php | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 config/pkg/lib/libavif.yml create mode 100644 src/Package/Library/libavif.php diff --git a/config/pkg/lib/libavif.yml b/config/pkg/lib/libavif.yml new file mode 100644 index 000000000..0d7ae151d --- /dev/null +++ b/config/pkg/lib/libavif.yml @@ -0,0 +1,11 @@ +libavif: + type: library + artifact: + source: + type: ghtar + repo: AOMediaCodec/libavif + metadata: + license-files: [LICENSE] + license: BSD-2-Clause + static-libs@unix: + - libavif.a diff --git a/src/Package/Library/libavif.php b/src/Package/Library/libavif.php new file mode 100644 index 000000000..87e6c650f --- /dev/null +++ b/src/Package/Library/libavif.php @@ -0,0 +1,25 @@ +addConfigureArgs('-DAVIF_LIBYUV=OFF') + ->build(); + // patch pkgconfig + $lib->patchPkgconfPrefix(['libavif.pc']); + } +} From 880bb8799b012710fbe857900b8139db38e40550 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 10:55:59 +0800 Subject: [PATCH 69/99] Add libevent and postinstall action adder for library package --- config/pkg/lib/libevent.yml | 18 ++++++ src/Package/Library/libevent.php | 81 ++++++++++++++++++++++++ src/StaticPHP/Package/LibraryPackage.php | 71 +++++++++++++++++++-- 3 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 config/pkg/lib/libevent.yml create mode 100644 src/Package/Library/libevent.php diff --git a/config/pkg/lib/libevent.yml b/config/pkg/lib/libevent.yml new file mode 100644 index 000000000..aa4d0d525 --- /dev/null +++ b/config/pkg/lib/libevent.yml @@ -0,0 +1,18 @@ +libevent: + type: library + artifact: + source: + type: ghrel + repo: libevent/libevent + match: libevent.+\.tar\.gz + prefer-stable: true + metadata: + license-files: [LICENSE] + license: BSD-3-Clause + depends@unix: + - openssl + static-libs@unix: + - libevent.a + - libevent_core.a + - libevent_extra.a + - libevent_openssl.a diff --git a/src/Package/Library/libevent.php b/src/Package/Library/libevent.php new file mode 100644 index 000000000..0e3dffe60 --- /dev/null +++ b/src/Package/Library/libevent.php @@ -0,0 +1,81 @@ +addPostinstallAction([ + 'action' => 'replace-path', + 'files' => [$cmake_file], + ]); + } + } + + #[BuildFor('Darwin')] + #[BuildFor('Linux')] + public function buildUnix(LibraryPackage $lib): void + { + $cmake = UnixCMakeExecutor::create($lib) + ->addConfigureArgs( + '-DEVENT__LIBRARY_TYPE=STATIC', + '-DEVENT__DISABLE_BENCHMARK=ON', + '-DEVENT__DISABLE_THREAD_SUPPORT=ON', + '-DEVENT__DISABLE_TESTS=ON', + '-DEVENT__DISABLE_SAMPLES=ON', + '-DEVENT__DISABLE_MBEDTLS=ON ', + ); + if (version_compare(get_cmake_version(), '4.0.0', '>=')) { + $cmake->addConfigureArgs('-DCMAKE_POLICY_VERSION_MINIMUM=3.10'); + } + $cmake->build(); + + $lib->patchPkgconfPrefix(['libevent.pc', 'libevent_core.pc', 'libevent_extra.pc', 'libevent_openssl.pc']); + + $lib->patchPkgconfPrefix( + ['libevent_openssl.pc'], + PKGCONF_PATCH_CUSTOM, + [ + '/Libs.private:.*/m', + 'Libs.private: -lssl -lcrypto', + ] + ); + } +} diff --git a/src/StaticPHP/Package/LibraryPackage.php b/src/StaticPHP/Package/LibraryPackage.php index 0567f92b8..8b392bab3 100644 --- a/src/StaticPHP/Package/LibraryPackage.php +++ b/src/StaticPHP/Package/LibraryPackage.php @@ -21,6 +21,12 @@ */ class LibraryPackage extends Package { + /** + * Custom postinstall actions for this package. + * @var array + */ + private array $customPostinstallActions = []; + public function isInstalled(): bool { foreach (PackageConfig::get($this->getName(), 'static-libs', []) as $lib) { @@ -52,6 +58,24 @@ public function isInstalled(): bool return true; } + /** + * Add a custom postinstall action for this package. + * Available actions: + * - replace-path: Replace placeholders with actual paths + * Example: ['action' => 'replace-path', 'files' => ['lib/cmake/xxx.cmake']] + * - replace-to-env: Replace string with environment variable value + * Example: ['action' => 'replace-to-env', 'file' => 'bin/xxx-config', 'search' => 'XXX', 'replace-env' => 'BUILD_ROOT_PATH'] + * + * @param array $action Action array with 'action' key and other required keys + */ + public function addPostinstallAction(array $action): void + { + if (!isset($action['action'])) { + throw new WrongUsageException('Postinstall action must have "action" key.'); + } + $this->customPostinstallActions[] = $action; + } + public function patchLaDependencyPrefix(?array $files = null): void { logger()->info("Patching library {$this->name} la files"); @@ -234,14 +258,49 @@ public function packPrebuilt(): void } } - // generate postinstall action file if there are files to process + // collect all postinstall actions + $postinstall_actions = []; + + // add default replace-path action if there are .pc/.la files if ($postinstall_files !== []) { - $postinstall_actions = [ - [ - 'action' => 'replace-path', - 'files' => $postinstall_files, - ], + $postinstall_actions[] = [ + 'action' => 'replace-path', + 'files' => $postinstall_files, ]; + } + + // merge custom postinstall actions and handle files for replace-path actions + foreach ($this->customPostinstallActions as $action) { + // if action is replace-path, process the files with placeholder replacement + if ($action['action'] === 'replace-path') { + $files = $action['files'] ?? []; + if (!is_array($files)) { + $files = [$files]; + } + foreach ($files as $file) { + if (file_exists(BUILD_ROOT_PATH . '/' . $file)) { + $content = FileSystem::readFile(BUILD_ROOT_PATH . '/' . $file); + $origin_files[$file] = $content; + // replace actual paths with placeholders + $content = str_replace( + array_keys($placeholder), + array_values($placeholder), + $content + ); + FileSystem::writeFile(BUILD_ROOT_PATH . '/' . $file, $content); + // ensure this file is included in the package + if (!in_array($file, $increase_files, true)) { + $increase_files[] = $file; + } + } + } + } + // add custom action to postinstall actions + $postinstall_actions[] = $action; + } + + // generate postinstall action file if there are actions to process + if ($postinstall_actions !== []) { FileSystem::writeFile($postinstall_file, json_encode($postinstall_actions, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); $increase_files[] = '.package.' . $this->getName() . '.postinstall.json'; } From a832cc2114d106ca6f02382ed50153ab663b7ada Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 11:08:16 +0800 Subject: [PATCH 70/99] Add libffi --- config/pkg/lib/libffi.yml | 16 ++++++++++++++ src/Package/Library/libffi.php | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 config/pkg/lib/libffi.yml create mode 100644 src/Package/Library/libffi.php diff --git a/config/pkg/lib/libffi.yml b/config/pkg/lib/libffi.yml new file mode 100644 index 000000000..a33956844 --- /dev/null +++ b/config/pkg/lib/libffi.yml @@ -0,0 +1,16 @@ +libffi: + type: library + artifact: + source: + type: ghrel + repo: libffi/libffi + match: libffi.+\.tar\.gz + prefer-stable: true + metadata: + license-files: [LICENSE] + license: MIT + headers@unix: + - ffi.h + - ffitarget.h + static-libs@unix: + - libffi.a diff --git a/src/Package/Library/libffi.php b/src/Package/Library/libffi.php new file mode 100644 index 000000000..351b9076c --- /dev/null +++ b/src/Package/Library/libffi.php @@ -0,0 +1,40 @@ +configure()->make(); + + if (is_file("{$this->getBuildRootPath()}/lib64/libffi.a")) { + copy("{$this->getBuildRootPath()}/lib64/libffi.a", "{$this->getBuildRootPath()}/lib/libffi.a"); + unlink("{$this->getBuildRootPath()}/lib64/libffi.a"); + } + $this->patchPkgconfPrefix(['libffi.pc']); + } + + #[BuildFor('Darwin')] + public function buildDarwin(): void + { + $arch = getenv('SPC_ARCH'); + UnixAutoconfExecutor::create($this) + ->configure( + "--host={$arch}-apple-darwin", + "--target={$arch}-apple-darwin", + ) + ->make(); + $this->patchPkgconfPrefix(['libffi.pc']); + } +} From 272338775ef0d411da596bb97a9b44dd08081e5d Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 11:17:37 +0800 Subject: [PATCH 71/99] Add liblz4 --- config/pkg/lib/liblz4.yml | 13 ++++++++++ src/Package/Library/liblz4.php | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 config/pkg/lib/liblz4.yml create mode 100644 src/Package/Library/liblz4.php diff --git a/config/pkg/lib/liblz4.yml b/config/pkg/lib/liblz4.yml new file mode 100644 index 000000000..298b3abf3 --- /dev/null +++ b/config/pkg/lib/liblz4.yml @@ -0,0 +1,13 @@ +liblz4: + type: library + artifact: + source: + type: ghrel + repo: lz4/lz4 + match: lz4-.+\.tar\.gz + prefer-stable: true + metadata: + license-files: [LICENSE] + license: BSD-2-Clause + static-libs@unix: + - liblz4.a diff --git a/src/Package/Library/liblz4.php b/src/Package/Library/liblz4.php new file mode 100644 index 000000000..fb52a4fba --- /dev/null +++ b/src/Package/Library/liblz4.php @@ -0,0 +1,46 @@ +getSourceDir() . '/programs/Makefile', 'install: lz4', "install: lz4\n\ninstallewfwef: lz4"); + } + + #[BuildFor('Darwin')] + #[BuildFor('Linux')] + public function buildUnix(LibraryPackage $lib, PackageBuilder $builder): void + { + shell()->cd($lib->getSourceDir())->initializeEnv($lib) + ->exec("make PREFIX='' clean") + ->exec("make lib -j{$builder->concurrency} PREFIX=''"); + + FileSystem::replaceFileStr("{$lib->getSourceDir()}/Makefile", '$(MAKE) -C $(PRGDIR) $@', ''); + + shell()->cd($lib->getSourceDir()) + ->exec("make install PREFIX='' DESTDIR={$lib->getBuildRootPath()}"); + + $lib->patchPkgconfPrefix(['liblz4.pc']); + + foreach (FileSystem::scanDirFiles($lib->getLibDir(), false, true) as $filename) { + if (str_starts_with($filename, 'liblz4') && (str_contains($filename, '.so') || str_ends_with($filename, '.dylib'))) { + unlink("{$lib->getLibDir()}/{$filename}"); + } + } + } +} From e9a411cc66aa4e28b40553a41d09379e5743545c Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 11:21:03 +0800 Subject: [PATCH 72/99] Add libmaxminddb --- config/pkg/lib/libmaxminddb.yml | 16 ++++++++++++++++ src/Package/Library/libmaxminddb.php | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 config/pkg/lib/libmaxminddb.yml create mode 100644 src/Package/Library/libmaxminddb.php diff --git a/config/pkg/lib/libmaxminddb.yml b/config/pkg/lib/libmaxminddb.yml new file mode 100644 index 000000000..a0c3a307f --- /dev/null +++ b/config/pkg/lib/libmaxminddb.yml @@ -0,0 +1,16 @@ +libmaxminddb: + type: library + artifact: + source: + type: ghrel + repo: maxmind/libmaxminddb + match: libmaxminddb-.+\.tar\.gz + prefer-stable: true + metadata: + license-files: [LICENSE] + license: Apache-2.0 + headers: + - maxminddb.h + - maxminddb_config.h + static-libs@unix: + - libmaxminddb.a diff --git a/src/Package/Library/libmaxminddb.php b/src/Package/Library/libmaxminddb.php new file mode 100644 index 000000000..a045e4f12 --- /dev/null +++ b/src/Package/Library/libmaxminddb.php @@ -0,0 +1,26 @@ +addConfigureArgs( + '-DBUILD_TESTING=OFF', + '-DMAXMINDDB_BUILD_BINARIES=OFF', + ) + ->build(); + } +} From bd1153386512894b900ad212dfb37b54927398f8 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 11:25:19 +0800 Subject: [PATCH 73/99] Add libmemcached --- config/pkg/lib/libmemcached.yml | 16 ++++++++++++++++ src/Package/Library/libmemcached.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 config/pkg/lib/libmemcached.yml create mode 100644 src/Package/Library/libmemcached.php diff --git a/config/pkg/lib/libmemcached.yml b/config/pkg/lib/libmemcached.yml new file mode 100644 index 000000000..5f56f215d --- /dev/null +++ b/config/pkg/lib/libmemcached.yml @@ -0,0 +1,16 @@ +libmemcached: + type: library + artifact: + source: + type: ghtagtar + repo: awesomized/libmemcached + match: 1.\d.\d + metadata: + license-files: [LICENSE] + license: BSD-3-Clause + lang: cpp + static-libs@unix: + - libmemcached.a + - libmemcachedprotocol.a + - libmemcachedutil.a + - libhashkit.a diff --git a/src/Package/Library/libmemcached.php b/src/Package/Library/libmemcached.php new file mode 100644 index 000000000..ea632192a --- /dev/null +++ b/src/Package/Library/libmemcached.php @@ -0,0 +1,28 @@ +addConfigureArgs('-DCMAKE_INSTALL_RPATH=""') + ->build(); + } + + #[BuildFor('Darwin')] + public function buildDarwin(): void + { + UnixCMakeExecutor::create($this)->build(); + } +} From f2d389d89a14ad28597fb10fb1202bfeef40e357 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 11:28:16 +0800 Subject: [PATCH 74/99] Add librabbitmq --- config/pkg/lib/librabbitmq.yml | 12 ++++++++++++ src/Package/Library/librabbitmq.php | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 config/pkg/lib/librabbitmq.yml create mode 100644 src/Package/Library/librabbitmq.php diff --git a/config/pkg/lib/librabbitmq.yml b/config/pkg/lib/librabbitmq.yml new file mode 100644 index 000000000..c58b13a03 --- /dev/null +++ b/config/pkg/lib/librabbitmq.yml @@ -0,0 +1,12 @@ +librabbitmq: + type: library + artifact: + source: + type: ghtar + repo: alanxz/rabbitmq-c + prefer-stable: true + metadata: + license-files: [LICENSE] + license: MIT + depends: + - openssl diff --git a/src/Package/Library/librabbitmq.php b/src/Package/Library/librabbitmq.php new file mode 100644 index 000000000..2350ea5e8 --- /dev/null +++ b/src/Package/Library/librabbitmq.php @@ -0,0 +1,21 @@ +addConfigureArgs('-DBUILD_STATIC_LIBS=ON')->build(); + } +} From 4cfd8f4ca32b641227d61efba5140e5ea652d7b8 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 11:45:14 +0800 Subject: [PATCH 75/99] Add librdkafka --- config/pkg/lib/librdkafka.yml | 19 ++++++++++ src/Package/Library/librdkafka.php | 59 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 config/pkg/lib/librdkafka.yml create mode 100644 src/Package/Library/librdkafka.php diff --git a/config/pkg/lib/librdkafka.yml b/config/pkg/lib/librdkafka.yml new file mode 100644 index 000000000..cb83fb43c --- /dev/null +++ b/config/pkg/lib/librdkafka.yml @@ -0,0 +1,19 @@ +librdkafka: + type: library + artifact: + source: + type: ghtar + repo: confluentinc/librdkafka + metadata: + license-files: [LICENSE] + license: BSD-2-Clause + suggests: + - curl + - liblz4 + - openssl + - zlib + - zstd + lang: cpp + pkg-configs: + - rdkafka++-static + - rdkafka-static diff --git a/src/Package/Library/librdkafka.php b/src/Package/Library/librdkafka.php new file mode 100644 index 000000000..29571c7da --- /dev/null +++ b/src/Package/Library/librdkafka.php @@ -0,0 +1,59 @@ +getSourceDir() . '/lds-gen.py', + "funcs.append('rd_ut_coverage_check')", + '' + ); + FileSystem::replaceFileStr( + $this->getSourceDir() . '/src/rd.h', + '#error "IOV_MAX not defined"', + "#define IOV_MAX 1024\n#define __GNU__" + ); + // Fix OAuthBearer OIDC flag + FileSystem::replaceFileStr( + $this->getSourceDir() . '/src/rdkafka_conf.c', + '#ifdef WITH_OAUTHBEARER_OIDC', + '#if WITH_OAUTHBEARER_OIDC' + ); + return true; + } + + #[BuildFor('Darwin')] + #[BuildFor('Linux')] + public function buildUnix(): void + { + UnixCMakeExecutor::create($this) + ->optionalPackage('zstd', ...cmake_boolean_args('WITH_ZSTD')) + ->optionalPackage('curl', ...cmake_boolean_args('WITH_CURL')) + ->optionalPackage('openssl', ...cmake_boolean_args('WITH_SSL')) + ->optionalPackage('zlib', ...cmake_boolean_args('WITH_ZLIB')) + ->optionalPackage('liblz4', ...cmake_boolean_args('ENABLE_LZ4_EXT')) + ->addConfigureArgs( + '-DWITH_SASL=OFF', + '-DRDKAFKA_BUILD_STATIC=ON', + '-DRDKAFKA_BUILD_EXAMPLES=OFF', + '-DRDKAFKA_BUILD_TESTS=OFF', + ) + ->build(); + } +} From 127697b814db5f0a5714eafe36c785c50746d20f Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 11:54:15 +0800 Subject: [PATCH 76/99] Add liburing --- config/pkg/lib/liburing.yml | 19 ++++++++++ src/Package/Library/liburing.php | 61 ++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 config/pkg/lib/liburing.yml create mode 100644 src/Package/Library/liburing.php diff --git a/config/pkg/lib/liburing.yml b/config/pkg/lib/liburing.yml new file mode 100644 index 000000000..58bda5240 --- /dev/null +++ b/config/pkg/lib/liburing.yml @@ -0,0 +1,19 @@ +liburing: + type: library + artifact: + source: + type: ghtar + repo: axboe/liburing + prefer-stable: true + metadata: + license-files: [COPYING] + license: LGPL-2.1-or-later + headers@linux: + - liburing/ + - liburing.h + pkg-configs: + - liburing + - liburing-ffi + static-libs@linux: + - liburing.a + - liburing-ffi.a diff --git a/src/Package/Library/liburing.php b/src/Package/Library/liburing.php new file mode 100644 index 000000000..ad396eac7 --- /dev/null +++ b/src/Package/Library/liburing.php @@ -0,0 +1,61 @@ +getSourceDir()}/configure", 'realpath -s', 'realpath'); + return true; + } + return false; + } + + #[BuildFor('Linux')] + public function buildLinux(ToolchainInterface $toolchain): void + { + $use_libc = !$toolchain instanceof GccNativeToolchain || version_compare(SystemTarget::getLibcVersion(), '2.30', '>='); + $make = UnixAutoconfExecutor::create($this); + + if ($use_libc) { + $make->appendEnv([ + 'CFLAGS' => '-D_GNU_SOURCE', + ]); + } + + $make + ->removeConfigureArgs( + '--disable-shared', + '--enable-static', + '--with-pic', + '--enable-pic', + ) + ->addConfigureArgs( + $use_libc ? '--use-libc' : '', + ) + ->configure() + ->make('library ENABLE_SHARED=0', 'install ENABLE_SHARED=0', with_clean: false); + + $this->patchPkgconfPrefix(); + } +} From fa1b71bebf49ce29a9375947f2112f4ff88324bb Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 12:26:35 +0800 Subject: [PATCH 77/99] Add libuuid --- config/pkg/lib/libuuid.yml | 14 ++++++++++++ src/Package/Library/libuuid.php | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 config/pkg/lib/libuuid.yml create mode 100644 src/Package/Library/libuuid.php diff --git a/config/pkg/lib/libuuid.yml b/config/pkg/lib/libuuid.yml new file mode 100644 index 000000000..65af3bc77 --- /dev/null +++ b/config/pkg/lib/libuuid.yml @@ -0,0 +1,14 @@ +libuuid: + type: library + artifact: + source: + type: git + url: 'https://github.com/static-php/libuuid.git' + rev: master + metadata: + license-files: [COPYING] + license: MIT + headers: + - uuid/uuid.h + static-libs@unix: + - libuuid.a diff --git a/src/Package/Library/libuuid.php b/src/Package/Library/libuuid.php new file mode 100644 index 000000000..3e49ad21f --- /dev/null +++ b/src/Package/Library/libuuid.php @@ -0,0 +1,40 @@ +toStep(2)->build(); + copy($this->getSourceDir() . '/build/libuuid.a', BUILD_LIB_PATH . '/libuuid.a'); + FileSystem::createDir(BUILD_INCLUDE_PATH . '/uuid'); + copy($this->getSourceDir() . '/uuid.h', BUILD_INCLUDE_PATH . '/uuid/uuid.h'); + $pc = FileSystem::readFile($this->getSourceDir() . '/uuid.pc.in'); + $pc = str_replace([ + '@prefix@', + '@exec_prefix@', + '@libdir@', + '@includedir@', + '@LIBUUID_VERSION@', + ], [ + BUILD_ROOT_PATH, + '${prefix}', + '${prefix}/lib', + '${prefix}/include', + '1.0.3', + ], $pc); + FileSystem::writeFile(BUILD_LIB_PATH . '/pkgconfig/uuid.pc', $pc); + } +} From d6af728e790420931dbf3e497e3991b6af279456 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 12:32:04 +0800 Subject: [PATCH 78/99] Add libuv --- config/pkg/lib/libuv.yml | 11 +++++++++++ src/Package/Library/libuv.php | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 config/pkg/lib/libuv.yml create mode 100644 src/Package/Library/libuv.php diff --git a/config/pkg/lib/libuv.yml b/config/pkg/lib/libuv.yml new file mode 100644 index 000000000..3c41906dc --- /dev/null +++ b/config/pkg/lib/libuv.yml @@ -0,0 +1,11 @@ +libuv: + type: library + artifact: + source: + type: ghtar + repo: libuv/libuv + metadata: + license-files: [LICENSE, LICENSE-extra] + license: MIT + static-libs@unix: + - libuv.a diff --git a/src/Package/Library/libuv.php b/src/Package/Library/libuv.php new file mode 100644 index 000000000..ed8c58381 --- /dev/null +++ b/src/Package/Library/libuv.php @@ -0,0 +1,25 @@ +addConfigureArgs('-DLIBUV_BUILD_SHARED=OFF') + ->build(); + // patch pkgconfig + $lib->patchPkgconfPrefix(['libuv-static.pc']); + } +} From 0c386e967afa29f56257c641104744a21f986ceb Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 12:37:02 +0800 Subject: [PATCH 79/99] Allow shell completion for build:libs command --- src/StaticPHP/Command/BuildLibsCommand.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/StaticPHP/Command/BuildLibsCommand.php b/src/StaticPHP/Command/BuildLibsCommand.php index a637fe7ce..63a3ad0f9 100644 --- a/src/StaticPHP/Command/BuildLibsCommand.php +++ b/src/StaticPHP/Command/BuildLibsCommand.php @@ -6,8 +6,10 @@ use StaticPHP\Artifact\DownloaderOptions; use StaticPHP\Package\PackageInstaller; +use StaticPHP\Registry\PackageLoader; use StaticPHP\Util\V2CompatLayer; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -16,7 +18,19 @@ class BuildLibsCommand extends BaseCommand { public function configure(): void { - $this->addArgument('libraries', InputArgument::REQUIRED, 'The library packages will be compiled, comma separated'); + $this->addArgument( + 'libraries', + InputArgument::REQUIRED, + 'The library packages will be compiled, comma separated', + suggestedValues: function (CompletionInput $input) { + $packages = []; + foreach (PackageLoader::getPackages(['target', 'library']) as $name => $_) { + $packages[] = $name; + } + $val = $input->getCompletionValue(); + return array_filter($packages, fn ($name) => str_starts_with($name, $val)); + } + ); // Builder options $this->getDefinition()->addOptions([ new InputOption('with-suggests', ['L', 'E'], null, 'Resolve and install suggested packages as well'), From 017fabd55675bd42b03acf2f5a3f1afd11c52ac8 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 12:46:26 +0800 Subject: [PATCH 80/99] Add libxslt --- config/pkg/lib/libxslt.yml | 15 ++++++++++ src/Package/Library/libxslt.php | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 config/pkg/lib/libxslt.yml create mode 100644 src/Package/Library/libxslt.php diff --git a/config/pkg/lib/libxslt.yml b/config/pkg/lib/libxslt.yml new file mode 100644 index 000000000..07955333a --- /dev/null +++ b/config/pkg/lib/libxslt.yml @@ -0,0 +1,15 @@ +libxslt: + type: library + artifact: + source: + type: filelist + url: 'https://download.gnome.org/sources/libxslt/1.1/' + regex: '/href="(?libxslt-(?[^"]+)\.tar\.xz)"/' + metadata: + license-files: [Copyright] + license: MIT + depends: + - libxml2 + static-libs@unix: + - libxslt.a + - libexslt.a diff --git a/src/Package/Library/libxslt.php b/src/Package/Library/libxslt.php new file mode 100644 index 000000000..11ba2bf84 --- /dev/null +++ b/src/Package/Library/libxslt.php @@ -0,0 +1,52 @@ + true, 'no_php'])->getPackageDepsConfig($lib->getName(), array_keys($installer->getResolvedPackages())); + $cpp = SystemTarget::getTargetOS() === 'Darwin' ? '-lc++' : '-lstdc++'; + $ac = UnixAutoconfExecutor::create($lib) + ->appendEnv([ + 'CFLAGS' => "-I{$lib->getIncludeDir()}", + 'LDFLAGS' => "-L{$lib->getLibDir()}", + 'LIBS' => "{$static_libs['libs']} {$cpp}", + ]) + ->addConfigureArgs( + '--without-python', + '--without-crypto', + '--without-debug', + '--without-debugger', + "--with-libxml-prefix={$lib->getBuildRootPath()}", + ); + if (getenv('SPC_LD_LIBRARY_PATH') && getenv('SPC_LIBRARY_PATH')) { + $ac->appendEnv([ + 'LD_LIBRARY_PATH' => getenv('SPC_LD_LIBRARY_PATH'), + 'LIBRARY_PATH' => getenv('SPC_LIBRARY_PATH'), + ]); + } + $ac->configure()->make(); + + $lib->patchPkgconfPrefix(['libexslt.pc', 'libxslt.pc']); + $lib->patchLaDependencyPrefix(); + $AR = getenv('AR') ?: 'ar'; + shell()->cd($lib->getLibDir()) + ->exec("{$AR} -t libxslt.a | grep '\\.a$' | xargs -n1 {$AR} d libxslt.a") + ->exec("{$AR} -t libexslt.a | grep '\\.a$' | xargs -n1 {$AR} d libexslt.a"); + } +} From b42601d288dfc507d95399e78ade9f4dd8b66680 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 13:02:24 +0800 Subject: [PATCH 81/99] Add libyaml --- config/pkg/lib/libyaml.yml | 15 +++++++++++++++ src/Package/Library/libyaml.php | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 config/pkg/lib/libyaml.yml create mode 100644 src/Package/Library/libyaml.php diff --git a/config/pkg/lib/libyaml.yml b/config/pkg/lib/libyaml.yml new file mode 100644 index 000000000..0d39e0b1d --- /dev/null +++ b/config/pkg/lib/libyaml.yml @@ -0,0 +1,15 @@ +libyaml: + type: library + artifact: + source: + type: ghrel + repo: yaml/libyaml + match: yaml-.+\.tar\.gz + prefer-stable: true + metadata: + license-files: [License] + license: MIT + headers: + - yaml.h + static-libs@unix: + - libyaml.a diff --git a/src/Package/Library/libyaml.php b/src/Package/Library/libyaml.php new file mode 100644 index 000000000..602c875c8 --- /dev/null +++ b/src/Package/Library/libyaml.php @@ -0,0 +1,21 @@ +configure()->make(); + } +} From 2874336f0edff67e397a78095e68b8ecb0cadc46 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 13:05:37 +0800 Subject: [PATCH 82/99] Add mimalloc --- config/pkg/lib/mimalloc.yml | 12 ++++++++++++ src/Package/Library/mimalloc.php | 31 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 config/pkg/lib/mimalloc.yml create mode 100644 src/Package/Library/mimalloc.php diff --git a/config/pkg/lib/mimalloc.yml b/config/pkg/lib/mimalloc.yml new file mode 100644 index 000000000..4ab343ab8 --- /dev/null +++ b/config/pkg/lib/mimalloc.yml @@ -0,0 +1,12 @@ +mimalloc: + type: library + artifact: + source: + type: ghtagtar + repo: microsoft/mimalloc + match: 'v2\.\d\.[^3].*' + metadata: + license-files: [LICENSE] + license: MIT + static-libs@unix: + - libmimalloc.a diff --git a/src/Package/Library/mimalloc.php b/src/Package/Library/mimalloc.php new file mode 100644 index 000000000..fd7a73513 --- /dev/null +++ b/src/Package/Library/mimalloc.php @@ -0,0 +1,31 @@ +addConfigureArgs( + '-DMI_BUILD_SHARED=OFF', + '-DMI_BUILD_OBJECT=OFF', + '-DMI_INSTALL_TOPLEVEL=ON', + ); + if (SystemTarget::getLibc() === 'musl') { + $cmake->addConfigureArgs('-DMI_LIBC_MUSL=ON'); + } + $cmake->build(); + } +} From 9912b213f0e093c937adab8aa207db2fa9c494f6 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 13:29:25 +0800 Subject: [PATCH 83/99] Add net-snmp --- config/pkg/lib/net-snmp.yml | 15 +++++++++ src/Package/Library/net_snmp.php | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 config/pkg/lib/net-snmp.yml create mode 100644 src/Package/Library/net_snmp.php diff --git a/config/pkg/lib/net-snmp.yml b/config/pkg/lib/net-snmp.yml new file mode 100644 index 000000000..b1e9912ec --- /dev/null +++ b/config/pkg/lib/net-snmp.yml @@ -0,0 +1,15 @@ +net-snmp: + type: library + artifact: + source: + type: ghtagtar + repo: net-snmp/net-snmp + metadata: + license-files: [COPYING] + license: 'BSD-3-Clause AND MIT' + depends: + - openssl + - zlib + pkg-configs: + - netsnmp + - netsnmp-agent diff --git a/src/Package/Library/net_snmp.php b/src/Package/Library/net_snmp.php new file mode 100644 index 000000000..cb7b2aaea --- /dev/null +++ b/src/Package/Library/net_snmp.php @@ -0,0 +1,57 @@ +getSourceDir()}/configure", 'LIBS="-lssl ${OPENSSL_LIBS}"', 'LIBS="-lssl ${OPENSSL_LIBS} -lpthread -ldl"'); + } + + #[BuildFor('Darwin')] + #[BuildFor('Linux')] + public function buildUnix(LibraryPackage $lib): void + { + // use --static for PKG_CONFIG + UnixAutoconfExecutor::create($lib) + ->setEnv(['PKG_CONFIG' => getenv('PKG_CONFIG') . ' --static']) + ->configure( + '--disable-mibs', + '--without-nl', + '--disable-agent', + '--disable-applications', + '--disable-manuals', + '--disable-scripts', + '--disable-embedded-perl', + '--without-perl-modules', + '--with-out-mib-modules="if-mib host disman/event-mib ucd-snmp/diskio mibII"', + '--with-out-transports="Unix"', + '--with-mib-modules=""', + '--enable-mini-agent', + '--with-default-snmp-version="3"', + '--with-sys-contact="@@no.where"', + '--with-sys-location="Unknown"', + '--with-logfile="/var/log/snmpd.log"', + '--with-persistent-directory="/var/lib/net-snmp"', + "--with-openssl={$lib->getBuildRootPath()}", + "--with-zlib={$lib->getBuildRootPath()}", + )->make(with_install: 'installheaders installlibs install_pkgconfig'); + $lib->patchPkgconfPrefix(); + } +} From aad710ed3e2adeb4c3f0ed072128488aa9a41181 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 13:40:18 +0800 Subject: [PATCH 84/99] Add postgresql --- config/pkg/lib/postgresql.yml | 23 ++++++ src/Package/Library/postgresql.php | 119 ++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 config/pkg/lib/postgresql.yml diff --git a/config/pkg/lib/postgresql.yml b/config/pkg/lib/postgresql.yml new file mode 100644 index 000000000..1237b1846 --- /dev/null +++ b/config/pkg/lib/postgresql.yml @@ -0,0 +1,23 @@ +postgresql: + type: library + artifact: + source: + type: ghtagtar + repo: postgres/postgres + match: REL_18_\d+ + metadata: + license-files: [COPYRIGHT] + license: PostgreSQL + depends: + - libiconv + - libxml2 + - openssl + - zlib + - libedit + suggests: + - icu + - libxslt + - ldap + - zstd + pkg-configs: + - libpq diff --git a/src/Package/Library/postgresql.php b/src/Package/Library/postgresql.php index bd96da2c9..84b4657e0 100644 --- a/src/Package/Library/postgresql.php +++ b/src/Package/Library/postgresql.php @@ -6,12 +6,22 @@ use Package\Target\php; use StaticPHP\Attribute\Package\BeforeStage; +use StaticPHP\Attribute\Package\BuildFor; use StaticPHP\Attribute\Package\Library; +use StaticPHP\Attribute\Package\PatchBeforeBuild; use StaticPHP\Attribute\PatchDescription; +use StaticPHP\Exception\FileSystemException; +use StaticPHP\Package\LibraryPackage; +use StaticPHP\Package\PackageBuilder; +use StaticPHP\Package\PackageInstaller; use StaticPHP\Package\TargetPackage; +use StaticPHP\Runtime\SystemTarget; +use StaticPHP\Util\FileSystem; +use StaticPHP\Util\PkgConfigUtil; +use StaticPHP\Util\SPCConfigUtil; #[Library('postgresql')] -class postgresql +class postgresql extends LibraryPackage { #[BeforeStage('php', [php::class, 'configureForUnix'], 'postgresql')] #[PatchDescription('Patch to avoid explicit_bzero detection issues on some systems')] @@ -20,4 +30,111 @@ public function patchBeforePHPConfigure(TargetPackage $package): void shell()->cd($package->getSourceDir()) ->exec('sed -i.backup "s/ac_cv_func_explicit_bzero\" = xyes/ac_cv_func_explicit_bzero\" = x_fake_yes/" ./configure'); } + + #[PatchBeforeBuild] + #[PatchDescription('Various patches before building PostgreSQL')] + public function patchBeforeBuild(): bool + { + // fix aarch64 build on glibc 2.17 (e.g. CentOS 7) + if (SystemTarget::getLibcVersion() === '2.17' && SystemTarget::getTargetArch() === 'aarch64') { + try { + FileSystem::replaceFileStr("{$this->getSourceDir()}/src/port/pg_popcount_aarch64.c", 'HWCAP_SVE', '0'); + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/src/port/pg_crc32c_armv8_choose.c", + '#if defined(__linux__) && !defined(__aarch64__) && !defined(HWCAP2_CRC32)', + '#if defined(__linux__) && !defined(HWCAP_CRC32)' + ); + } catch (FileSystemException) { + // allow file not-existence to make it compatible with old and new version + } + } + + // skip the test on platforms where libpq infrastructure may be provided by statically-linked libraries + FileSystem::replaceFileStr("{$this->getSourceDir()}/src/interfaces/libpq/Makefile", 'invokes exit\'; exit 1;', 'invokes exit\';'); + // disable shared libs build + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/src/Makefile.shlib", + [ + '$(LINK.shared) -o $@ $(OBJS) $(LDFLAGS) $(LDFLAGS_SL) $(SHLIB_LINK)', + '$(INSTALL_SHLIB) $< \'$(DESTDIR)$(pkglibdir)/$(shlib)\'', + '$(INSTALL_SHLIB) $< \'$(DESTDIR)$(libdir)/$(shlib)\'', + '$(INSTALL_SHLIB) $< \'$(DESTDIR)$(bindir)/$(shlib)\'', + ], + '' + ); + return true; + } + + #[BuildFor('Darwin')] + #[BuildFor('Linux')] + public function buildUnix(PackageInstaller $installer, PackageBuilder $builder): void + { + $spc_config = new SPCConfigUtil(['no_php' => true, 'libs_only_deps' => true]); + $config = $spc_config->getPackageDepsConfig('postgresql', array_keys($installer->getResolvedPackages()), include_suggests: $builder->getOption('with-suggests', false)); + + $env_vars = [ + 'CFLAGS' => $config['cflags'] . ' -std=c17', + 'CPPFLAGS' => '-DPIC', + 'LDFLAGS' => $config['ldflags'], + 'LIBS' => $config['libs'], + ]; + + if ($ldLibraryPath = getenv('SPC_LD_LIBRARY_PATH')) { + $env_vars['LD_LIBRARY_PATH'] = $ldLibraryPath; + } + + FileSystem::resetDir("{$this->getSourceDir()}/build"); + + // PHP source relies on the non-private encoding functions in libpgcommon.a + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/src/common/Makefile", + '$(OBJS_FRONTEND): CPPFLAGS += -DUSE_PRIVATE_ENCODING_FUNCS', + '$(OBJS_FRONTEND): CPPFLAGS += -UUSE_PRIVATE_ENCODING_FUNCS -DFRONTEND', + ); + + // configure + $shell = shell()->cd("{$this->getSourceDir()}/build")->initializeEnv($this) + ->appendEnv($env_vars) + ->exec( + '../configure ' . + "--prefix={$this->getBuildRootPath()} " . + '--enable-coverage=no ' . + '--with-ssl=openssl ' . + '--with-readline ' . + '--with-libxml ' . + ($installer->isPackageResolved('icu') ? '--with-icu ' : '--without-icu ') . + ($installer->isPackageResolved('ldap') ? '--with-ldap ' : '--without-ldap ') . + ($installer->isPackageResolved('libxslt') ? '--with-libxslt ' : '--without-libxslt ') . + ($installer->isPackageResolved('zstd') ? '--with-zstd ' : '--without-zstd ') . + '--without-lz4 ' . + '--without-perl ' . + '--without-python ' . + '--without-pam ' . + '--without-bonjour ' . + '--without-tcl ' + ); + + // patch ldap lib + if ($installer->isPackageResolved('ldap')) { + $libs = PkgConfigUtil::getLibsArray('ldap'); + $libs = clean_spaces(implode(' ', $libs)); + FileSystem::replaceFileStr("{$this->getSourceDir()}/build/config.status", '-lldap', $libs); + FileSystem::replaceFileStr("{$this->getSourceDir()}/build/src/Makefile.global", '-lldap', $libs); + } + + $shell + ->exec('make -C src/bin/pg_config install') + ->exec('make -C src/include install') + ->exec('make -C src/common install') + ->exec('make -C src/port install') + ->exec('make -C src/interfaces/libpq install'); + + // remove dynamic libs + shell()->cd($this->getSourceDir() . '/build') + ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.so.*") + ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.so") + ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.dylib"); + + FileSystem::replaceFileStr("{$this->getLibDir()}/pkgconfig/libpq.pc", '-lldap', '-lldap -llber'); + } } From 67bea25214b0b43183cfdfe114d9cb9adc202bd4 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 13:55:03 +0800 Subject: [PATCH 85/99] Add qdbm --- config/pkg/lib/qdbm.yml | 12 ++++++++++++ src/Package/Library/qdbm.php | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 config/pkg/lib/qdbm.yml create mode 100644 src/Package/Library/qdbm.php diff --git a/config/pkg/lib/qdbm.yml b/config/pkg/lib/qdbm.yml new file mode 100644 index 000000000..1b46e3049 --- /dev/null +++ b/config/pkg/lib/qdbm.yml @@ -0,0 +1,12 @@ +qdbm: + type: library + artifact: + source: + type: git + url: 'https://github.com/static-php/qdbm.git' + rev: main + metadata: + license-files: [COPYING] + license: 'GPL-2.0-only OR LGPL-2.1-only' + static-libs@unix: + - libqdbm.a diff --git a/src/Package/Library/qdbm.php b/src/Package/Library/qdbm.php new file mode 100644 index 000000000..3b5c276c8 --- /dev/null +++ b/src/Package/Library/qdbm.php @@ -0,0 +1,26 @@ +configure(); + FileSystem::replaceFileRegex($lib->getSourceDir() . '/Makefile', '/MYLIBS = libqdbm.a.*/m', 'MYLIBS = libqdbm.a'); + $ac->make(SystemTarget::getTargetOS() === 'Darwin' ? 'mac' : ''); + $lib->patchPkgconfPrefix(['qdbm.pc']); + } +} From 425010fbb0f2ee86d5d484fb33d59badc9a39f29 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 13:59:58 +0800 Subject: [PATCH 86/99] Add re2c --- config/pkg/target/re2c.yml | 14 ++++++++++++++ src/Package/Target/re2c.php | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 config/pkg/target/re2c.yml create mode 100644 src/Package/Target/re2c.php diff --git a/config/pkg/target/re2c.yml b/config/pkg/target/re2c.yml new file mode 100644 index 000000000..eb4f85f31 --- /dev/null +++ b/config/pkg/target/re2c.yml @@ -0,0 +1,14 @@ +re2c: + type: target + artifact: + source: + type: ghrel + repo: skvadrik/re2c + match: re2c.+\.tar\.xz + prefer-stable: true + source-mirror: 'https://dl.static-php.dev/static-php-cli/deps/re2c/re2c-4.3.tar.xz' + metadata: + license-files: [LICENSE] + license: 'MIT OR Apache-2.0' + static-bins@unix: + - re2c diff --git a/src/Package/Target/re2c.php b/src/Package/Target/re2c.php new file mode 100644 index 000000000..c718c1d83 --- /dev/null +++ b/src/Package/Target/re2c.php @@ -0,0 +1,38 @@ +addConfigureArgs( + '-DRE2C_BUILD_TESTS=OFF', + '-DRE2C_BUILD_EXAMPLES=OFF', + '-DRE2C_BUILD_DOCS=OFF', + '-DRE2C_BUILD_RE2D=OFF', + '-DRE2C_BUILD_RE2GO=OFF', + '-DRE2C_BUILD_RE2HS=OFF', + '-DRE2C_BUILD_RE2JAVA=OFF', + '-DRE2C_BUILD_RE2JS=OFF', + '-DRE2C_BUILD_RE2OCAML=OFF', + '-DRE2C_BUILD_RE2PY=OFF', + '-DRE2C_BUILD_RE2RUST=OFF', + '-DRE2C_BUILD_RE2SWIFT=OFF', + '-DRE2C_BUILD_RE2V=OFF', + '-DRE2C_BUILD_RE2ZIG=OFF', + ) + ->build(); + } +} From 6be4da26aade2fa5d1770e3e5ac778763bc3eb97 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 14:03:42 +0800 Subject: [PATCH 87/99] Add readline --- config/pkg/lib/readline.yml | 14 ++++++++++++++ src/Package/Library/readline.php | 27 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 config/pkg/lib/readline.yml create mode 100644 src/Package/Library/readline.php diff --git a/config/pkg/lib/readline.yml b/config/pkg/lib/readline.yml new file mode 100644 index 000000000..0cb80b00f --- /dev/null +++ b/config/pkg/lib/readline.yml @@ -0,0 +1,14 @@ +readline: + type: library + artifact: + source: + type: filelist + url: 'https://ftp.gnu.org/pub/gnu/readline/' + regex: '/href="(?readline-(?[^"]+)\.tar\.gz)"/' + metadata: + license-files: [COPYING] + license: GPL-3.0-or-later + depends: + - ncurses + static-libs@unix: + - libreadline.a diff --git a/src/Package/Library/readline.php b/src/Package/Library/readline.php new file mode 100644 index 000000000..a32334723 --- /dev/null +++ b/src/Package/Library/readline.php @@ -0,0 +1,27 @@ +configure( + '--with-curses', + '--enable-multibyte=yes', + ) + ->make(); + $lib->patchPkgconfPrefix(['readline.pc']); + } +} From fd40b92041c8ffe633cfb584481dffed10ca75ca Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 14:07:04 +0800 Subject: [PATCH 88/99] Add snappy --- config/pkg/lib/snappy.yml | 20 ++++++++++++++++++++ src/Package/Library/snappy.php | 27 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 config/pkg/lib/snappy.yml create mode 100644 src/Package/Library/snappy.php diff --git a/config/pkg/lib/snappy.yml b/config/pkg/lib/snappy.yml new file mode 100644 index 000000000..a369fa339 --- /dev/null +++ b/config/pkg/lib/snappy.yml @@ -0,0 +1,20 @@ +snappy: + type: library + artifact: + source: + type: git + rev: main + url: 'https://github.com/google/snappy' + metadata: + license-files: [COPYING] + license: BSD-3-Clause + depends: + - zlib + headers@unix: + - snappy.h + - snappy-c.h + - snappy-sinksource.h + - snappy-stubs-public.h + lang: cpp + static-libs@unix: + - libsnappy.a diff --git a/src/Package/Library/snappy.php b/src/Package/Library/snappy.php new file mode 100644 index 000000000..d822c3cfd --- /dev/null +++ b/src/Package/Library/snappy.php @@ -0,0 +1,27 @@ +setBuildDir("{$lib->getSourceDir()}/cmake/build") + ->addConfigureArgs( + '-DSNAPPY_BUILD_TESTS=OFF', + '-DSNAPPY_BUILD_BENCHMARKS=OFF', + ) + ->build('../..'); + } +} From d163c3dff68510ac9a78a9f13d388a1976ad350e Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 14:12:06 +0800 Subject: [PATCH 89/99] Add sqlite --- config/pkg/lib/sqlite.yml | 12 ++++++++++++ src/Package/Library/sqlite.php | 22 ++++++++++++++++++++++ src/globals/licenses/sqlite.txt | 6 ++++++ 3 files changed, 40 insertions(+) create mode 100644 config/pkg/lib/sqlite.yml create mode 100644 src/Package/Library/sqlite.php create mode 100644 src/globals/licenses/sqlite.txt diff --git a/config/pkg/lib/sqlite.yml b/config/pkg/lib/sqlite.yml new file mode 100644 index 000000000..fb6eecf35 --- /dev/null +++ b/config/pkg/lib/sqlite.yml @@ -0,0 +1,12 @@ +sqlite: + type: library + artifact: + source: 'https://www.sqlite.org/2024/sqlite-autoconf-3450200.tar.gz' + metadata: + license-files: ['@/sqlite.txt'] + license: Unlicense + headers: + - sqlite3.h + - sqlite3ext.h + static-libs@unix: + - libsqlite3.a diff --git a/src/Package/Library/sqlite.php b/src/Package/Library/sqlite.php new file mode 100644 index 000000000..ae802bfa9 --- /dev/null +++ b/src/Package/Library/sqlite.php @@ -0,0 +1,22 @@ +configure()->make(); + $lib->patchPkgconfPrefix(['sqlite3.pc']); + } +} diff --git a/src/globals/licenses/sqlite.txt b/src/globals/licenses/sqlite.txt new file mode 100644 index 000000000..f68a6c175 --- /dev/null +++ b/src/globals/licenses/sqlite.txt @@ -0,0 +1,6 @@ +The author disclaims copyright to this source code. In place of +a legal notice, here is a blessing: + + * May you do good and not evil. + * May you find forgiveness for yourself and forgive others. + * May you share freely, never taking more than you give. From a5f8402703624f639bfefe457771d6ae5c178bef Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 14:16:20 +0800 Subject: [PATCH 90/99] Add tidy --- config/pkg/lib/tidy.yml | 12 ++++++++++++ src/Package/Library/tidy.php | 31 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 config/pkg/lib/tidy.yml create mode 100644 src/Package/Library/tidy.php diff --git a/config/pkg/lib/tidy.yml b/config/pkg/lib/tidy.yml new file mode 100644 index 000000000..41487c1d3 --- /dev/null +++ b/config/pkg/lib/tidy.yml @@ -0,0 +1,12 @@ +tidy: + type: library + artifact: + source: + type: ghtar + repo: htacg/tidy-html5 + prefer-stable: true + metadata: + license-files: [README/LICENSE.md] + license: W3C + static-libs@unix: + - libtidy.a diff --git a/src/Package/Library/tidy.php b/src/Package/Library/tidy.php new file mode 100644 index 000000000..b59160262 --- /dev/null +++ b/src/Package/Library/tidy.php @@ -0,0 +1,31 @@ +setBuildDir("{$lib->getSourceDir()}/build-dir") + ->addConfigureArgs( + '-DSUPPORT_CONSOLE_APP=OFF', + '-DBUILD_SHARED_LIB=OFF' + ); + if (version_compare(get_cmake_version(), '4.0.0', '>=')) { + $cmake->addConfigureArgs('-DCMAKE_POLICY_VERSION_MINIMUM=3.5'); + } + $cmake->build(); + $lib->patchPkgconfPrefix(['tidy.pc']); + } +} From b6d8bf563981aed577ced3b36eadaa235b95dc41 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 14:20:41 +0800 Subject: [PATCH 91/99] Add unixodbc --- config/pkg/lib/unixodbc.yml | 13 ++++++++++ src/Package/Library/unixodbc.php | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 config/pkg/lib/unixodbc.yml create mode 100644 src/Package/Library/unixodbc.php diff --git a/config/pkg/lib/unixodbc.yml b/config/pkg/lib/unixodbc.yml new file mode 100644 index 000000000..af98916a0 --- /dev/null +++ b/config/pkg/lib/unixodbc.yml @@ -0,0 +1,13 @@ +unixodbc: + type: library + artifact: + source: 'https://www.unixodbc.org/unixODBC-2.3.12.tar.gz' + metadata: + license-files: [COPYING] + license: LGPL-2.1-only + depends: + - libiconv + static-libs@unix: + - libodbc.a + - libodbccr.a + - libodbcinst.a diff --git a/src/Package/Library/unixodbc.php b/src/Package/Library/unixodbc.php new file mode 100644 index 000000000..e482e68be --- /dev/null +++ b/src/Package/Library/unixodbc.php @@ -0,0 +1,43 @@ + match (SystemTarget::getTargetArch()) { + 'x86_64' => '/usr/local/etc', + 'aarch64' => '/opt/homebrew/etc', + default => throw new WrongUsageException('Unsupported architecture: ' . GNU_ARCH), + }, + 'Linux' => '/etc', + default => throw new WrongUsageException("Unsupported OS: {$os}"), + }; + UnixAutoconfExecutor::create($this) + ->configure( + '--disable-debug', + '--disable-dependency-tracking', + "--with-libiconv-prefix={$this->getBuildRootPath()}", + '--with-included-ltdl', + "--sysconfdir={$sysconf_selector}", + '--enable-gui=no', + ) + ->make(); + $this->patchPkgconfPrefix(['odbc.pc', 'odbccr.pc', 'odbcinst.pc']); + $this->patchLaDependencyPrefix(); + } +} From ca9dc25f9aa7f521199df86c98221c47ecfdcebb Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 14:26:24 +0800 Subject: [PATCH 92/99] Add watcher --- config/pkg/lib/watcher.yml | 15 +++++++++++++++ src/Package/Library/watcher.php | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 config/pkg/lib/watcher.yml create mode 100644 src/Package/Library/watcher.php diff --git a/config/pkg/lib/watcher.yml b/config/pkg/lib/watcher.yml new file mode 100644 index 000000000..6cf376f69 --- /dev/null +++ b/config/pkg/lib/watcher.yml @@ -0,0 +1,15 @@ +watcher: + type: library + artifact: + source: + type: ghtar + repo: e-dant/watcher + prefer-stable: true + metadata: + license-files: [license] + license: MIT + headers: + - wtr/watcher-c.h + lang: cpp + static-libs@unix: + - libwatcher-c.a diff --git a/src/Package/Library/watcher.php b/src/Package/Library/watcher.php new file mode 100644 index 000000000..56f93d931 --- /dev/null +++ b/src/Package/Library/watcher.php @@ -0,0 +1,32 @@ +getLibExtraCXXFlags(); + if (stripos($cflags, '-fpic') === false) { + $cflags .= ' -fPIC'; + } + $ldflags = $this->getLibExtraLdFlags() ? ' ' . $this->getLibExtraLdFlags() : ''; + shell()->cd("{$this->getSourceDir()}/watcher-c") + ->exec(getenv('CXX') . " -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra {$cflags}{$ldflags}") + ->exec(getenv('AR') . ' rcs libwatcher-c.a libwatcher-c.o'); + + copy("{$this->getSourceDir()}/watcher-c/libwatcher-c.a", "{$this->getLibDir()}/libwatcher-c.a"); + FileSystem::createDir("{$this->getIncludeDir()}/wtr"); + copy("{$this->getSourceDir()}/watcher-c/include/wtr/watcher-c.h", "{$this->getIncludeDir()}/wtr/watcher-c.h"); + } +} From 368461d1ad0baad57b3095f62785ef35d6ecb208 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 14:27:56 +0800 Subject: [PATCH 93/99] phpstan fix --- src/StaticPHP/Runtime/SystemTarget.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/StaticPHP/Runtime/SystemTarget.php b/src/StaticPHP/Runtime/SystemTarget.php index 584fa49a6..489f84384 100644 --- a/src/StaticPHP/Runtime/SystemTarget.php +++ b/src/StaticPHP/Runtime/SystemTarget.php @@ -88,8 +88,6 @@ public static function getTargetOS(): string /** * Returns the target architecture, e.g. x86_64, aarch64. * Currently, we only support 'x86_64' and 'aarch64' and both can only be built natively. - * - * @return 'aarch64'|'x86_64' */ public static function getTargetArch(): string { From c72a2b622845c8238c7ca4f157d4a1d564dc39d2 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 14:46:57 +0800 Subject: [PATCH 94/99] Refactor nasm,php-sdk-binary-tools,strawberry-perl,vswhere --- config/pkg.target.json | 21 --------------------- config/pkg/target/nasm.yml | 5 +++++ config/pkg/target/php-sdk-binary-tools.yml | 5 +++++ config/pkg/target/strawberry-perl.yml | 5 +++++ config/pkg/target/vswhere.yml | 5 +++++ spc.registry.json | 3 +-- src/StaticPHP/Package/PackageInstaller.php | 2 +- 7 files changed, 22 insertions(+), 24 deletions(-) delete mode 100644 config/pkg.target.json create mode 100644 config/pkg/target/nasm.yml create mode 100644 config/pkg/target/php-sdk-binary-tools.yml create mode 100644 config/pkg/target/strawberry-perl.yml create mode 100644 config/pkg/target/vswhere.yml diff --git a/config/pkg.target.json b/config/pkg.target.json deleted file mode 100644 index 59c6c65ac..000000000 --- a/config/pkg.target.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "nasm": { - "type": "target", - "artifact": "nasm" - }, - "php-sdk-binary-tools": { - "type": "target", - "artifact": "php-sdk-binary-tools" - }, - "strawberry-perl": { - "type": "target", - "artifact": "strawberry-perl" - }, - "vswhere": { - "type": "target", - "artifact": "vswhere", - "static-bins@windows": [ - "vswhere.exe" - ] - } -} diff --git a/config/pkg/target/nasm.yml b/config/pkg/target/nasm.yml new file mode 100644 index 000000000..3f483e8bb --- /dev/null +++ b/config/pkg/target/nasm.yml @@ -0,0 +1,5 @@ +nasm: + type: target + artifact: + binary: + windows-x86_64: { type: url, url: 'https://dl.static-php.dev/static-php-cli/deps/nasm/nasm-2.16.01-win64.zip', extract: { nasm.exe: '{php_sdk_path}/bin/nasm.exe', ndisasm.exe: '{php_sdk_path}/bin/ndisasm.exe' } } diff --git a/config/pkg/target/php-sdk-binary-tools.yml b/config/pkg/target/php-sdk-binary-tools.yml new file mode 100644 index 000000000..81180007e --- /dev/null +++ b/config/pkg/target/php-sdk-binary-tools.yml @@ -0,0 +1,5 @@ +php-sdk-binary-tools: + type: target + artifact: + binary: + windows-x86_64: { type: git, rev: master, url: 'https://github.com/php/php-sdk-binary-tools.git', extract: '{php_sdk_path}' } diff --git a/config/pkg/target/strawberry-perl.yml b/config/pkg/target/strawberry-perl.yml new file mode 100644 index 000000000..9e2e3187c --- /dev/null +++ b/config/pkg/target/strawberry-perl.yml @@ -0,0 +1,5 @@ +strawberry-perl: + type: target + artifact: + binary: + windows-x86_64: { type: url, url: 'https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip', extract: '{pkg_root_path}/strawberry-perl' } diff --git a/config/pkg/target/vswhere.yml b/config/pkg/target/vswhere.yml new file mode 100644 index 000000000..d7f8670cc --- /dev/null +++ b/config/pkg/target/vswhere.yml @@ -0,0 +1,5 @@ +vswhere: + type: target + artifact: + binary: + windows-x86_64: { type: url, url: 'https://github.com/microsoft/vswhere/releases/download/3.1.7/vswhere.exe', extract: '{pkg_root_path}/bin/vswhere.exe' } diff --git a/spc.registry.json b/spc.registry.json index cf49c6c83..b55f63a68 100644 --- a/spc.registry.json +++ b/spc.registry.json @@ -16,8 +16,7 @@ "config": [ "config/pkg/lib/", "config/pkg/target/", - "config/pkg/ext/", - "config/pkg.target.json" + "config/pkg/ext/" ] }, "artifact": { diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 80ea1aef7..169630f99 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -534,7 +534,7 @@ private function validatePackageArtifact(Package $package): void { // target and library must have at least source or platform binary if (in_array($package->getType(), ['library', 'target']) && !$package->getArtifact()?->hasSource() && !$package->getArtifact()?->hasPlatformBinary()) { - throw new WrongUsageException("Validation failed: Target package '{$package->getName()}' has no source or platform binary defined."); + throw new WrongUsageException("Validation failed: Target package '{$package->getName()}' has no source or current platform (" . SystemTarget::getCurrentPlatformString() . ') binary defined.'); } } From d8d9f389ba1a75df812b7a1b31d140df0aa043ea Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 16:33:13 +0800 Subject: [PATCH 95/99] Refactor patching logic for Alpine Linux and macOS in attr.php --- src/Package/Artifact/attr.php | 6 +++--- src/globals/functions.php | 21 +++++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Package/Artifact/attr.php b/src/Package/Artifact/attr.php index e80c8c831..9974a4a23 100644 --- a/src/Package/Artifact/attr.php +++ b/src/Package/Artifact/attr.php @@ -7,6 +7,7 @@ use StaticPHP\Artifact\Artifact; use StaticPHP\Attribute\Artifact\AfterSourceExtract; use StaticPHP\Attribute\PatchDescription; +use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\SourcePatcher; use StaticPHP\Util\System\LinuxUtil; @@ -16,8 +17,7 @@ class attr #[PatchDescription('Patch attr for Alpine Linux (musl) and macOS - gethostname declaration')] public function patchAttrForAlpine(Artifact $artifact): void { - if (PHP_OS_FAMILY === 'Darwin' || PHP_OS_FAMILY === 'Linux' && LinuxUtil::isMuslDist()) { - SourcePatcher::patchFile('attr_alpine_gethostname.patch', $artifact->getSourceDir()); - } + spc_skip_unless(SystemTarget::getTargetOS() === 'Darwin' || SystemTarget::getTargetOS() === 'Linux' && !LinuxUtil::isMuslDist(), 'Only for Alpine Linux (musl) and macOS'); + SourcePatcher::patchFile('attr_alpine_gethostname.patch', $artifact->getSourceDir()); } } diff --git a/src/globals/functions.php b/src/globals/functions.php index e04bad5e0..712cf621e 100644 --- a/src/globals/functions.php +++ b/src/globals/functions.php @@ -321,28 +321,29 @@ function get_display_path(string $path): string } /** - * Get the global DI container instance. + * Skip the current operation if the condition is true. + * You should ALWAYS use this function inside an attribute callback. * - * @deprecated Use ApplicationContext::getContainer() or dependency injection instead. - * This function is kept for backward compatibility during the migration period. + * @param bool $condition Condition to evaluate + * @param string $message Optional message for the skip exception */ -function spc_container(): DI\Container +function spc_skip_if(bool $condition, string $message = ''): void { - return \StaticPHP\DI\ApplicationContext::getContainer(); + if ($condition) { + throw new StaticPHP\Exception\SkipException($message); + } } /** - * Skip the current operation if the condition is true. + * Skip the current operation unless the condition is true. * You should ALWAYS use this function inside an attribute callback. * * @param bool $condition Condition to evaluate * @param string $message Optional message for the skip exception */ -function spc_skip_if(bool $condition, string $message = ''): void +function spc_skip_unless(bool $condition, string $message = ''): void { - if ($condition) { - throw new StaticPHP\Exception\SkipException($message); - } + spc_skip_if(!$condition, $message); } /** From 95f34fbbc35f27f29dbd28d711655414cb26d01b Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 16:33:34 +0800 Subject: [PATCH 96/99] Add extension amqp --- config/pkg/ext/ext-amqp.yml | 18 ++++++++++++++++++ src/Package/Extension/amqp.php | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 config/pkg/ext/ext-amqp.yml create mode 100644 src/Package/Extension/amqp.php diff --git a/config/pkg/ext/ext-amqp.yml b/config/pkg/ext/ext-amqp.yml new file mode 100644 index 000000000..937569144 --- /dev/null +++ b/config/pkg/ext/ext-amqp.yml @@ -0,0 +1,18 @@ +ext-amqp: + type: php-extension + artifact: + source: + type: url + url: 'https://pecl.php.net/get/amqp' + extract: php-src/ext/amqp + filename: amqp.tgz + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - librabbitmq + depends@windows: + - ext-openssl + php-extension: + arg-type: '--with-amqp@shared_suffix@ --with-librabbitmq-dir=@build_root_path@' + arg-type@windows: '--with-amqp' diff --git a/src/Package/Extension/amqp.php b/src/Package/Extension/amqp.php new file mode 100644 index 000000000..82e3c961b --- /dev/null +++ b/src/Package/Extension/amqp.php @@ -0,0 +1,26 @@ + Date: Fri, 6 Feb 2026 16:33:50 +0800 Subject: [PATCH 97/99] Add extension bcmath,openssl,zlib --- config/pkg/ext/builtin-extensions.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 config/pkg/ext/builtin-extensions.yml diff --git a/config/pkg/ext/builtin-extensions.yml b/config/pkg/ext/builtin-extensions.yml new file mode 100644 index 000000000..d12cd2191 --- /dev/null +++ b/config/pkg/ext/builtin-extensions.yml @@ -0,0 +1,21 @@ +ext-bcmath: + type: php-extension +ext-openssl: + type: php-extension + depends: + - openssl + - zlib + - ext-zlib + php-extension: + arg-type: custom + arg-type@windows: with + build-with-php: true +ext-zlib: + type: php-extension + depends: + - zlib + php-extension: + arg-type: custom + arg-type@windows: with + build-with-php: true + build-shared: false From 478b85879fde188ec5264c93a425a4b945ec5784 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 6 Feb 2026 16:34:51 +0800 Subject: [PATCH 98/99] Chore --- config/pkg/lib/librabbitmq.yml | 2 + src/Package/Artifact/bzip2.php | 6 +-- src/Package/Artifact/php_src.php | 16 +++---- .../Command/SwitchPhpVersionCommand.php | 11 ++--- src/Package/Target/php.php | 24 +++++++++-- src/Package/Target/php/unix.php | 2 +- src/Package/Target/pkgconfig.php | 2 +- src/SPC/store/SourcePatcher.php | 20 ++++----- src/StaticPHP/Package/LibraryPackage.php | 38 ++-------------- src/StaticPHP/Package/PackageBuilder.php | 3 ++ src/StaticPHP/Util/GlobalPathTrait.php | 43 +++++++++++++++++++ 11 files changed, 97 insertions(+), 70 deletions(-) create mode 100644 src/StaticPHP/Util/GlobalPathTrait.php diff --git a/config/pkg/lib/librabbitmq.yml b/config/pkg/lib/librabbitmq.yml index c58b13a03..da4e98562 100644 --- a/config/pkg/lib/librabbitmq.yml +++ b/config/pkg/lib/librabbitmq.yml @@ -10,3 +10,5 @@ librabbitmq: license: MIT depends: - openssl + static-libs@unix: + - librabbitmq.a diff --git a/src/Package/Artifact/bzip2.php b/src/Package/Artifact/bzip2.php index a6a1e58e5..b3cef4155 100644 --- a/src/Package/Artifact/bzip2.php +++ b/src/Package/Artifact/bzip2.php @@ -15,10 +15,6 @@ class bzip2 #[PatchDescription('Patch bzip2 Makefile to add -fPIC flag for position-independent code')] public function patchBzip2Makefile(Artifact $artifact): void { - FileSystem::replaceFileStr( - $artifact->getSourceDir() . '/Makefile', - 'CFLAGS=-Wall', - 'CFLAGS=-fPIC -Wall' - ); + FileSystem::replaceFileStr("{$artifact->getSourceDir()}/Makefile", 'CFLAGS=-Wall', 'CFLAGS=-fPIC -Wall'); } } diff --git a/src/Package/Artifact/php_src.php b/src/Package/Artifact/php_src.php index 498abc66e..ae9488d69 100644 --- a/src/Package/Artifact/php_src.php +++ b/src/Package/Artifact/php_src.php @@ -4,6 +4,7 @@ namespace Package\Artifact; +use Package\Target\php; use StaticPHP\Attribute\Artifact\AfterSourceExtract; use StaticPHP\Attribute\PatchDescription; use StaticPHP\Runtime\SystemTarget; @@ -16,9 +17,8 @@ class php_src #[PatchDescription('Patch PHP source for libxml2 2.12 compatibility on Alpine Linux')] public function patchPhpLibxml212(): void { - $file = file_get_contents(SOURCE_PATH . '/php-src/main/php_version.h'); - if (preg_match('/PHP_VERSION_ID (\d+)/', $file, $match) !== 0) { - $ver_id = intval($match[1]); + $ver_id = php::getPHPVersionID(return_null_if_failed: true); + if ($ver_id) { if ($ver_id < 80000) { SourcePatcher::patchFile('spc_fix_alpine_build_php80.patch', SOURCE_PATH . '/php-src'); return; @@ -39,9 +39,8 @@ public function patchPhpLibxml212(): void #[PatchDescription('Patch GD extension for Windows builds')] public function patchGDWin32(): void { - $file = file_get_contents(SOURCE_PATH . '/php-src/main/php_version.h'); - if (preg_match('/PHP_VERSION_ID (\d+)/', $file, $match) !== 0) { - $ver_id = intval($match[1]); + $ver_id = php::getPHPVersionID(return_null_if_failed: true); + if ($ver_id) { if ($ver_id < 80200) { // see: https://github.com/php/php-src/commit/243966177e39eb71822935042c3f13fa6c5b9eed FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/ext/gd/libgd/gdft.c', '#ifndef MSWIN32', '#ifndef _WIN32'); @@ -58,9 +57,8 @@ public function patchGDWin32(): void public function patchFfiCentos7FixO3strncmp(): void { spc_skip_if(!($ver = SystemTarget::getLibcVersion()) || version_compare($ver, '2.17', '>')); - spc_skip_if(!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')); - $file = file_get_contents(SOURCE_PATH . '/php-src/main/php_version.h'); - spc_skip_if(preg_match('/PHP_VERSION_ID (\d+)/', $file, $match) !== 0 && intval($match[1]) < 80316); + $ver_id = php::getPHPVersionID(return_null_if_failed: true); + spc_skip_if($ver_id === null || $ver_id < 80316); SourcePatcher::patchFile('ffi_centos7_fix_O3_strncmp.patch', SOURCE_PATH . '/php-src'); } diff --git a/src/Package/Command/SwitchPhpVersionCommand.php b/src/Package/Command/SwitchPhpVersionCommand.php index 3782a6452..a6594713d 100644 --- a/src/Package/Command/SwitchPhpVersionCommand.php +++ b/src/Package/Command/SwitchPhpVersionCommand.php @@ -4,6 +4,7 @@ namespace Package\Command; +use Package\Target\php; use StaticPHP\Artifact\ArtifactCache; use StaticPHP\Artifact\ArtifactDownloader; use StaticPHP\Artifact\DownloaderOptions; @@ -25,7 +26,7 @@ public function configure(): void $this->addArgument( 'php-version', InputArgument::REQUIRED, - 'PHP version (e.g., 8.4, 8.3, 8.2, 8.1, 8.0, 7.4, or specific like 8.4.5)', + 'PHP version (e.g., ' . implode(', ', php::SUPPORTED_MAJOR_VERSIONS) . ' or specific like 8.4.5)', ); // Downloader options @@ -42,7 +43,7 @@ public function handle(): int // Validate version format if (!$this->isValidPhpVersion($php_ver)) { $this->output->writeln("Invalid PHP version '{$php_ver}'!"); - $this->output->writeln('Supported formats: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, or specific version like 8.4.5'); + $this->output->writeln('Supported formats: ' . implode(', ', php::SUPPORTED_MAJOR_VERSIONS) . ', or specific version like 8.4.5'); return static::FAILURE; } @@ -101,13 +102,13 @@ public function handle(): int * Validate PHP version format. * * Accepts: - * - Major.Minor format: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 - * - Full version format: 8.4.5, 8.3.12, etc. + * - Major.Minor format, e.g. 7.4 + * - Full version format, e.g. 8.4.5, 8.3.12, etc. */ private function isValidPhpVersion(string $version): bool { // Check major.minor format (e.g., 8.4) - if (in_array($version, ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'], true)) { + if (in_array($version, php::SUPPORTED_MAJOR_VERSIONS, true)) { return true; } diff --git a/src/Package/Target/php.php b/src/Package/Target/php.php index 32e8a13b1..239fcf43b 100644 --- a/src/Package/Target/php.php +++ b/src/Package/Target/php.php @@ -43,18 +43,34 @@ class php extends TargetPackage use unix; use windows; - public static function getPHPVersionID(): int + /** @var string[] Supported major PHP versions */ + public const array SUPPORTED_MAJOR_VERSIONS = ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']; + + /** + * Get PHP version ID from php_version.h + * + * @param null|string $from_custom_source Where to read php_version.h from custom source + * @param bool $return_null_if_failed Whether to return null if failed to get version ID + * @return null|int PHP version ID (e.g., 80400 for PHP 8.4.0) or null if failed + */ + public static function getPHPVersionID(?string $from_custom_source = null, bool $return_null_if_failed = false): ?int { - $artifact = ArtifactLoader::getArtifactInstance('php-src'); - if (!file_exists("{$artifact->getSourceDir()}/main/php_version.h")) { + $source_dir = $from_custom_source ?? ArtifactLoader::getArtifactInstance('php-src')->getSourceDir(); + if (!file_exists("{$source_dir}/main/php_version.h")) { + if ($return_null_if_failed) { + return null; + } throw new WrongUsageException('PHP source files are not available, you need to download them first'); } - $file = file_get_contents("{$artifact->getSourceDir()}/main/php_version.h"); + $file = file_get_contents("{$source_dir}/main/php_version.h"); if (preg_match('/PHP_VERSION_ID (\d+)/', $file, $match) !== 0) { return intval($match[1]); } + if ($return_null_if_failed) { + return null; + } throw new WrongUsageException('PHP version file format is malformed, please remove "./source/php-src" dir and download/extract again'); } diff --git a/src/Package/Target/php/unix.php b/src/Package/Target/php/unix.php index edbc6dd23..18946ad4f 100644 --- a/src/Package/Target/php/unix.php +++ b/src/Package/Target/php/unix.php @@ -262,7 +262,7 @@ public function makeEmbedForUnix(TargetPackage $package, PackageInstaller $insta UnixUtil::exportDynamicSymbols($libphp_a); // deploy embed php scripts - $package->runStage([$this, 'patchEmbedScripts']); + $package->runStage([$this, 'patchUnixEmbedScripts']); } #[Stage] diff --git a/src/Package/Target/pkgconfig.php b/src/Package/Target/pkgconfig.php index e99e2d7c0..aa75fc881 100644 --- a/src/Package/Target/pkgconfig.php +++ b/src/Package/Target/pkgconfig.php @@ -40,6 +40,6 @@ public function build(TargetPackage $package, ToolchainInterface $toolchain): vo ) ->make(with_install: 'install-exec'); - shell()->exec('strip ' . BUILD_ROOT_PATH . '/bin/pkg-config'); + shell()->exec("strip {$package->getBinDir()}/pkg-config"); } } diff --git a/src/SPC/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php index 0068f53e3..0a1e02998 100644 --- a/src/SPC/store/SourcePatcher.php +++ b/src/SPC/store/SourcePatcher.php @@ -18,22 +18,22 @@ class SourcePatcher public static function init(): void { // FileSystem::addSourceExtractHook('swow', [__CLASS__, 'patchSwow']); - FileSystem::addSourceExtractHook('openssl', [__CLASS__, 'patchOpenssl11Darwin']); + FileSystem::addSourceExtractHook('openssl', [__CLASS__, 'patchOpenssl11Darwin']); // migrated FileSystem::addSourceExtractHook('swoole', [__CLASS__, 'patchSwoole']); - FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchPhpLibxml212']); - FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchGDWin32']); - FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchFfiCentos7FixO3strncmp']); + FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchPhpLibxml212']); // migrated + FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchGDWin32']); // migrated + FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchFfiCentos7FixO3strncmp']); // migrated FileSystem::addSourceExtractHook('sqlsrv', [__CLASS__, 'patchSQLSRVWin32']); FileSystem::addSourceExtractHook('pdo_sqlsrv', [__CLASS__, 'patchSQLSRVWin32']); FileSystem::addSourceExtractHook('pdo_sqlsrv', [__CLASS__, 'patchSQLSRVPhp85']); FileSystem::addSourceExtractHook('yaml', [__CLASS__, 'patchYamlWin32']); - FileSystem::addSourceExtractHook('libyaml', [__CLASS__, 'patchLibYaml']); - FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchImapLicense']); + FileSystem::addSourceExtractHook('libyaml', [__CLASS__, 'patchLibYaml']); // removed + FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchImapLicense']); // migrated FileSystem::addSourceExtractHook('ext-imagick', [__CLASS__, 'patchImagickWith84']); - FileSystem::addSourceExtractHook('libaom', [__CLASS__, 'patchLibaomForAlpine']); - FileSystem::addSourceExtractHook('pkg-config', [__CLASS__, 'patchPkgConfigForGcc15']); - FileSystem::addSourceExtractHook('attr', [__CLASS__, 'patchAttrForAlpine']); - FileSystem::addSourceExtractHook('gmssl', [__CLASS__, 'patchGMSSL']); + FileSystem::addSourceExtractHook('libaom', [__CLASS__, 'patchLibaomForAlpine']); // migrated + FileSystem::addSourceExtractHook('pkg-config', [__CLASS__, 'patchPkgConfigForGcc15']); // migrated + FileSystem::addSourceExtractHook('attr', [__CLASS__, 'patchAttrForAlpine']); // migrated + FileSystem::addSourceExtractHook('gmssl', [__CLASS__, 'patchGMSSL']); // migrated } public static function patchBeforeBuildconf(BuilderBase $builder): void diff --git a/src/StaticPHP/Package/LibraryPackage.php b/src/StaticPHP/Package/LibraryPackage.php index 8b392bab3..aa24f057c 100644 --- a/src/StaticPHP/Package/LibraryPackage.php +++ b/src/StaticPHP/Package/LibraryPackage.php @@ -14,6 +14,7 @@ use StaticPHP\Util\DependencyResolver; use StaticPHP\Util\DirDiff; use StaticPHP\Util\FileSystem; +use StaticPHP\Util\GlobalPathTrait; use StaticPHP\Util\SPCConfigUtil; /** @@ -21,6 +22,8 @@ */ class LibraryPackage extends Package { + use GlobalPathTrait; + /** * Custom postinstall actions for this package. * @var array @@ -369,41 +372,6 @@ public function getLibExtraLibs(): string return getenv($this->getSnakeCaseName() . '_LIBS') ?: ''; } - /** - * Get the build root path for the package. - * - * TODO: Can be changed to support per-package build root path in the future. - */ - public function getBuildRootPath(): string - { - return BUILD_ROOT_PATH; - } - - /** - * Get the include directory for the package. - * - * TODO: Can be changed to support per-package include directory in the future. - */ - public function getIncludeDir(): string - { - return BUILD_INCLUDE_PATH; - } - - /** - * Get the library directory for the package. - * - * TODO: Can be changed to support per-package library directory in the future. - */ - public function getLibDir(): string - { - return BUILD_LIB_PATH; - } - - public function getBinDir(): string - { - return BUILD_BIN_PATH; - } - /** * Get tar compress options from suffix * diff --git a/src/StaticPHP/Package/PackageBuilder.php b/src/StaticPHP/Package/PackageBuilder.php index 5d603625c..c9b1051f9 100644 --- a/src/StaticPHP/Package/PackageBuilder.php +++ b/src/StaticPHP/Package/PackageBuilder.php @@ -11,11 +11,14 @@ use StaticPHP\Runtime\Shell\Shell; use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\FileSystem; +use StaticPHP\Util\GlobalPathTrait; use StaticPHP\Util\InteractiveTerm; use StaticPHP\Util\System\LinuxUtil; class PackageBuilder { + use GlobalPathTrait; + /** @var int make jobs count */ public readonly int $concurrency; diff --git a/src/StaticPHP/Util/GlobalPathTrait.php b/src/StaticPHP/Util/GlobalPathTrait.php new file mode 100644 index 000000000..f94c501bc --- /dev/null +++ b/src/StaticPHP/Util/GlobalPathTrait.php @@ -0,0 +1,43 @@ + Date: Fri, 6 Feb 2026 16:38:03 +0800 Subject: [PATCH 99/99] Support define php extension arg-type in config --- src/Package/Artifact/gmssl.php | 20 ++++++++ src/Package/Artifact/libaom.php | 22 ++++++++ src/Package/Artifact/pkg_config.php | 19 +++++++ src/Package/Extension/openssl.php | 50 +++++++++++++++++++ src/StaticPHP/Package/PhpExtensionPackage.php | 17 +++++-- 5 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 src/Package/Artifact/gmssl.php create mode 100644 src/Package/Artifact/libaom.php create mode 100644 src/Package/Artifact/pkg_config.php create mode 100644 src/Package/Extension/openssl.php diff --git a/src/Package/Artifact/gmssl.php b/src/Package/Artifact/gmssl.php new file mode 100644 index 000000000..4caa4d1e2 --- /dev/null +++ b/src/Package/Artifact/gmssl.php @@ -0,0 +1,20 @@ += 80400 ? '' : ' --with-openssl-dir=' . BUILD_ROOT_PATH; + $args = '--with-openssl=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $openssl_dir; + if (php::getPHPVersionID() >= 80500 || (php::getPHPVersionID() >= 80400 && !$builder->getOption('enable-zts'))) { + $args .= ' --with-openssl-argon2 OPENSSL_LIBS="-lz"'; + } + return $args; + } + + #[CustomPhpConfigureArg('Windows')] + public function getWindowsConfigureArg(PackageBuilder $builder): string + { + $args = '--with-openssl'; + if (php::getPHPVersionID() >= 80500 || (php::getPHPVersionID() >= 80400 && !$builder->getOption('enable-zts'))) { + $args .= ' --with-openssl-argon2'; + } + return $args; + } +} diff --git a/src/StaticPHP/Package/PhpExtensionPackage.php b/src/StaticPHP/Package/PhpExtensionPackage.php index 84aa3020d..3f2f18cf3 100644 --- a/src/StaticPHP/Package/PhpExtensionPackage.php +++ b/src/StaticPHP/Package/PhpExtensionPackage.php @@ -79,7 +79,7 @@ public function getPhpConfigureArg(string $os, bool $shared): string return ApplicationContext::invoke($callback, ['shared' => $shared, static::class => $this, Package::class => $this]); } $escapedPath = str_replace("'", '', escapeshellarg(BUILD_ROOT_PATH)) !== BUILD_ROOT_PATH || str_contains(BUILD_ROOT_PATH, ' ') ? escapeshellarg(BUILD_ROOT_PATH) : BUILD_ROOT_PATH; - $name = str_replace('_', '-', $this->getExtensionName()); + $name = str_replace('_', '-', $this->getName()); $ext_config = PackageConfig::get($name, 'php-extension', []); $arg_type = match (SystemTarget::getTargetOS()) { @@ -89,13 +89,22 @@ public function getPhpConfigureArg(string $os, bool $shared): string default => $ext_config['arg-type'] ?? 'enable', }; - return match ($arg_type) { + $arg = match ($arg_type) { 'enable' => $shared ? "--enable-{$name}=shared" : "--enable-{$name}", 'enable-path' => $shared ? "--enable-{$name}=shared,{$escapedPath}" : "--enable-{$name}={$escapedPath}", 'with' => $shared ? "--with-{$name}=shared" : "--with-{$name}", 'with-path' => $shared ? "--with-{$name}=shared,{$escapedPath}" : "--with-{$name}={$escapedPath}", - default => throw new WrongUsageException("Unknown argument type '{$arg_type}' for PHP extension '{$name}'"), + 'custom' => '', + default => $arg_type, }; + // customize argument from config string + $replace = get_pack_replace(); + $arg = str_replace(array_values($replace), array_keys($replace), $arg); + $replace = [ + '@shared_suffix@' => $shared ? '=shared' : '', + '@shared_path_suffix@' => $shared ? "=shared,{$escapedPath}" : "={$escapedPath}", + ]; + return str_replace(array_keys($replace), array_values($replace), $arg); } public function setBuildShared(bool $build_shared = true): void @@ -233,7 +242,7 @@ public function registerDefaultStages(): void { // Add build stages for shared build on Unix-like systems // TODO: Windows shared build support - if ($this->build_shared && in_array(SystemTarget::getTargetOS(), ['Linux', 'Darwin'])) { + if ((PackageConfig::get($this->getName(), 'php-extension')['build-shared'] ?? true) && in_array(SystemTarget::getTargetOS(), ['Linux', 'Darwin'])) { if (!$this->hasStage('build')) { $this->addBuildFunction(SystemTarget::getTargetOS(), [$this, 'buildSharedForUnix']); }