Compare commits

..

4 Commits

Author SHA1 Message Date
crazywhalecc
1bbf83872f Change phpmicro base link 2025-11-26 13:28:15 +08:00
crazywhalecc
fadb1ce6c9 Rename static-php-cli to StaticPHP 2025-11-26 13:24:38 +08:00
crazywhalecc
58bf51e9c9 Next version yeah 2025-11-26 12:55:14 +08:00
crazywhalecc
b60ea8e3d6 Add maxminddb extension and libmaxminddb 2025-11-26 12:54:52 +08:00
46 changed files with 447 additions and 1115 deletions

View File

@@ -1,24 +1,24 @@
# static-php-cli
# StaticPHP
[![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md)
[![Chinese readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87%20%F0%9F%87%A8%F0%9F%87%B3-moccasin?style=flat-square)](README-zh.md)
[![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md)
[![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
**static-php-cli** 是一个用于构建静态、独立 PHP 运行时的强大工具,支持众多流行扩展。
**StaticPHP**(原名 **static-php-cli**是一个用于构建静态、独立 PHP 运行时的强大工具,支持众多流行扩展。
## 特性
- :elephant: **支持多 PHP 版本** - 支持 PHP 8.1, 8.2, 8.3, 8.4, 8.5
- :handbag: **单文件 PHP 可执行文件** - 构建零依赖的独立 PHP
- :hamburger: **phpmicro 集成** - 构建 **[phpmicro](https://github.com/dixyes/phpmicro)** 自解压可执行文件(将 PHP 二进制文件和源代码合并为一个文件)
- :hamburger: **phpmicro 集成** - 构建 **[phpmicro](https://github.com/static-php/phpmicro)** 自解压可执行文件(将 PHP 二进制文件和源代码合并为一个文件)
- :pill: **智能环境检查器** - 自动构建环境检查器,具备自动修复功能
- :zap: **跨平台支持** - 支持 Linux、macOS、FreeBSD 和 Windows
- :wrench: **可配置补丁** - 可自定义的源代码补丁系统
- :books: **智能依赖管理** - 自动处理构建依赖
- 📦 **自包含工具** - 提供使用 [box](https://github.com/box-project/box) 构建的 `spc` 可执行文件
- :fire: **广泛的扩展支持** - 支持 75+ 流行 [扩展](https://static-php.dev/zh/guide/extensions.html)
- :fire: **广泛的扩展支持** - 支持 100+ 流行 [扩展](https://static-php.dev/zh/guide/extensions.html)
- :floppy_disk: **UPX 压缩** - 减小二进制文件大小 30-50%(仅 Linux/Windows
**单文件独立 php-cli**
@@ -33,6 +33,8 @@
### 1. 下载 spc 二进制文件
`spc` 二进制文件是 StaticPHP 项目的 CLI 工具,打包为单个可执行文件。
```bash
# Linux x86_64
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
@@ -107,7 +109,7 @@ buildroot/bin/php-fpm -v
以下是几个具有不同扩展组合的预编译静态 PHP 二进制文件,
您可以根据需要直接下载。
| 组合名称 | 扩展数量 | 系统 | 备注 |
| 组合名称 | 扩展数量 | 系统 | 备注 |
|----------------------------------------------------------------------|----------------------------------------------------------------------------|--------------|--------------------|
| [common](https://dl.static-php.dev/static-php-cli/common/) | [30+](https://dl.static-php.dev/static-php-cli/common/README.txt) | Linux, macOS | 二进制文件大小约为 7.5MB |
| [bulk](https://dl.static-php.dev/static-php-cli/bulk/) | [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) | Linux, macOS | 二进制文件大小约为 25MB |

View File

@@ -1,4 +1,4 @@
# static-php-cli
# StaticPHP
[![Chinese readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87%20%F0%9F%87%A8%F0%9F%87%B3-moccasin?style=flat-square)](README-zh.md)
[![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md)
@@ -6,14 +6,13 @@
[![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
**static-php-cli** is a powerful tool designed for building static, standalone PHP runtime
with popular extensions.
**StaticPHP** (formerly **static-php-cli**) is a powerful tool designed for building static, standalone PHP runtime with popular extensions.
## Features
- :elephant: Support multiple PHP versions - PHP 8.1, 8.2, 8.3, 8.4, 8.5
- :handbag: Build single-file PHP executable with zero dependencies
- :hamburger:Build **[phpmicro](https://github.com/dixyes/phpmicro)** self-extracting executables (combines PHP binary and source code into one file)
- :hamburger:Build **[phpmicro](https://github.com/static-php/phpmicro)** self-extracting executables (combines PHP binary and source code into one file)
- :pill: Automatic build environment checker with auto-fix capabilities
- :zap: `Linux`, `macOS`, `FreeBSD`, `Windows` support
- :wrench: Configurable source code patching
@@ -34,6 +33,8 @@ with popular extensions.
### 1. Download spc binary
`spc` binary is StaticPHP CLI tool packaged as a single executable file.
```bash
# For Linux x86_64
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
@@ -53,7 +54,7 @@ For macOS and Linux, add execute permission first:
chmod +x ./spc
```
### 2. Build Static PHP
### 2. Build standalone PHP binaries
First, create a `craft.yml` file and specify which extensions you want to include from [extension list](https://static-php.dev/en/guide/extensions.html) or [command generator](https://static-php.dev/en/guide/cli-generator.html):
@@ -79,7 +80,7 @@ Run command:
./spc craft --debug
```
### 3. Static PHP usage
### 3. Using the built binaries
Now you can copy binaries built by static-php-cli to another machine and run with no dependencies:
@@ -105,7 +106,7 @@ see <https://static-php.dev> .
If you don't want to build or want to test first, you can download example pre-compiled artifact from [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-bulk.yml), or from self-hosted server.
Below are several precompiled static-php binaries with different extension combinations,
Below are several precompiled statically built php binaries with different extension combinations,
which can be downloaded directly according to your needs.
| Combination | Extension Count | OS | Comment |

428
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -232,13 +232,11 @@
"BSD": "wip"
},
"type": "external",
"source": "ext-grpc",
"source": "grpc",
"arg-type-unix": "enable-path",
"cpp-extension": true,
"lib-depends": [
"zlib",
"openssl",
"libcares"
"grpc"
]
},
"iconv": {
@@ -489,36 +487,6 @@
"zlib"
]
},
"mysqlnd_ed25519": {
"type": "external",
"source": "mysqlnd_ed25519",
"arg-type": "enable",
"target": [
"shared"
],
"ext-depends": [
"mysqlnd"
],
"lib-depends": [
"libsodium",
"openssl"
]
},
"mysqlnd_parsec": {
"type": "external",
"source": "mysqlnd_parsec",
"arg-type": "enable",
"target": [
"shared"
],
"ext-depends": [
"mysqlnd"
],
"lib-depends": [
"libsodium",
"openssl"
]
},
"oci8": {
"type": "wip",
"support": {
@@ -599,13 +567,6 @@
"type": "builtin",
"unix-only": true
},
"pcov": {
"type": "external",
"source": "pcov",
"target": [
"shared"
]
},
"pdo": {
"type": "builtin"
},

View File

@@ -11,6 +11,7 @@
"type": "url",
"url": "https://pecl.php.net/get/amqp",
"path": "php-src/ext/amqp",
"filename": "amqp.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -20,6 +21,7 @@
"type": "url",
"url": "https://pecl.php.net/get/APCu",
"path": "php-src/ext/apcu",
"filename": "apcu.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -29,6 +31,7 @@
"type": "url",
"url": "https://pecl.php.net/get/ast",
"path": "php-src/ext/ast",
"filename": "ast.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -85,6 +88,7 @@
"type": "url",
"url": "https://pecl.php.net/get/dio",
"path": "php-src/ext/dio",
"filename": "dio.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -94,6 +98,7 @@
"type": "url",
"url": "https://pecl.php.net/get/ev",
"path": "php-src/ext/ev",
"filename": "ev.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -113,6 +118,7 @@
"type": "url",
"url": "https://pecl.php.net/get/ds",
"path": "php-src/ext/ds",
"filename": "ds.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -145,21 +151,11 @@
"path": "LICENSE"
}
},
"ext-grpc": {
"type": "url",
"url": "https://pecl.php.net/get/grpc",
"path": "php-src/ext/grpc",
"license": {
"type": "file",
"path": [
"LICENSE"
]
}
},
"ext-imagick": {
"type": "url",
"url": "https://pecl.php.net/get/imagick",
"path": "php-src/ext/imagick",
"filename": "imagick.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -169,6 +165,7 @@
"type": "url",
"url": "https://pecl.php.net/get/imap",
"path": "php-src/ext/imap",
"filename": "imap.tgz",
"license": {
"type": "file",
"path": [
@@ -190,6 +187,7 @@
"ext-maxminddb": {
"type": "url",
"url": "https://pecl.php.net/get/maxminddb",
"filename": "ext-maxminddb.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -199,6 +197,7 @@
"type": "url",
"url": "https://pecl.php.net/get/memcache",
"path": "php-src/ext/memcache",
"filename": "memcache.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -217,6 +216,7 @@
"type": "url",
"url": "https://pecl.php.net/get/simdjson",
"path": "php-src/ext/simdjson",
"filename": "simdjson.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -236,6 +236,7 @@
"type": "url",
"url": "https://pecl.php.net/get/ssh2",
"path": "php-src/ext/ssh2",
"filename": "ssh2.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -245,6 +246,7 @@
"type": "url",
"url": "https://pecl.php.net/get/trader",
"path": "php-src/ext/trader",
"filename": "trader.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -254,6 +256,7 @@
"type": "url",
"url": "https://pecl.php.net/get/uuid",
"path": "php-src/ext/uuid",
"filename": "uuid.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -263,6 +266,7 @@
"type": "url",
"url": "https://pecl.php.net/get/uv",
"path": "php-src/ext/uv",
"filename": "uv.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -281,6 +285,7 @@
"ext-zip": {
"type": "url",
"url": "https://pecl.php.net/get/zip",
"filename": "ext-zip.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -388,6 +393,7 @@
"type": "url",
"url": "https://pecl.php.net/get/igbinary",
"path": "php-src/ext/igbinary",
"filename": "igbinary.tgz",
"license": {
"type": "file",
"path": "COPYING"
@@ -414,6 +420,7 @@
"type": "url",
"url": "https://pecl.php.net/get/inotify",
"path": "php-src/ext/inotify",
"filename": "inotify.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -663,10 +670,9 @@
}
},
"libpng": {
"type": "ghtagtar",
"repo": "pnggroup/libpng",
"match": "v1\\.6\\.\\d+",
"query": "?per_page=150",
"type": "git",
"url": "https://github.com/glennrp/libpng.git",
"rev": "libpng16",
"provide-pre-built": true,
"license": {
"type": "file",
@@ -674,9 +680,9 @@
}
},
"librabbitmq": {
"type": "ghtar",
"repo": "alanxz/rabbitmq-c",
"prefer-stable": true,
"type": "git",
"url": "https://github.com/alanxz/rabbitmq-c.git",
"rev": "master",
"license": {
"type": "file",
"path": "LICENSE"
@@ -765,9 +771,8 @@
]
},
"libwebp": {
"type": "ghtagtar",
"repo": "webmproject/libwebp",
"match": "v1\\.\\d+\\.\\d+$",
"type": "url",
"url": "https://github.com/webmproject/libwebp/archive/refs/tags/v1.3.2.tar.gz",
"provide-pre-built": true,
"license": {
"type": "file",
@@ -775,10 +780,8 @@
}
},
"libxml2": {
"type": "ghtagtar",
"repo": "GNOME/libxml2",
"match": "v2\\.\\d+\\.\\d+$",
"provide-pre-built": false,
"type": "url",
"url": "https://github.com/GNOME/libxml2/archive/refs/tags/v2.12.5.tar.gz",
"license": {
"type": "file",
"path": "Copyright"
@@ -818,6 +821,7 @@
"type": "url",
"url": "https://pecl.php.net/get/memcached",
"path": "php-src/ext/memcached",
"filename": "memcached.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -858,24 +862,7 @@
"type": "url",
"url": "https://pecl.php.net/get/msgpack",
"path": "php-src/ext/msgpack",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"mysqlnd_ed25519": {
"type": "pie",
"repo": "mariadb/mysqlnd_ed25519",
"path": "php-src/ext/mysqlnd_ed25519",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"mysqlnd_parsec": {
"type": "pie",
"repo": "mariadb/mysqlnd_parsec",
"path": "php-src/ext/mysqlnd_parsec",
"filename": "msgpack.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -960,6 +947,7 @@
"type": "url",
"url": "https://pecl.php.net/get/opentelemetry",
"path": "php-src/ext/opentelemetry",
"filename": "opentelemetry.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -969,14 +957,7 @@
"type": "url",
"url": "https://pecl.php.net/get/parallel",
"path": "php-src/ext/parallel",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"pcov": {
"type": "url",
"url": "https://pecl.php.net/get/pcov",
"filename": "parallel.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -986,6 +967,7 @@
"type": "url",
"url": "https://pecl.php.net/get/pdo_sqlsrv",
"path": "php-src/ext/pdo_sqlsrv",
"filename": "pdo_sqlsrv.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -1021,6 +1003,7 @@
"type": "url",
"url": "https://pecl.php.net/get/protobuf",
"path": "php-src/ext/protobuf",
"filename": "protobuf.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -1082,6 +1065,7 @@
"type": "url",
"url": "https://pecl.php.net/get/redis",
"path": "php-src/ext/redis",
"filename": "redis.tgz",
"license": {
"type": "file",
"path": [
@@ -1121,6 +1105,7 @@
"type": "url",
"url": "https://pecl.php.net/get/sqlsrv",
"path": "php-src/ext/sqlsrv",
"filename": "sqlsrv.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -1175,8 +1160,9 @@
}
},
"xdebug": {
"type": "pie",
"repo": "xdebug/xdebug",
"type": "url",
"url": "https://pecl.php.net/get/xdebug",
"filename": "xdebug.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -1186,6 +1172,7 @@
"type": "url",
"url": "https://pecl.php.net/get/xhprof",
"path": "php-src/ext/xhprof-src",
"filename": "xhprof.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -1195,6 +1182,7 @@
"type": "url",
"url": "https://pecl.php.net/get/xlswriter",
"path": "php-src/ext/xlswriter",
"filename": "xlswriter.tgz",
"license": {
"type": "file",
"path": "LICENSE"
@@ -1215,6 +1203,7 @@
"type": "url",
"url": "https://pecl.php.net/get/yac",
"path": "php-src/ext/yac",
"filename": "yac.tgz",
"license": {
"type": "file",
"path": "LICENSE"

View File

@@ -4,7 +4,7 @@ import sidebarZh from "./sidebar.zh";
// https://vitepress.dev/reference/site-config
export default {
title: "Static PHP",
title: "StaticPHP",
description: "Build single static PHP binary, with PHP project together, with popular extensions included.",
locales: {
en: {

View File

@@ -1,6 +1,6 @@
# Guide
Static php cli is a tool used to build statically compiled PHP binaries,
StaticPHP is a tool used to build statically compiled PHP binaries,
currently supporting Linux and macOS systems.
In the guide section, you will learn how to use static php cli to build standalone PHP programs.
@@ -14,12 +14,14 @@ In the guide section, you will learn how to use static php cli to build standalo
The following is the architecture support situation, where :gear: represents support for GitHub Action build,
:computer: represents support for local manual build, and empty represents temporarily not supported.
| | x86_64 | aarch64 |
|---------|-------------------|-------------------|
| macOS | :gear: :computer: | :gear: :computer: |
| Linux | :gear: :computer: | :gear: :computer: |
| Windows | :gear: :computer: | |
| FreeBSD | :computer: | :computer: |
| | x86_64 | aarch64 |
|---------|------------------------|------------------------|
| macOS | :gear: :computer: | :gear: :computer: |
| Linux | :gear: :computer: | :gear: :computer: |
| Windows | :gear: :computer: | |
| FreeBSD | :computer: (:warning:) | :computer: (:warning:) |
> Due to lack of users and testing, FreeBSD is no longer supported in latest StaticPHP project.
Current supported PHP versions for compilation:
@@ -29,17 +31,17 @@ Current supported PHP versions for compilation:
>
> :x: Not supported
| PHP Version | Status | Comment |
|-------------|--------------------|-------------------------------------------------------------------------------------------------------------------------|
| 7.2 | :x: | |
| 7.3 | :x: | phpmicro and many extensions do not support 7.3, 7.4 versions |
| 7.4 | :x: | phpmicro and many extensions do not support 7.3, 7.4 versions |
| 8.0 | :warning: | PHP official has stopped maintaining 8.0, we no longer handle 8.0 related backport support |
| 8.1 | :warning: | PHP official only provides security updates for 8.1, we no longer handle 8.1 related backport support after 8.5 release |
| 8.2 | :heavy_check_mark: | |
| 8.3 | :heavy_check_mark: | |
| 8.4 | :heavy_check_mark: | |
| 8.5 (beta) | :warning: | PHP 8.5 is currently in beta stage |
| PHP Version | Status | Comment |
|-------------|--------------------|--------------------------------------------------------------------------------------------|
| 7.2 | :x: | |
| 7.3 | :x: | phpmicro and many extensions do not support 7.3, 7.4 versions |
| 7.4 | :x: | phpmicro and many extensions do not support 7.3, 7.4 versions |
| 8.0 | :warning: | PHP official has stopped maintaining 8.0, we no longer handle 8.0 related backport support |
| 8.1 | :warning: | PHP official only provides security updates for 8.1 |
| 8.2 | :heavy_check_mark: | |
| 8.3 | :heavy_check_mark: | |
| 8.4 | :heavy_check_mark: | |
| 8.5 | :heavy_check_mark: | |
> This table shows the support status of static-php-cli for building corresponding versions, not the PHP official support status for that version.

View File

@@ -3,7 +3,7 @@
layout: home
hero:
name: "Static PHP"
name: "StaticPHP"
tagline: "Build standalone PHP binary on Linux, macOS, FreeBSD, Windows, with PHP project together, with popular extensions included."
image:
src: /images/static-php_nobg.png
@@ -14,9 +14,9 @@ hero:
link: ./guide/
features:
- title: Static CLI Binary
- title: Static PHP Binary
details: You can easily compile a standalone php binary for general use. Including CLI, FPM sapi.
- title: Micro Self-Extracted Executable
- title: PHP micro Self-Extracted Executable
details: You can compile a self-extracted executable and build with your php source code.
- title: Dependency Management
details: static-php-cli comes with dependency management and supports installation of different types of PHP extensions.

View File

@@ -3,7 +3,7 @@
layout: home
hero:
name: "Static PHP"
name: "StaticPHP"
tagline: "Build standalone PHP binary on Linux, macOS, FreeBSD, Windows, with PHP project together, with popular extensions included."
image:
src: /images/static-php_nobg.png

View File

@@ -12,12 +12,14 @@ static-php-cli 是一个用于构建静态编译的 PHP 二进制的工具,目
下面是架构支持情况,:gear: 代表支持 GitHub Action 构建,:computer: 代表支持本地构建,空 代表暂不支持。
| | x86_64 | aarch64 |
|---------|-------------------|-------------------|
| macOS | :gear: :computer: | :gear: :computer: |
| Linux | :gear: :computer: | :gear: :computer: |
| Windows | :gear: :computer: | |
| FreeBSD | :computer: | :computer: |
| | x86_64 | aarch64 |
|---------|------------------------|------------------------|
| macOS | :gear: :computer: | :gear: :computer: |
| Linux | :gear: :computer: | :gear: :computer: |
| Windows | :gear: :computer: | |
| FreeBSD | :computer: (:warning:) | :computer: (:warning:) |
> 由于缺乏用户和测试FreeBSD 在最新的 StaticPHP 项目中不再受支持。
当前支持编译的 PHP 版本:
@@ -27,17 +29,17 @@ static-php-cli 是一个用于构建静态编译的 PHP 二进制的工具,目
>
> :x: 不支持
| PHP Version | Status | Comment |
|-------------|--------------------|---------------------------------------------------------|
| 7.2 | :x: | |
| 7.3 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
| 7.4 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
| 8.0 | :warning: | PHP 官方已停止 8.0 的维护,我们不再处理 8.0 相关的 backport 支持 |
| 8.1 | :warning: | PHP 官方仅对 8.1 提供安全更新,在 8.5 发布后我们不再处理 8.1 相关的 backport 支持 |
| 8.2 | :heavy_check_mark: | |
| 8.3 | :heavy_check_mark: | |
| 8.4 | :heavy_check_mark: | |
| 8.5 (beta) | :warning: | PHP 8.5 目前处于 beta 阶段 |
| PHP Version | Status | Comment |
|-------------|--------------------|----------------------------------------------|
| 7.2 | :x: | |
| 7.3 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
| 7.4 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
| 8.0 | :warning: | PHP 官方已停止 8.0 的维护,我们不再处理 8.0 相关的 backport 支持 |
| 8.1 | :warning: | PHP 官方仅对 8.1 提供安全更新 |
| 8.2 | :heavy_check_mark: | |
| 8.3 | :heavy_check_mark: | |
| 8.4 | :heavy_check_mark: | |
| 8.5 | :heavy_check_mark: | |
> 这个表格的支持状态是 static-php-cli 对构建对应版本的支持情况,不是 PHP 官方对该版本的支持情况。

View File

@@ -34,7 +34,7 @@ use Symfony\Component\Console\Application;
*/
final class ConsoleApplication extends Application
{
public const string VERSION = '2.7.11';
public const string VERSION = '2.7.8';
public function __construct()
{

View File

@@ -160,7 +160,7 @@ abstract class BuilderBase
}
if (!$skip_extract) {
$this->emitPatchPoint('before-php-extract');
SourceManager::initSource(sources: [$this->getPhpSrcName()], source_only: true);
SourceManager::initSource(sources: ['php-src'], source_only: true);
$this->emitPatchPoint('after-php-extract');
if ($this->getPHPVersionID() >= 80000) {
$this->emitPatchPoint('before-micro-extract');
@@ -319,7 +319,7 @@ abstract class BuilderBase
public function getPHPVersionFromArchive(?string $file = null): false|string
{
if ($file === null) {
$lock = LockFile::get($this->getPhpSrcName());
$lock = LockFile::get('php-src');
if ($lock === null) {
return false;
}
@@ -498,14 +498,6 @@ abstract class BuilderBase
}
}
/**
* Get the php-src name to use for lock file lookups (supports version-specific names like php-src-8.2)
*/
protected function getPhpSrcName(): string
{
return getenv('SPC_PHP_SRC_NAME') ?: 'php-src';
}
/**
* Generate micro extension test php code.
*/

View File

@@ -385,9 +385,6 @@ class Extension
logger()->info('Shared extension [' . $this->getName() . '] was already built, skipping (' . $this->getName() . '.so)');
return;
}
if ((string) Config::getExt($this->getName(), 'type') === 'addon') {
return;
}
logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)');
foreach ($this->dependencies as $dependency) {
if (!$dependency instanceof Extension) {

View File

@@ -184,18 +184,18 @@ abstract class LibraryBase
// extract first if not exists
if (!is_dir($this->source_dir)) {
$this->getBuilder()->emitPatchPoint('before-library[' . static::NAME . ']-extract');
$this->getBuilder()->emitPatchPoint('before-library[ ' . static::NAME . ']-extract');
SourceManager::initSource(libs: [static::NAME], source_only: true);
$this->getBuilder()->emitPatchPoint('after-library[' . static::NAME . ']-extract');
$this->getBuilder()->emitPatchPoint('after-library[ ' . static::NAME . ']-extract');
}
if (!$this->patched && $this->patchBeforeBuild()) {
file_put_contents($this->source_dir . '/.spc.patched', 'PATCHED!!!');
}
$this->getBuilder()->emitPatchPoint('before-library[' . static::NAME . ']-build');
$this->getBuilder()->emitPatchPoint('before-library[ ' . static::NAME . ']-build');
$this->build();
$this->installLicense();
$this->getBuilder()->emitPatchPoint('after-library[' . static::NAME . ']-build');
$this->getBuilder()->emitPatchPoint('after-library[ ' . static::NAME . ']-build');
return LIB_STATUS_OK;
}
@@ -346,19 +346,19 @@ abstract class LibraryBase
*/
protected function installLicense(): void
{
FileSystem::createDir(BUILD_ROOT_PATH . '/source-licenses/' . $this->getName());
$source = Config::getLib($this->getName(), 'source');
FileSystem::createDir(BUILD_ROOT_PATH . "/source-licenses/{$source}");
$license_files = Config::getSource($source)['license'] ?? [];
if (is_assoc_array($license_files)) {
$license_files = [$license_files];
}
foreach ($license_files as $index => $license) {
if ($license['type'] === 'text') {
FileSystem::writeFile(BUILD_ROOT_PATH . "/source-licenses/{$source}/{$index}.txt", $license['text']);
FileSystem::writeFile(BUILD_ROOT_PATH . '/source-licenses/' . $this->getName() . "/{$index}.txt", $license['text']);
continue;
}
if ($license['type'] === 'file') {
copy($this->source_dir . '/' . $license['path'], BUILD_ROOT_PATH . "/source-licenses/{$source}/{$index}.txt");
copy($this->source_dir . '/' . $license['path'], BUILD_ROOT_PATH . '/source-licenses/' . $this->getName() . "/{$index}.txt");
}
}
}
@@ -375,17 +375,8 @@ abstract class LibraryBase
return false;
}
}
$pkg_config_path = getenv('PKG_CONFIG_PATH') ?: '';
$search_paths = array_filter(explode(is_unix() ? ':' : ';', $pkg_config_path));
foreach (Config::getLib(static::NAME, 'pkg-configs', []) as $name) {
$found = false;
foreach ($search_paths as $path) {
if (file_exists($path . "/{$name}.pc")) {
$found = true;
break;
}
}
if (!$found) {
if (!file_exists(BUILD_LIB_PATH . "/pkgconfig/{$name}.pc")) {
return false;
}
}

View File

@@ -21,14 +21,18 @@ class grpc extends Extension
if ($this->builder instanceof WindowsBuilder) {
throw new ValidationException('grpc extension does not support windows yet');
}
FileSystem::replaceFileStr(
$this->source_dir . '/src/php/ext/grpc/call.c',
'zend_exception_get_default(TSRMLS_C),',
'zend_ce_exception,',
);
if (file_exists(SOURCE_PATH . '/php-src/ext/grpc')) {
return false;
}
// soft link to the grpc source code
if (is_dir($this->source_dir . '/src/php/ext/grpc')) {
shell()->exec('ln -s ' . $this->source_dir . '/src/php/ext/grpc ' . SOURCE_PATH . '/php-src/ext/grpc');
} else {
throw new ValidationException('Cannot find grpc source code in ' . $this->source_dir . '/src/php/ext/grpc');
}
if (SPCTarget::getTargetOS() === 'Darwin') {
FileSystem::replaceFileRegex(
$this->source_dir . '/config.m4',
SOURCE_PATH . '/php-src/ext/grpc/config.m4',
'/GRPC_LIBDIR=.*$/m',
'GRPC_LIBDIR=' . BUILD_LIB_PATH . "\n" . 'LDFLAGS="$LDFLAGS -framework CoreFoundation"'
);

View File

@@ -5,8 +5,6 @@ declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\toolchain\ToolchainManager;
use SPC\toolchain\ZigToolchain;
use SPC\util\CustomExt;
#[CustomExt('imagick')]
@@ -21,9 +19,7 @@ class imagick extends Extension
protected function splitLibsIntoStaticAndShared(string $allLibs): array
{
[$static, $shared] = parent::splitLibsIntoStaticAndShared($allLibs);
if (ToolchainManager::getToolchainClass() !== ZigToolchain::class &&
(str_contains(getenv('PATH'), 'rh/devtoolset') || str_contains(getenv('PATH'), 'rh/gcc-toolset'))
) {
if (str_contains(getenv('PATH'), 'rh/devtoolset') || str_contains(getenv('PATH'), 'rh/gcc-toolset')) {
$static .= ' -l:libstdc++.a';
$shared = str_replace('-lstdc++', '', $shared);
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\store\FileSystem;
use SPC\util\CustomExt;
#[CustomExt('maxminddb')]
@@ -13,13 +12,15 @@ class maxminddb extends Extension
{
public function patchBeforeBuildconf(): bool
{
if (!is_dir(SOURCE_PATH . '/php-src/ext/maxminddb')) {
if (!is_link(SOURCE_PATH . '/php-src/ext/maxminddb')) {
$original = $this->source_dir;
FileSystem::copyDir($original . '/ext', SOURCE_PATH . '/php-src/ext/maxminddb');
$this->source_dir = SOURCE_PATH . '/php-src/ext/maxminddb';
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('cd ' . SOURCE_PATH . '/php-src/ext && mklink /D maxminddb ' . $original . '\ext');
} else {
f_passthru('cd ' . SOURCE_PATH . '/php-src/ext && ln -s ' . $original . '/ext maxminddb');
}
return true;
}
$this->source_dir = SOURCE_PATH . '/php-src/ext/maxminddb';
return false;
}
}

View File

@@ -43,9 +43,4 @@ EOF
);
return true;
}
protected function getExtraEnv(): array
{
return ['CFLAGS' => '-std=c17'];
}
}

View File

@@ -24,9 +24,4 @@ class mongodb extends Extension
$arg .= $this->builder->getLib('zlib') ? ' --with-mongodb-zlib=yes ' : ' --with-mongodb-zlib=bundled ';
return clean_spaces($arg);
}
public function getExtraEnv(): array
{
return ['CFLAGS' => '-std=c17'];
}
}

View File

@@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\util\CustomExt;
#[CustomExt('mysqlnd_ed25519')]
class mysqlnd_ed25519 extends Extension
{
public function getConfigureArg(bool $shared = false): string
{
return '--with-mysqlnd_ed25519' . ($shared ? '=shared' : '');
}
public function getUnixConfigureArg(bool $shared = false): string
{
return $this->getConfigureArg();
}
}

View File

@@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\util\CustomExt;
#[CustomExt('mysqlnd_parsec')]
class mysqlnd_parsec extends Extension
{
public function getConfigureArg(bool $shared = false): string
{
return '--enable-mysqlnd_parsec' . ($shared ? '=shared' : '');
}
public function getUnixConfigureArg(bool $shared = false): string
{
return $this->getConfigureArg();
}
}

View File

@@ -45,7 +45,7 @@ class pgsql extends Extension
protected function getExtraEnv(): array
{
return [
'CFLAGS' => '-std=c17 -Wno-int-conversion',
'CFLAGS' => '-Wno-int-conversion',
];
}
}

View File

@@ -17,7 +17,6 @@ class swoole extends Extension
public function patchBeforeMake(): bool
{
$patched = parent::patchBeforeMake();
FileSystem::replaceFileStr($this->source_dir . '/ext-src/php_swoole_private.h', 'PHP_VERSION_ID > 80500', 'PHP_VERSION_ID >= 80600');
if ($this->builder instanceof MacOSBuilder) {
// Fix swoole with event extension <util.h> conflict bug
$util_path = shell()->execWithResult('xcrun --show-sdk-path', false)[1][0] . '/usr/include/util.h';

View File

@@ -6,8 +6,6 @@ namespace SPC\builder\linux\library;
use SPC\builder\linux\SystemUtil;
use SPC\store\FileSystem;
use SPC\toolchain\GccNativeToolchain;
use SPC\toolchain\ToolchainManager;
use SPC\util\executor\UnixAutoconfExecutor;
use SPC\util\SPCTarget;
@@ -17,19 +15,26 @@ class liburing extends LinuxLibraryBase
public function patchBeforeBuild(): bool
{
if (SystemUtil::isMuslDist()) {
FileSystem::replaceFileStr($this->source_dir . '/configure', 'realpath -s', 'realpath');
return true;
if (!SystemUtil::isMuslDist()) {
return false;
}
return false;
FileSystem::replaceFileStr($this->source_dir . '/configure', 'realpath -s', 'realpath');
return true;
}
protected function build(): void
{
$use_libc = ToolchainManager::getToolchainClass() !== GccNativeToolchain::class || version_compare(SPCTarget::getLibcVersion(), '2.30', '>=');
$use_libc = SPCTarget::getLibc() !== 'glibc' || version_compare(SPCTarget::getLibcVersion(), '2.30', '>=');
$make = UnixAutoconfExecutor::create($this);
if ($use_libc) {
if (!$use_libc) {
$make->appendEnv([
'CC' => 'gcc', // libc-less version fails to compile with clang or zig
'CXX' => 'g++',
'AR' => 'ar',
'LD' => 'ld',
]);
} else {
$make->appendEnv([
'CFLAGS' => '-D_GNU_SOURCE',
]);
@@ -46,7 +51,7 @@ class liburing extends LinuxLibraryBase
$use_libc ? '--use-libc' : '',
)
->configure()
->make('library ENABLE_SHARED=0', 'install ENABLE_SHARED=0', with_clean: false);
->make('library', 'install ENABLE_SHARED=0', with_clean: false);
$this->patchPkgconfPrefix();
}

View File

@@ -62,7 +62,6 @@ class openssl extends LinuxLibraryBase
"{$zlib_extra}" .
'enable-pie ' .
'no-legacy ' .
'no-tests ' .
"linux-{$arch}"
)
->exec('make clean')

View File

@@ -34,7 +34,7 @@ trait UnixLibraryTrait
$files = array_map(fn ($x) => "{$x}.pc", $conf_pc);
}
foreach ($files as $name) {
$realpath = realpath(BUILD_LIB_PATH . '/pkgconfig/' . $name);
$realpath = realpath(BUILD_ROOT_PATH . '/lib/pkgconfig/' . $name);
if ($realpath === false) {
throw new PatchException('pkg-config prefix patcher', 'Cannot find library [' . static::NAME . '] pkgconfig file [' . $name . '] in ' . BUILD_LIB_PATH . '/pkgconfig/ !');
}

View File

@@ -145,10 +145,11 @@ abstract class UnixBuilderBase extends BuilderBase
throw new SPCInternalException("Deploy failed. Cannot find file after copy: {$dst}");
}
// extract debug info
$this->extractDebugInfo($dst);
// strip
if (!$this->getOption('no-strip')) {
// extract debug info
$this->extractDebugInfo($dst);
// extra strip
$this->stripBinary($dst);
}

View File

@@ -10,14 +10,7 @@ trait gmp
{
protected function build(): void
{
UnixAutoconfExecutor::create($this)
->appendEnv([
'CFLAGS' => '-std=c17',
])
->configure(
'--enable-fat'
)
->make();
UnixAutoconfExecutor::create($this)->configure()->make();
$this->patchPkgconfPrefix(['gmp.pc']);
}
}

View File

@@ -29,17 +29,13 @@ trait libjxl
);
if (ToolchainManager::getToolchainClass() === ZigToolchain::class) {
$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->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();

View File

@@ -10,26 +10,8 @@ trait libwebp
{
protected function build(): void
{
$code = '#include <immintrin.h>
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' : ''
)
->addConfigureArgs('-DWEBP_BUILD_EXTRAS=ON')
->build();
// patch pkgconfig
$this->patchPkgconfPrefix(patch_option: PKGCONF_PATCH_PREFIX | PKGCONF_PATCH_LIBDIR);

View File

@@ -16,7 +16,6 @@ trait ncurses
UnixAutoconfExecutor::create($this)
->appendEnv([
'CFLAGS' => '-std=c17',
'LDFLAGS' => SPCTarget::isStatic() ? '-static' : '',
])
->configure(
@@ -30,7 +29,7 @@ trait ncurses
'--without-tests',
'--without-dlsym',
'--without-debug',
'--enable-symlinks',
'-enable-symlinks',
"--bindir={$this->getBinDir()}",
"--includedir={$this->getIncludeDir()}",
"--libdir={$this->getLibDir()}",

View File

@@ -49,7 +49,6 @@ class BuildPHPCommand extends BuildCommand
$this->addOption('with-micro-logo', null, InputOption::VALUE_REQUIRED, 'Use custom .ico for micro.sfx (windows only)');
$this->addOption('enable-micro-win32', null, null, 'Enable win32 mode for phpmicro (Windows only)');
$this->addOption('with-frankenphp-app', null, InputOption::VALUE_REQUIRED, 'Path to a folder to be embedded in FrankenPHP');
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'PHP version to build (e.g., 8.2, 8.3, 8.4). Uses php-src-X.Y if available, otherwise php-src');
}
public function handle(): int
@@ -121,31 +120,6 @@ class BuildPHPCommand extends BuildCommand
logger()->warning('Some cases micro.sfx cannot be packed via UPX due to dynamic size bug, be aware!');
}
}
// Determine which php-src to use based on --with-php option
$php_version = $this->getOption('with-php');
if ($php_version !== null) {
// Check if version-specific php-src exists in lock file
$version_specific_name = "php-src-{$php_version}";
$lock_file_path = DOWNLOAD_PATH . '/.lock.json';
if (file_exists($lock_file_path)) {
$lock_content = json_decode(file_get_contents($lock_file_path), true);
if (isset($lock_content[$version_specific_name])) {
// Use version-specific php-src
f_putenv("SPC_PHP_SRC_NAME={$version_specific_name}");
logger()->info("Building with PHP {$php_version} (using {$version_specific_name})");
} else {
logger()->error('No php-src found in downloads. Please run download command first.');
return static::FAILURE;
}
} else {
logger()->error('Lock file not found. Please download sources first.');
return static::FAILURE;
}
} else {
f_putenv('SPC_PHP_SRC_NAME=php-src');
}
// create builder
$builder = BuilderProvider::makeBuilderByInput($this->input);
$include_suggest_ext = $this->getOption('with-suggested-exts');

View File

@@ -9,9 +9,7 @@ use SPC\exception\DownloaderException;
use SPC\exception\SPCException;
use SPC\store\Config;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\LockFile;
use SPC\store\source\CustomSourceBase;
use SPC\util\DependencyUtil;
use SPC\util\SPCTarget;
use Symfony\Component\Console\Attribute\AsCommand;
@@ -29,10 +27,10 @@ class DownloadCommand extends BaseCommand
public function configure(): void
{
$this->addArgument('sources', InputArgument::OPTIONAL, 'The sources will be compiled, comma separated');
$this->addArgument('sources', InputArgument::REQUIRED, 'The sources will be compiled, comma separated');
$this->addOption('shallow-clone', null, null, 'Clone shallow');
$this->addOption('with-openssl11', null, null, 'Use openssl 1.1');
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'version in major.minor format, comma-separated for multiple versions (default 8.4)', '8.4');
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'version in major.minor format (default 8.4)', '8.4');
$this->addOption('clean', null, null, 'Clean old download cache and source before fetch');
$this->addOption('all', 'A', null, 'Fetch all sources that static-php-cli needed');
$this->addOption('custom-url', 'U', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Specify custom source download url, e.g "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"');
@@ -45,31 +43,10 @@ class DownloadCommand extends BaseCommand
$this->addOption('retry', 'R', InputOption::VALUE_REQUIRED, 'Set retry time when downloading failed (default: 0)', '0');
$this->addOption('prefer-pre-built', 'P', null, 'Download pre-built libraries when available');
$this->addOption('no-alt', null, null, 'Do not download alternative sources');
$this->addOption('update', null, null, 'Check and update downloaded sources');
}
public function initialize(InputInterface $input, OutputInterface $output): void
{
// mode: --update
if ($input->getOption('update') && empty($input->getArgument('sources')) && empty($input->getOption('for-extensions')) && empty($input->getOption('for-libs'))) {
if (!file_exists(LockFile::LOCK_FILE)) {
parent::initialize($input, $output);
return;
}
$lock_content = json_decode(file_get_contents(LockFile::LOCK_FILE), true);
if (is_array($lock_content)) {
// Filter out pre-built sources
$sources_to_check = array_filter($lock_content, function ($name) {
return
!str_contains($name, '-Linux-') &&
!str_contains($name, '-Windows-') &&
!str_contains($name, '-Darwin-');
}, ARRAY_FILTER_USE_KEY);
$input->setArgument('sources', implode(',', array_keys($sources_to_check)));
}
parent::initialize($input, $output);
return;
}
// mode: --all
if ($input->getOption('all')) {
$input->setArgument('sources', implode(',', array_keys(Config::getSources())));
@@ -117,29 +94,17 @@ class DownloadCommand extends BaseCommand
return $this->downloadFromZip($path);
}
if ($this->getOption('update')) {
return $this->handleUpdate();
}
// Define PHP major version(s)
$php_versions_str = $this->getOption('with-php');
$php_versions = array_map('trim', explode(',', $php_versions_str));
// Validate all versions
foreach ($php_versions as $ver) {
if ($ver !== 'git' && !preg_match('/^\d+\.\d+$/', $ver)) {
// If not git, we need to check the version format
if (!preg_match('/^\d+\.\d+(\.\d+)?$/', $ver)) {
logger()->error("bad version arg: {$ver}, x.y or x.y.z required!");
return static::FAILURE;
}
// Define PHP major version
$ver = $this->php_major_ver = $this->getOption('with-php');
define('SPC_BUILD_PHP_VERSION', $ver);
if ($ver !== 'git' && !preg_match('/^\d+\.\d+$/', $ver)) {
// If not git, we need to check the version format
if (!preg_match('/^\d+\.\d+(\.\d+)?$/', $ver)) {
logger()->error("bad version arg: {$ver}, x.y or x.y.z required!");
return static::FAILURE;
}
}
// Set the first version as the default for backward compatibility
$this->php_major_ver = $php_versions[0];
define('SPC_BUILD_PHP_VERSION', $this->php_major_ver);
// retry
$retry = (int) $this->getOption('retry');
f_putenv('SPC_DOWNLOAD_RETRIES=' . $retry);
@@ -160,20 +125,6 @@ class DownloadCommand extends BaseCommand
$chosen_sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
// Handle multiple PHP versions
// If php-src is in the sources, replace it with version-specific sources
if (in_array('php-src', $chosen_sources)) {
// Remove php-src from the list
$chosen_sources = array_diff($chosen_sources, ['php-src']);
// Add version-specific php-src for each version
foreach ($php_versions as $ver) {
$version_specific_name = "php-src-{$ver}";
$chosen_sources[] = $version_specific_name;
// Store the version for this specific php-src
f_putenv("SPC_PHP_VERSION_{$version_specific_name}={$ver}");
}
}
$sss = $this->getOption('ignore-cache-sources');
if ($sss === false) {
// false is no-any-ignores, that is, default.
@@ -250,16 +201,7 @@ class DownloadCommand extends BaseCommand
logger()->info("[{$ni}/{$cnt}] Downloading source {$source} from custom git: {$new_config['url']}");
Downloader::downloadSource($source, $new_config, true);
} else {
// Handle version-specific php-src (php-src-8.2, php-src-8.3, etc.)
if (preg_match('/^php-src-[\d.]+$/', $source)) {
$config = Config::getSource('php-src');
if ($config === null) {
logger()->error('php-src configuration not found in source.json');
return static::FAILURE;
}
} else {
$config = Config::getSource($source);
}
$config = Config::getSource($source);
// Prefer pre-built, we need to search pre-built library
if ($this->getOption('prefer-pre-built') && ($config['provide-pre-built'] ?? false) === true) {
// We need to replace pattern
@@ -280,9 +222,8 @@ class DownloadCommand extends BaseCommand
logger()->warning("Pre-built content not found for {$source}, fallback to source download");
}
logger()->info("[{$ni}/{$cnt}] Downloading source {$source}");
$force_download = $force_all || in_array($source, $force_list) || str_starts_with($source, 'php-src-') && in_array('php-src', $force_list);
try {
Downloader::downloadSource($source, $config, $force_download);
Downloader::downloadSource($source, $config, $force_all || in_array($source, $force_list));
} catch (SPCException $e) {
// if `--no-alt` option is set, we will not download alternative sources
if ($this->getOption('no-alt')) {
@@ -300,7 +241,7 @@ class DownloadCommand extends BaseCommand
logger()->notice("Trying to download alternative sources for {$source}");
$alt_config = array_merge($config, $alt_sources);
}
Downloader::downloadSource($source, $alt_config, $force_download);
Downloader::downloadSource($source, $alt_config, $force_all || in_array($source, $force_list));
}
}
}
@@ -421,284 +362,4 @@ class DownloadCommand extends BaseCommand
}
return static::FAILURE;
}
private function handleUpdate(): int
{
logger()->info('Checking sources for updates...');
// Get lock file content
$lock_file_path = LockFile::LOCK_FILE;
if (!file_exists($lock_file_path)) {
logger()->warning('No lock file found. Please download sources first using "bin/spc download"');
return static::FAILURE;
}
$lock_content = json_decode(file_get_contents($lock_file_path), true);
if ($lock_content === null || !is_array($lock_content)) {
logger()->error('Failed to parse lock file');
return static::FAILURE;
}
// Filter sources to check
$sources_arg = $this->getArgument('sources');
if (!empty($sources_arg)) {
$requested_sources = array_map('trim', array_filter(explode(',', $sources_arg)));
$sources_to_check = [];
foreach ($requested_sources as $source) {
if (isset($lock_content[$source])) {
$sources_to_check[$source] = $lock_content[$source];
} else {
logger()->warning("Source '{$source}' not found in lock file, skipping");
}
}
} else {
$sources_to_check = $lock_content;
}
// Filter out pre-built sources (they are derivatives)
$sources_to_check = array_filter($sources_to_check, function ($lock_item, $name) {
// Skip pre-built sources (they contain OS/arch in the name)
if (str_contains($name, '-Linux-') || str_contains($name, '-Windows-') || str_contains($name, '-Darwin-')) {
logger()->debug("Skipping pre-built source: {$name}");
return false;
}
return true;
}, ARRAY_FILTER_USE_BOTH);
if (empty($sources_to_check)) {
logger()->warning('No sources to check');
return static::FAILURE;
}
$total = count($sources_to_check);
$current = 0;
$updated_sources = [];
foreach ($sources_to_check as $name => $lock_item) {
++$current;
try {
// Handle version-specific php-src (php-src-8.2, php-src-8.3, etc.)
if (preg_match('/^php-src-[\d.]+$/', $name)) {
$config = Config::getSource('php-src');
} else {
$config = Config::getSource($name);
}
if ($config === null) {
logger()->warning("[{$current}/{$total}] Source '{$name}' not found in source config, skipping");
continue;
}
// Check and update based on source type
$source_type = $lock_item['source_type'] ?? 'unknown';
if ($source_type === SPC_SOURCE_ARCHIVE) {
if ($this->checkArchiveSourceUpdate($name, $lock_item, $config, $current, $total)) {
$updated_sources[] = $name;
}
} elseif ($source_type === SPC_SOURCE_GIT) {
if ($this->checkGitSourceUpdate($name, $lock_item, $config, $current, $total)) {
$updated_sources[] = $name;
}
} elseif ($source_type === SPC_SOURCE_LOCAL) {
logger()->debug("[{$current}/{$total}] Source '{$name}' is local, skipping");
} else {
logger()->warning("[{$current}/{$total}] Unknown source type '{$source_type}' for '{$name}', skipping");
}
} catch (\Throwable $e) {
logger()->error("[{$current}/{$total}] Error checking '{$name}': {$e->getMessage()}");
continue;
}
}
// Output summary
if (empty($updated_sources)) {
logger()->info('All sources are up to date.');
} else {
logger()->info('Updated sources: ' . implode(', ', $updated_sources));
// Write updated sources to file
$date = date('Y-m-d');
$update_file = DOWNLOAD_PATH . '/.update-' . $date . '.txt';
if (file_exists($update_file)) {
$existing_content = file_get_contents($update_file);
$existing_sources = array_map('trim', explode(',', $existing_content));
$updated_sources = array_unique(array_merge($existing_sources, $updated_sources));
}
$content = implode(',', $updated_sources);
file_put_contents($update_file, $content);
logger()->debug("Updated sources written to: {$update_file}");
}
return static::SUCCESS;
}
private function checkCustomSourceUpdate(string $name, array $lock, array $config, int $current, int $total): ?array
{
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source');
foreach ($classes as $class) {
// Support php-src and php-src-X.Y patterns
$matches = ($class::NAME === $name) ||
($class::NAME === 'php-src' && preg_match('/^php-src(-[\d.]+)?$/', $name));
if (is_a($class, CustomSourceBase::class, true) && $matches) {
try {
$config['source_name'] = $name;
return (new $class())->update($lock, $config);
} catch (\Throwable $e) {
logger()->warning("[{$current}/{$total}] Failed to check '{$name}': {$e->getMessage()}");
return null;
}
}
}
logger()->debug("[{$current}/{$total}] Custom source handler for '{$name}' not found");
return null;
}
/**
* Check and update an archive source
*
* @param string $name Source name
* @param array $lock Lock file entry
* @param array $config Source configuration
* @param int $current Current progress number
* @param int $total Total sources to check
* @return bool True if updated, false otherwise
*/
private function checkArchiveSourceUpdate(string $name, array $lock, array $config, int $current, int $total): bool
{
$type = $config['type'] ?? 'unknown';
$locked_filename = $lock['filename'] ?? '';
// Skip local types that don't support version detection
if (in_array($type, ['local', 'unknown'])) {
logger()->debug("[{$current}/{$total}] Source '{$name}' (type: {$type}) doesn't support version detection, skipping");
return false;
}
try {
$latest_info = match ($type) {
'ghtar' => Downloader::getLatestGithubTarball($name, $config),
'ghtagtar' => Downloader::getLatestGithubTarball($name, $config, 'tags'),
'ghrel' => Downloader::getLatestGithubRelease($name, $config),
'pie' => Downloader::getPIEInfo($name, $config),
'bitbuckettag' => Downloader::getLatestBitbucketTag($name, $config),
'filelist' => Downloader::getFromFileList($name, $config),
'url' => Downloader::getLatestUrlInfo($name, $config),
'custom' => $this->checkCustomSourceUpdate($name, $lock, $config, $current, $total),
default => null,
};
if ($latest_info === null) {
logger()->warning("[{$current}/{$total}] Could not get version info for '{$name}' (type: {$type})");
return false;
}
$latest_filename = $latest_info[1] ?? '';
// Compare filenames
if ($locked_filename !== $latest_filename) {
logger()->info("[{$current}/{$total}] Update available for '{$name}': {$locked_filename}{$latest_filename}");
$this->downloadSourceForUpdate($name, $config, $current, $total);
return true;
}
logger()->info("[{$current}/{$total}] Source '{$name}' is up to date");
return false;
} catch (DownloaderException $e) {
logger()->warning("[{$current}/{$total}] Failed to check '{$name}': {$e->getMessage()}");
return false;
}
}
/**
* Check and update a git source
*
* @param string $name Source name
* @param array $lock Lock file entry
* @param array $config Source configuration
* @param int $current Current progress number
* @param int $total Total sources to check
* @return bool True if updated, false otherwise
*/
private function checkGitSourceUpdate(string $name, array $lock, array $config, int $current, int $total): bool
{
$locked_hash = $lock['hash'] ?? '';
$url = $config['url'] ?? '';
$branch = $config['rev'] ?? 'main';
if (empty($url)) {
logger()->warning("[{$current}/{$total}] No URL found for git source '{$name}'");
return false;
}
try {
$remote_hash = $this->getRemoteGitCommit($url, $branch);
if ($remote_hash === null) {
logger()->warning("[{$current}/{$total}] Could not fetch remote commit for '{$name}'");
return false;
}
// Compare hashes (use first 7 chars for display)
$locked_short = substr($locked_hash, 0, 7);
$remote_short = substr($remote_hash, 0, 7);
if ($locked_hash !== $remote_hash) {
logger()->info("[{$current}/{$total}] Update available for '{$name}': {$locked_short}{$remote_short}");
$this->downloadSourceForUpdate($name, $config, $current, $total);
return true;
}
logger()->info("[{$current}/{$total}] Source '{$name}' is up to date");
return false;
} catch (\Throwable $e) {
logger()->warning("[{$current}/{$total}] Failed to check '{$name}': {$e->getMessage()}");
return false;
}
}
/**
* Download a source after removing old lock entry
*
* @param string $name Source name
* @param array $config Source configuration
* @param int $current Current progress number
* @param int $total Total sources to check
*/
private function downloadSourceForUpdate(string $name, array $config, int $current, int $total): void
{
logger()->info("[{$current}/{$total}] Downloading '{$name}'...");
// Remove old lock entry
LockFile::put($name, null);
// Download new version
Downloader::downloadSource($name, $config, true);
}
/**
* Get remote git commit hash without cloning
*
* @param string $url Git repository URL
* @param string $branch Branch or tag to check
* @return null|string Remote commit hash or null on failure
*/
private function getRemoteGitCommit(string $url, string $branch): ?string
{
try {
$cmd = SPC_GIT_EXEC . ' ls-remote ' . escapeshellarg($url) . ' ' . escapeshellarg($branch);
f_exec($cmd, $output, $ret);
if ($ret !== 0 || empty($output)) {
return null;
}
// Output format: "commit_hash\trefs/heads/branch" or "commit_hash\tHEAD"
$parts = preg_split('/\s+/', $output[0]);
return $parts[0] ?? null;
} catch (\Throwable $e) {
logger()->debug("Failed to fetch remote git commit: {$e->getMessage()}");
return null;
}
}
}

View File

@@ -97,9 +97,8 @@ class Downloader
public static function getLatestGithubTarball(string $name, array $source, string $type = 'releases'): array
{
logger()->debug("finding {$name} source from github {$type} tarball");
$source['query'] ??= '';
$data = json_decode(self::curlExec(
url: "https://api.github.com/repos/{$source['repo']}/{$type}{$source['query']}",
url: "https://api.github.com/repos/{$source['repo']}/{$type}",
hooks: [[CurlHook::class, 'setupGithubToken']],
retries: self::getRetryAttempts()
), true, 512, JSON_THROW_ON_ERROR);
@@ -109,9 +108,6 @@ class Downloader
if (($rel['prerelease'] ?? false) === true && ($source['prefer-stable'] ?? false)) {
continue;
}
if (($rel['draft'] ?? false) === true && (($source['prefer-stable'] ?? false) || !$rel['tarball_url'])) {
continue;
}
if (!($source['match'] ?? null)) {
$url = $rel['tarball_url'] ?? null;
break;
@@ -220,39 +216,6 @@ class Downloader
return [$source['url'] . end($versions), end($versions), key($versions)];
}
/**
* Get latest version from direct URL (detect redirect and filename)
*
* @param string $name Source name
* @param array $source Source meta info: [url]
* @return array<int, string> [url, filename]
*/
public static function getLatestUrlInfo(string $name, array $source): array
{
logger()->debug("finding {$name} source from direct url");
$url = $source['url'];
$headers = self::curlExec(
url: $url,
method: 'HEAD',
retries: self::getRetryAttempts()
);
// Find redirect location if any
if (preg_match('/^location:\s+(?<url>.+)$/im', $headers, $matches)) {
$url = trim($matches['url']);
// If it's a relative URL, we need to handle it, but usually it's absolute for downloads
}
// Find filename from content-disposition
if (preg_match('/^content-disposition:\s+attachment;\s*filename=("?)(?<filename>.+)\1/im', $headers, $matches)) {
$filename = trim($matches['filename']);
} else {
$filename = $source['filename'] ?? basename($url);
}
return [$url, $filename];
}
/**
* Download file from URL
*
@@ -280,7 +243,7 @@ class Downloader
if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
$name = self::getPreBuiltLockName($name);
}
LockFile::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'url' => $url, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
LockFile::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
}
/**
@@ -339,7 +302,7 @@ class Downloader
}
// Lock
logger()->debug("Locking git source {$name}");
LockFile::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'url' => $url, 'rev' => $branch, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]);
LockFile::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]);
/*
// 复制目录过去
@@ -689,7 +652,8 @@ class Downloader
self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as);
break;
case 'url': // Direct download URL
[$url, $filename] = self::getLatestUrlInfo($name, $conf);
$url = $conf['url'];
$filename = $conf['filename'] ?? basename($conf['url']);
self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as);
break;
case 'git': // Git repo
@@ -699,8 +663,6 @@ class Downloader
LockFile::lockSource($name, [
'source_type' => SPC_SOURCE_LOCAL,
'dirname' => $conf['dirname'],
'url' => null,
'path' => $conf['path'] ?? null,
'move_path' => $conf['path'] ?? $conf['extract'] ?? null,
'lock_as' => $download_as,
]);
@@ -716,11 +678,7 @@ class Downloader
...FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg'),
];
foreach ($classes as $class) {
// Support php-src and php-src-X.Y patterns
$matches = ($class::NAME === $name) ||
($class::NAME === 'php-src' && preg_match('/^php-src(-[\d.]+)?$/', $name));
if (is_a($class, CustomSourceBase::class, true) && $matches) {
$conf['source_name'] = $name; // Pass the actual source name
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
(new $class())->fetch($force, $conf, $download_as);
break;
}

View File

@@ -408,15 +408,15 @@ class FileSystem
continue;
}
$sub_file = self::convertPath($dir . '/' . $v);
if (is_link($sub_file) || is_file($sub_file)) {
if (!unlink($sub_file)) {
return false;
}
} elseif (is_dir($sub_file)) {
if (is_dir($sub_file)) {
# 如果是 目录 且 递推 , 则递推添加下级文件
if (!self::removeDir($sub_file)) {
return false;
}
} elseif (is_link($sub_file) || is_file($sub_file)) {
if (!unlink($sub_file)) {
return false;
}
}
}
if (is_link($dir)) {
@@ -572,44 +572,6 @@ class FileSystem
return file_put_contents($file, implode('', $lines));
}
/**
* Move file or directory, handling cross-device scenarios
* Uses rename() if possible, falls back to copy+delete for cross-device moves
*
* @param string $source Source path
* @param string $dest Destination path
*/
public static function moveFileOrDir(string $source, string $dest): void
{
$source = self::convertPath($source);
$dest = self::convertPath($dest);
// Check if source and dest are on the same device to avoid cross-device rename errors
$source_stat = @stat($source);
$dest_parent = dirname($dest);
$dest_stat = @stat($dest_parent);
// Only use rename if on same device
if ($source_stat !== false && $dest_stat !== false && $source_stat['dev'] === $dest_stat['dev']) {
if (@rename($source, $dest)) {
return;
}
}
// Fall back to copy + delete for cross-device moves or if rename failed
if (is_dir($source)) {
self::copyDir($source, $dest);
self::removeDir($source);
} else {
if (!copy($source, $dest)) {
throw new FileSystemException("Failed to copy file from {$source} to {$dest}");
}
if (!unlink($source)) {
throw new FileSystemException("Failed to remove source file: {$source}");
}
}
}
private static function extractArchive(string $filename, string $target): void
{
// Create base dir
@@ -686,6 +648,36 @@ class FileSystem
};
}
/**
* Move file or directory, handling cross-device scenarios
* Uses rename() if possible, falls back to copy+delete for cross-device moves
*
* @param string $source Source path
* @param string $dest Destination path
*/
private static function moveFileOrDir(string $source, string $dest): void
{
$source = self::convertPath($source);
$dest = self::convertPath($dest);
// Try rename first (fast, atomic)
if (@rename($source, $dest)) {
return;
}
if (is_dir($source)) {
self::copyDir($source, $dest);
self::removeDir($source);
} else {
if (!copy($source, $dest)) {
throw new FileSystemException("Failed to copy file from {$source} to {$dest}");
}
if (!unlink($source)) {
throw new FileSystemException("Failed to remove source file: {$source}");
}
}
}
/**
* Unzip file with stripping top-level directory
*/

View File

@@ -155,7 +155,6 @@ class LockFile
* @param string $name Source name
* @param array{
* source_type: string,
* url: ?string,
* dirname?: ?string,
* filename?: ?string,
* move_path: ?string,

View File

@@ -39,14 +39,7 @@ class SourceManager
// start check
foreach ($sources_extracted as $source => $item) {
$extract_dir_name = $source;
// Handle version-specific php-src (php-src-8.2, php-src-8.3, etc.)
$source_config = Config::getSource($source);
if ($source_config === null && preg_match('/^php-src-[\d.]+$/', $source)) {
$source_config = Config::getSource('php-src');
$extract_dir_name = 'php-src';
}
if ($source_config === null) {
if (Config::getSource($source) === null) {
throw new WrongUsageException("Source [{$source}] does not exist, please check the name and correct it !");
}
// check source downloaded
@@ -63,12 +56,12 @@ class SourceManager
$lock_content = LockFile::get($lock_name);
// check source dir exist
$check = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $extract_dir_name);
$check = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source);
// $check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']);
if (!is_dir($check)) {
logger()->debug("Extracting source [{$source}] to {$check} ...");
$filename = LockFile::getLockFullPath($lock_content);
FileSystem::extractSource($extract_dir_name, $lock_content['source_type'], $filename, $check);
FileSystem::extractSource($source, $lock_content['source_type'], $filename, $check);
LockFile::putLockSourceHash($lock_content, $check);
continue;
}
@@ -96,7 +89,7 @@ class SourceManager
logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ...");
FileSystem::removeDir($check);
$filename = LockFile::getLockFullPath($lock_content);
$move_path = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $extract_dir_name);
$move_path = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source);
FileSystem::extractSource($source, $lock_content['source_type'], $filename, $move_path);
LockFile::putLockSourceHash($lock_content, $check);
}

View File

@@ -25,7 +25,6 @@ class SourcePatcher
FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchFfiCentos7FixO3strncmp']);
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']);
@@ -433,23 +432,6 @@ class SourcePatcher
return false;
}
/**
* Fix the compilation issue of pdo_sqlsrv with php 8.5
*/
public static function patchSQLSRVPhp85(): bool
{
$source_dir = SOURCE_PATH . '/php-src/ext/pdo_sqlsrv';
if (!file_exists($source_dir . '/config.m4') && is_dir($source_dir . '/source/pdo_sqlsrv')) {
FileSystem::moveFileOrDir($source_dir . '/LICENSE', $source_dir . '/source/pdo_sqlsrv/LICENSE');
FileSystem::moveFileOrDir($source_dir . '/source/shared', $source_dir . '/source/pdo_sqlsrv/shared');
FileSystem::moveFileOrDir($source_dir . '/source/pdo_sqlsrv', SOURCE_PATH . '/pdo_sqlsrv');
FileSystem::removeDir($source_dir);
FileSystem::moveFileOrDir(SOURCE_PATH . '/pdo_sqlsrv', $source_dir);
return true;
}
return false;
}
public static function patchYamlWin32(): bool
{
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/ext/yaml/config.w32', "lib.substr(lib.length - 6, 6) == '_a.lib'", "lib.substr(lib.length - 6, 6) == '_a.lib' || 'yes' == 'yes'");

View File

@@ -48,10 +48,10 @@ class GoXcaddy extends CustomPackage
'macos' => 'darwin',
default => throw new \InvalidArgumentException('Unsupported OS: ' . $name),
};
[$go_version] = explode("\n", Downloader::curlExec('https://go.dev/VERSION?m=text'));
$go_version = '1.25.0';
$config = [
'type' => 'url',
'url' => "https://go.dev/dl/{$go_version}.{$os}-{$arch}.tar.gz",
'url' => "https://go.dev/dl/go{$go_version}.{$os}-{$arch}.tar.gz",
];
Downloader::downloadPackage($name, $config, $force);
}

View File

@@ -72,11 +72,8 @@ class Zig extends CustomPackage
$latest_version = null;
foreach ($index_json as $version => $data) {
// Skip the master branch, get the latest stable release
if ($version !== 'master') {
$latest_version = $version;
break;
}
$latest_version = $version;
break;
}
if (!$latest_version) {

View File

@@ -25,13 +25,4 @@ abstract class CustomSourceBase
* @param int $lock_as Lock type constant
*/
abstract public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void;
/**
* Update the source from its repository
*
* @param array $lock Lock file entry
* @param array $config Optional configuration array
* @return null|array Latest version info [url, filename], or null if no update needed
*/
abstract public function update(array $lock, ?array $config = null): ?array;
}

View File

@@ -7,7 +7,6 @@ namespace SPC\store\source;
use JetBrains\PhpStorm\ArrayShape;
use SPC\exception\DownloaderException;
use SPC\store\Downloader;
use SPC\store\LockFile;
class PhpSource extends CustomSourceBase
{
@@ -15,46 +14,12 @@ class PhpSource extends CustomSourceBase
public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void
{
$source_name = $config['source_name'] ?? 'php-src';
// Try to extract version from source name (e.g., "php-src-8.2" -> "8.2")
if (preg_match('/^php-src-([\d.]+)$/', $source_name, $matches)) {
$major = $matches[1];
} else {
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4';
}
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4';
if ($major === 'git') {
Downloader::downloadSource($source_name, ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force);
Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force);
} else {
Downloader::downloadSource($source_name, $this->getLatestPHPInfo($major), $force);
Downloader::downloadSource('php-src', $this->getLatestPHPInfo($major), $force);
}
if ($source_name !== 'php-src') {
LockFile::put('php-src', LockFile::get($source_name));
}
}
public function update(array $lock, ?array $config = null): ?array
{
$source_name = $config['source_name'] ?? 'php-src';
// Try to extract version from source name (e.g., "php-src-8.2" -> "8.2")
if (preg_match('/^php-src-([\d.]+)$/', $source_name, $matches)) {
$major = $matches[1];
} else {
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4';
}
if ($major === 'git') {
return null;
}
$latest_php = $this->getLatestPHPInfo($major);
$latest_url = $latest_php['url'];
$filename = basename($latest_url);
return [$latest_url, $filename];
}
/**

View File

@@ -15,13 +15,6 @@ class PostgreSQLSource extends CustomSourceBase
Downloader::downloadSource('postgresql', self::getLatestInfo(), $force);
}
public function update(array $lock, ?array $config = null): ?array
{
$latest = $this->getLatestInfo();
$filename = basename($latest['url']);
return [$latest['url'], $filename];
}
public function getLatestInfo(): array
{
[, $filename, $version] = Downloader::getFromFileList('postgresql', [

View File

@@ -67,8 +67,7 @@ class ZigToolchain implements ToolchainInterface
$cflags = getenv('SPC_DEFAULT_C_FLAGS') ?: getenv('CFLAGS') ?: '';
$has_avx512 = str_contains($cflags, '-mavx512') || str_contains($cflags, '-march=x86-64-v4');
if (!$has_avx512) {
$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}");
GlobalEnvManager::putenv('SPC_EXTRA_PHP_VARS=php_cv_have_avx512=no php_cv_have_avx512vbmi=no');
}
}

View File

@@ -226,17 +226,9 @@ class SPCConfigUtil
// parse pkg-configs
foreach ($libraries as $library) {
$pc = Config::getLib($library, 'pkg-configs', []);
$pkg_config_path = getenv('PKG_CONFIG_PATH') ?: '';
$search_paths = array_filter(explode(is_unix() ? ':' : ';', $pkg_config_path));
foreach ($pc as $file) {
$found = false;
foreach ($search_paths as $path) {
if (file_exists($path . "/{$file}.pc")) {
$found = true;
}
}
if (!$found) {
throw new WrongUsageException("pkg-config file '{$file}.pc' for lib [{$library}] does not exist. Please build it first.");
if (!file_exists(BUILD_LIB_PATH . "/pkgconfig/{$file}.pc")) {
throw new WrongUsageException("pkg-config file '{$file}.pc' for lib [{$library}] does not exist in '" . BUILD_LIB_PATH . "/pkgconfig'. Please build it first.");
}
}
$pc_cflags = implode(' ', $pc);
@@ -265,17 +257,9 @@ class SPCConfigUtil
foreach ($libraries as $library) {
// add pkg-configs libs
$pkg_configs = Config::getLib($library, 'pkg-configs', []);
$pkg_config_path = getenv('PKG_CONFIG_PATH') ?: '';
$search_paths = array_filter(explode(is_unix() ? ':' : ';', $pkg_config_path));
foreach ($pkg_configs as $file) {
$found = false;
foreach ($search_paths as $path) {
if (file_exists($path . "/{$file}.pc")) {
$found = true;
}
}
if (!$found) {
throw new WrongUsageException("pkg-config file '{$file}.pc' for lib [{$library}] does not exist. Please build it first.");
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 [{$library}] does not exist in '" . BUILD_LIB_PATH . "/pkgconfig'. Please build it first.");
}
}
$pkg_configs = implode(' ', $pkg_configs);

View File

@@ -13,7 +13,7 @@ declare(strict_types=1);
// test php version (8.1 ~ 8.4 available, multiple for matrix)
$test_php_version = [
// '8.1',
'8.1',
'8.2',
'8.3',
'8.4',
@@ -28,14 +28,14 @@ $test_os = [
'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
'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',
];
// whether enable thread safe
$zts = true;
$zts = false;
$no_strip = false;
@@ -50,13 +50,13 @@ $prefer_pre_built = false;
// 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',
'Linux', 'Darwin' => 'maxminddb',
'Windows' => 'bcmath',
};
// If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`).
$shared_extensions = match (PHP_OS_FAMILY) {
'Linux' => 'grpc,mysqlnd_parsec,mysqlnd_ed25519',
'Linux' => '',
'Darwin' => '',
'Windows' => '',
};
@@ -66,7 +66,7 @@ $with_suggested_libs = false;
// If you want to test extra libs for extensions, add them below (comma separated, example `libwebp,libavif`). Unnecessary, when $with_suggested_libs is true.
$with_libs = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'libwebp',
'Linux', 'Darwin' => '',
'Windows' => '',
};