diff --git a/config/artifact/ncurses.yml b/config/artifact/ncurses.yml new file mode 100644 index 000000000..52c8f59ff --- /dev/null +++ b/config/artifact/ncurses.yml @@ -0,0 +1,9 @@ +ncurses: + binary: hosted + metadata: + license-files: + - COPYING + source: + type: filelist + url: 'https://ftp.gnu.org/pub/gnu/ncurses/' + regex: '/href="(?ncurses-(?[^"]+)\.tar\.gz)"/' diff --git a/config/env.ini b/config/env.ini index 9e295d796..3143efaf5 100644 --- a/config/env.ini +++ b/config/env.ini @@ -121,6 +121,8 @@ SPC_CMD_PREFIX_PHP_CONFIGURE="./configure --prefix= --with-valgrind=no --disable SPC_CMD_VAR_PHP_EMBED_TYPE="static" ; EXTRA_CFLAGS for `configure` and `make` php SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fno-ident -fPIE ${SPC_DEFAULT_C_FLAGS}" +; EXTRA_CXXFLAGS for `configure` and `make` php +SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS="-g -fstack-protector-strong -fno-ident -fPIE ${SPC_DEFAULT_CXX_FLAGS}" ; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS="" @@ -155,5 +157,9 @@ SPC_CMD_PREFIX_PHP_CONFIGURE="./configure --prefix= --with-valgrind=no --enable- SPC_CMD_VAR_PHP_EMBED_TYPE="static" ; EXTRA_CFLAGS for `configure` and `make` php SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie -Werror=unknown-warning-option ${SPC_DEFAULT_C_FLAGS}" +; EXTRA_CXXFLAGS for `configure` and `make` php +SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS="-g -fstack-protector-strong -fno-ident -fpie -Werror=unknown-warning-option ${SPC_DEFAULT_CXX_FLAGS}" +; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.dylib +SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS="" ; minimum compatible macOS version (LLVM vars, availability not guaranteed) MACOSX_DEPLOYMENT_TARGET=12.0 diff --git a/config/pkg/ext/builtin-extensions.yml b/config/pkg/ext/builtin-extensions.yml index 0149fe974..b938182c0 100644 --- a/config/pkg/ext/builtin-extensions.yml +++ b/config/pkg/ext/builtin-extensions.yml @@ -1,5 +1,110 @@ ext-bcmath: type: php-extension +ext-bz2: + type: php-extension + depends: + - bzip2 + php-extension: + arg-type@unix: with-path + arg-type@windows: with +ext-calendar: + type: php-extension +ext-ctype: + type: php-extension +ext-curl: + type: php-extension + depends: + - curl + depends@windows: + - ext-zlib + - ext-openssl + php-extension: + arg-type: with +ext-dba: + type: php-extension + suggests: + - qdbm + php-extension: + arg-type: custom +ext-dom: + type: php-extension + depends: + - libxml2 + - ext-xml + php-extension: + arg-type: '--enable-dom@shared_suffix@ --with-libxml=@build_root_path@' + arg-type@windows: with +ext-exif: + type: php-extension +ext-ffi: + type: php-extension + depends@unix: + - libffi + php-extension: + arg-type@unix: '--with-ffi=@shared_suffix@ --enable-zend-signals' + arg-type@windows: with +ext-fileinfo: + type: php-extension +ext-filter: + type: php-extension +ext-ftp: + type: php-extension + suggests: + - ext-openssl +ext-gd: + type: php-extension + depends: + - zlib + - libpng + - ext-zlib + suggests: + - libavif + - libwebp + - libjpeg + - freetype + php-extension: + arg-type: custom +ext-gettext: + type: php-extension + depends: + - gettext + php-extension: + arg-type: with-path +ext-gmp: + type: php-extension + depends: + - gmp + php-extension: + arg-type: with-path +ext-iconv: + type: php-extension + depends@unix: + - libiconv + php-extension: + arg-type@unix: with-path + arg-type@windows: with +ext-intl: + type: php-extension + depends@unix: + - icu +ext-ldap: + type: php-extension + depends: + - ldap + suggests: + - gmp + - libsodium + - ext-openssl + php-extension: + arg-type: with-path +ext-libxml: + type: php-extension + depends: + - ext-xml + php-extension: + build-with-php: true + build-shared: false + arg-type: none ext-mbregex: type: php-extension depends: @@ -14,6 +119,34 @@ ext-mbstring: type: php-extension php-extension: arg-type: custom +ext-mysqli: + type: php-extension + depends: + - ext-mysqlnd + php-extension: + arg-type: with + build-with-php: true +ext-mysqlnd: + type: php-extension + depends: + - zlib + php-extension: + arg-type@unix: enable + arg-type@windows: with + build-with-php: true +ext-odbc: + type: php-extension + depends@unix: + - unixodbc + php-extension: + arg-type@unix: '--with-unixODBC@shared_path_suffix@' +ext-opcache: + type: php-extension + php-extension: + arg-type@unix: custom + arg-type@windows: enable + zend-extension: true + display-name: 'Zend Opcache' ext-openssl: type: php-extension depends: @@ -24,10 +157,65 @@ ext-openssl: arg-type: custom arg-type@windows: with build-with-php: true +ext-password-argon2: + type: php-extension + depends: + - libargon2 + - ext-openssl + php-extension: + arg-type: custom + display-name: '' +ext-pcntl: + type: php-extension +ext-pdo: + type: php-extension +ext-pdo_mysql: + type: php-extension + depends: + - ext-pdo + - ext-mysqlnd + php-extension: + arg-type: with +ext-pdo_odbc: + type: php-extension + depends: + - ext-pdo + - ext-odbc + depends@unix: + - unixodbc + - ext-pdo + - ext-odbc + php-extension: + arg-type: custom +ext-pdo_pgsql: + type: php-extension + depends@unix: + - ext-pdo + - ext-pgsql + - postgresql + php-extension: + arg-type@unix: with-path + arg-type@windows: '--with-pdo-pgsql=yes' +ext-pdo_sqlite: + type: php-extension + depends: + - ext-pdo + - ext-sqlite3 + - sqlite + php-extension: + arg-type: with +ext-pgsql: + type: php-extension + depends@unix: + - postgresql + php-extension: + arg-type: custom ext-phar: type: php-extension depends: - zlib +ext-posix: + type: php-extension ext-readline: type: php-extension depends: @@ -36,9 +224,118 @@ ext-readline: support: Windows: wip BSD: wip - arg-type: with-path + arg-type: '--with-libedit --without-readline' build-shared: false build-static: true +ext-session: + type: php-extension +ext-shmop: + type: php-extension + php-extension: + build-with-php: true +ext-simplexml: + type: php-extension + depends: + - ext-xml + php-extension: + arg-type@unix: '--enable-simplexml@shared_suffix@ --with-libxml=@build_root_path@' + arg-type@windows: with + build-with-php: true +ext-snmp: + type: php-extension + depends: + - net-snmp + php-extension: + arg-type: with +ext-soap: + type: php-extension + depends: + - ext-xml + - ext-session + php-extension: + arg-type@unix: '--enable-soap@shared_suffix@ --with-libxml=@build_root_path@' + arg-type@windows: with + build-with-php: true +ext-sockets: + type: php-extension +ext-sodium: + type: php-extension + depends: + - libsodium + php-extension: + arg-type: with +ext-sqlite3: + type: php-extension + depends: + - sqlite + php-extension: + arg-type@unix: with-path + arg-type@windows: with + build-with-php: true +ext-sysvmsg: + type: php-extension + php-extension: + support: + Windows: 'no' + BSD: wip +ext-sysvsem: + type: php-extension + php-extension: + support: + Windows: 'no' + BSD: wip +ext-sysvshm: + type: php-extension + php-extension: + support: + BSD: wip +ext-tidy: + type: php-extension + depends: + - tidy + php-extension: + support: + Windows: wip + BSD: wip + arg-type: with-path +ext-tokenizer: + type: php-extension + php-extension: + build-with-php: true +ext-xml: + type: php-extension + depends: + - libxml2 + depends@windows: + - libxml2 + - ext-iconv + php-extension: + arg-type: '--enable-xml@shared_suffix@ --with-libxml=@build_root_path@' + arg-type@windows: with + build-with-php: true +ext-xmlreader: + type: php-extension + depends: + - libxml2 + php-extension: + arg-type: '--enable-xmlreader@shared_suffix@ --with-libxml=@build_root_path@' + build-with-php: true +ext-xmlwriter: + type: php-extension + depends: + - libxml2 + php-extension: + arg-type: '--enable-xmlwriter@shared_suffix@ --with-libxml=@build_root_path@' + build-with-php: true +ext-xsl: + type: php-extension + depends: + - libxslt + - ext-xml + - ext-dom + php-extension: + arg-type: with-path + build-with-php: true ext-zlib: type: php-extension depends: diff --git a/config/pkg/ext/ext-ast.yml b/config/pkg/ext/ext-ast.yml index 0684959dd..776b82dff 100644 --- a/config/pkg/ext/ext-ast.yml +++ b/config/pkg/ext/ext-ast.yml @@ -4,3 +4,6 @@ ext-ast: source: type: pecl name: ast + metadata: + license-files: [LICENSE] + license: BSD-3-Clause diff --git a/config/pkg/ext/ext-brotli.yml b/config/pkg/ext/ext-brotli.yml new file mode 100644 index 000000000..147ecb636 --- /dev/null +++ b/config/pkg/ext/ext-brotli.yml @@ -0,0 +1,13 @@ +ext-brotli: + type: php-extension + artifact: + source: + type: git + extract: php-src/ext/brotli + rev: master + url: 'https://github.com/kjdev/php-ext-brotli' + metadata: + license-files: [LICENSE] + license: MIT + depends: + - brotli diff --git a/config/pkg/ext/ext-dio.yml b/config/pkg/ext/ext-dio.yml new file mode 100644 index 000000000..b445940c3 --- /dev/null +++ b/config/pkg/ext/ext-dio.yml @@ -0,0 +1,9 @@ +ext-dio: + type: php-extension + artifact: + source: + type: pecl + name: dio + metadata: + license-files: [LICENSE] + license: PHP-3.01 diff --git a/config/pkg/ext/ext-ds.yml b/config/pkg/ext/ext-ds.yml new file mode 100644 index 000000000..0c0a4b3c4 --- /dev/null +++ b/config/pkg/ext/ext-ds.yml @@ -0,0 +1,9 @@ +ext-ds: + type: php-extension + artifact: + source: + type: pecl + name: ds + metadata: + license-files: [LICENSE] + license: MIT diff --git a/config/pkg/ext/ext-ev.yml b/config/pkg/ext/ext-ev.yml new file mode 100644 index 000000000..174e5f843 --- /dev/null +++ b/config/pkg/ext/ext-ev.yml @@ -0,0 +1,13 @@ +ext-ev: + type: php-extension + artifact: + source: + type: pecl + name: ev + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - ext-sockets + php-extension: + arg-type@windows: with diff --git a/config/pkg/ext/ext-event.yml b/config/pkg/ext/ext-event.yml new file mode 100644 index 000000000..dd9c1c8ec --- /dev/null +++ b/config/pkg/ext/ext-event.yml @@ -0,0 +1,19 @@ +ext-event: + type: php-extension + artifact: + source: + type: url + url: 'https://bitbucket.org/osmanov/pecl-event/get/3.1.4.tar.gz' + extract: php-src/ext/event + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - libevent + - ext-openssl + suggests: + - ext-sockets + php-extension: + support: + Windows: wip + arg-type: custom diff --git a/config/pkg/ext/ext-excimer.yml b/config/pkg/ext/ext-excimer.yml new file mode 100644 index 000000000..3d0858882 --- /dev/null +++ b/config/pkg/ext/ext-excimer.yml @@ -0,0 +1,9 @@ +ext-excimer: + type: php-extension + artifact: + source: + type: pecl + name: excimer + metadata: + license-files: [LICENSE] + license: PHP-3.01 diff --git a/config/pkg/ext/ext-glfw.yml b/config/pkg/ext/ext-glfw.yml index dc8844a8f..1be1e75f5 100644 --- a/config/pkg/ext/ext-glfw.yml +++ b/config/pkg/ext/ext-glfw.yml @@ -3,3 +3,5 @@ ext-glfw: artifact: glfw depends: - glfw + php-extension: + arg-type@unix: '--enable-glfw --with-glfw-dir=@build_root_path@' diff --git a/config/pkg/ext/ext-gmssl.yml b/config/pkg/ext/ext-gmssl.yml new file mode 100644 index 000000000..7ed8981d7 --- /dev/null +++ b/config/pkg/ext/ext-gmssl.yml @@ -0,0 +1,12 @@ +ext-gmssl: + type: php-extension + artifact: + source: + type: ghtar + repo: gmssl/GmSSL-PHP + extract: php-src/ext/gmssl + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - gmssl diff --git a/config/pkg/ext/ext-grpc.yml b/config/pkg/ext/ext-grpc.yml new file mode 100644 index 000000000..ff5bae7b8 --- /dev/null +++ b/config/pkg/ext/ext-grpc.yml @@ -0,0 +1,14 @@ +ext-grpc: + type: php-extension + artifact: + source: + type: pecl + name: grpc + metadata: + license-files: [LICENSE] + license: Apache-2.0 + depends: + - grpc + lang: cpp + php-extension: + arg-type@unix: enable-path diff --git a/config/pkg/ext/ext-igbinary.yml b/config/pkg/ext/ext-igbinary.yml new file mode 100644 index 000000000..1a80831bb --- /dev/null +++ b/config/pkg/ext/ext-igbinary.yml @@ -0,0 +1,12 @@ +ext-igbinary: + type: php-extension + artifact: + source: + type: pecl + name: igbinary + metadata: + license-files: [COPYING] + license: BSD-3-Clause + suggests: + - ext-session + - ext-apcu diff --git a/config/pkg/ext/ext-imagick.yml b/config/pkg/ext/ext-imagick.yml new file mode 100644 index 000000000..e6f9843eb --- /dev/null +++ b/config/pkg/ext/ext-imagick.yml @@ -0,0 +1,13 @@ +ext-imagick: + type: php-extension + artifact: + source: + type: pecl + name: imagick + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - imagemagick + php-extension: + arg-type: custom diff --git a/config/pkg/ext/ext-imap.yml b/config/pkg/ext/ext-imap.yml new file mode 100644 index 000000000..a6c18daca --- /dev/null +++ b/config/pkg/ext/ext-imap.yml @@ -0,0 +1,15 @@ +ext-imap: + type: php-extension + artifact: + source: + type: pecl + name: imap + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - imap + suggests: + - ext-openssl + php-extension: + arg-type: custom diff --git a/config/pkg/ext/ext-inotify.yml b/config/pkg/ext/ext-inotify.yml new file mode 100644 index 000000000..0956f9e40 --- /dev/null +++ b/config/pkg/ext/ext-inotify.yml @@ -0,0 +1,9 @@ +ext-inotify: + type: php-extension + artifact: + source: + type: pecl + name: inotify + metadata: + license-files: [LICENSE] + license: PHP-3.01 diff --git a/config/pkg/ext/ext-lz4.yml b/config/pkg/ext/ext-lz4.yml new file mode 100644 index 000000000..8a3bb4dba --- /dev/null +++ b/config/pkg/ext/ext-lz4.yml @@ -0,0 +1,15 @@ +ext-lz4: + type: php-extension + artifact: + source: + type: ghtagtar + repo: kjdev/php-ext-lz4 + extract: php-src/ext/lz4 + metadata: + license-files: [LICENSE] + license: MIT + depends: + - liblz4 + php-extension: + arg-type@unix: '--enable-lz4=@shared_suffix@ --with-lz4-includedir=@build_root_path@' + arg-type@windows: '--enable-lz4' diff --git a/config/pkg/ext/ext-maxminddb.yml b/config/pkg/ext/ext-maxminddb.yml new file mode 100644 index 000000000..59d7e4e3d --- /dev/null +++ b/config/pkg/ext/ext-maxminddb.yml @@ -0,0 +1,13 @@ +ext-maxminddb: + type: php-extension + artifact: + source: + type: pecl + name: maxminddb + metadata: + license-files: [LICENSE] + license: Apache-2.0 + depends: + - libmaxminddb + php-extension: + arg-type: with diff --git a/config/pkg/ext/ext-memcache.yml b/config/pkg/ext/ext-memcache.yml new file mode 100644 index 000000000..9db51c05b --- /dev/null +++ b/config/pkg/ext/ext-memcache.yml @@ -0,0 +1,14 @@ +ext-memcache: + type: php-extension + artifact: + source: + type: pecl + name: memcache + metadata: + license-files: [LICENSE] + license: PHP-3.0 + depends: + - ext-zlib + - ext-session + php-extension: + arg-type: '--enable-memcache@shared_suffix@ --with-zlib-dir=@build_root_path@' diff --git a/config/pkg/ext/ext-memcached.yml b/config/pkg/ext/ext-memcached.yml new file mode 100644 index 000000000..329227f2b --- /dev/null +++ b/config/pkg/ext/ext-memcached.yml @@ -0,0 +1,23 @@ +ext-memcached: + type: php-extension + artifact: + source: + type: pecl + name: memcached + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - libmemcached + depends@unix: + - libmemcached + - fastlz + - ext-session + - ext-zlib + suggests: + - zstd + - ext-igbinary + - ext-msgpack + - ext-session + php-extension: + arg-type: '--enable-memcached@shared_suffix@ --with-zlib-dir=@build_root_path@' diff --git a/config/pkg/ext/ext-mongodb.yml b/config/pkg/ext/ext-mongodb.yml new file mode 100644 index 000000000..7cbdbb140 --- /dev/null +++ b/config/pkg/ext/ext-mongodb.yml @@ -0,0 +1,21 @@ +ext-mongodb: + type: php-extension + artifact: + source: + type: ghrel + repo: mongodb/mongo-php-driver + match: mongodb.+\.tgz + extract: php-src/ext/mongodb + metadata: + license-files: [LICENSE] + license: PHP-3.01 + suggests: + - icu + - openssl + - zstd + - zlib + frameworks: + - CoreFoundation + - Security + php-extension: + arg-type: custom diff --git a/config/pkg/ext/ext-msgpack.yml b/config/pkg/ext/ext-msgpack.yml new file mode 100644 index 000000000..8b230c31a --- /dev/null +++ b/config/pkg/ext/ext-msgpack.yml @@ -0,0 +1,14 @@ +ext-msgpack: + type: php-extension + artifact: + source: + type: pecl + name: msgpack + metadata: + license-files: [LICENSE] + license: BSD-3-Clause + depends: + - ext-session + php-extension: + arg-type@unix: with + arg-type@windows: enable diff --git a/config/pkg/ext/ext-mysqlnd_ed25519.yml b/config/pkg/ext/ext-mysqlnd_ed25519.yml new file mode 100644 index 000000000..e7aa3de89 --- /dev/null +++ b/config/pkg/ext/ext-mysqlnd_ed25519.yml @@ -0,0 +1,18 @@ +ext-mysqlnd_ed25519: + type: php-extension + artifact: + source: + type: pie + repo: mariadb/mysqlnd_ed25519 + extract: php-src/ext/mysqlnd_ed25519 + metadata: + license-files: [LICENSE] + license: BSD-3-Clause + depends: + - ext-mysqlnd + - libsodium + suggests: + - openssl + php-extension: + arg-type: '--with-mysqlnd_ed25519=@shared_suffix@' + build-static: false diff --git a/config/pkg/ext/ext-mysqlnd_parsec.yml b/config/pkg/ext/ext-mysqlnd_parsec.yml new file mode 100644 index 000000000..903d65c40 --- /dev/null +++ b/config/pkg/ext/ext-mysqlnd_parsec.yml @@ -0,0 +1,17 @@ +ext-mysqlnd_parsec: + type: php-extension + artifact: + source: + type: pie + repo: mariadb/mysqlnd_parsec + extract: php-src/ext/mysqlnd_parsec + metadata: + license-files: [LICENSE] + license: BSD-3-Clause + depends: + - ext-mysqlnd + - libsodium + - openssl + php-extension: + arg-type: '--enable-mysqlnd_parsec' + build-static: false diff --git a/config/pkg/ext/ext-opentelemetry.yml b/config/pkg/ext/ext-opentelemetry.yml new file mode 100644 index 000000000..5caebef2a --- /dev/null +++ b/config/pkg/ext/ext-opentelemetry.yml @@ -0,0 +1,9 @@ +ext-opentelemetry: + type: php-extension + artifact: + source: + type: pecl + name: opentelemetry + metadata: + license-files: [LICENSE] + license: Apache-2.0 diff --git a/config/pkg/ext/ext-parallel.yml b/config/pkg/ext/ext-parallel.yml new file mode 100644 index 000000000..a3e91efe5 --- /dev/null +++ b/config/pkg/ext/ext-parallel.yml @@ -0,0 +1,9 @@ +ext-parallel: + type: php-extension + artifact: + source: + type: pecl + name: parallel + metadata: + license-files: [LICENSE] + license: PHP-3.01 diff --git a/config/pkg/ext/ext-pcov.yml b/config/pkg/ext/ext-pcov.yml new file mode 100644 index 000000000..3fac61d08 --- /dev/null +++ b/config/pkg/ext/ext-pcov.yml @@ -0,0 +1,12 @@ +ext-pcov: + type: php-extension + artifact: + source: + type: pecl + name: pcov + metadata: + license-files: [LICENSE] + license: PHP-3.01 + php-extension: + build-static: false + build-shared: true diff --git a/config/pkg/ext/ext-pdo_sqlsrv.yml b/config/pkg/ext/ext-pdo_sqlsrv.yml new file mode 100644 index 000000000..6d57333b3 --- /dev/null +++ b/config/pkg/ext/ext-pdo_sqlsrv.yml @@ -0,0 +1,14 @@ +ext-pdo_sqlsrv: + type: php-extension + artifact: + source: + type: pecl + name: pdo_sqlsrv + metadata: + license-files: [LICENSE] + license: MIT + depends: + - ext-pdo + - ext-sqlsrv + php-extension: + arg-type: with diff --git a/config/pkg/ext/ext-protobuf.yml b/config/pkg/ext/ext-protobuf.yml new file mode 100644 index 000000000..020059d39 --- /dev/null +++ b/config/pkg/ext/ext-protobuf.yml @@ -0,0 +1,9 @@ +ext-protobuf: + type: php-extension + artifact: + source: + type: pecl + name: protobuf + metadata: + license-files: [LICENSE] + license: BSD-3-Clause diff --git a/config/pkg/ext/ext-rar.yml b/config/pkg/ext/ext-rar.yml new file mode 100644 index 000000000..1770788a6 --- /dev/null +++ b/config/pkg/ext/ext-rar.yml @@ -0,0 +1,12 @@ +ext-rar: + type: php-extension + artifact: + source: + type: git + url: 'https://github.com/static-php/php-rar.git' + rev: issue-php82 + extract: php-src/ext/rar + metadata: + license-files: [LICENSE] + license: PHP-3.01 + lang: cpp diff --git a/config/pkg/ext/ext-rdkafka.yml b/config/pkg/ext/ext-rdkafka.yml new file mode 100644 index 000000000..1f26e49cb --- /dev/null +++ b/config/pkg/ext/ext-rdkafka.yml @@ -0,0 +1,15 @@ +ext-rdkafka: + type: php-extension + artifact: + source: + type: ghtar + repo: arnaud-lb/php-rdkafka + extract: php-src/ext/rdkafka + metadata: + license-files: [LICENSE] + license: MIT + depends: + - librdkafka + lang: cpp + php-extension: + arg-type: custom diff --git a/config/pkg/ext/ext-redis.yml b/config/pkg/ext/ext-redis.yml new file mode 100644 index 000000000..c05b4ee26 --- /dev/null +++ b/config/pkg/ext/ext-redis.yml @@ -0,0 +1,21 @@ +ext-redis: + type: php-extension + artifact: + source: + type: pecl + name: redis + metadata: + license-files: [LICENSE] + license: PHP-3.01 + suggests: + - ext-session + - ext-igbinary + - ext-msgpack + suggests@unix: + - ext-session + - ext-igbinary + - ext-msgpack + - zstd + - liblz4 + php-extension: + arg-type: custom diff --git a/config/pkg/ext/ext-simdjson.yml b/config/pkg/ext/ext-simdjson.yml new file mode 100644 index 000000000..37eeb5f1a --- /dev/null +++ b/config/pkg/ext/ext-simdjson.yml @@ -0,0 +1,10 @@ +ext-simdjson: + type: php-extension + artifact: + source: + type: pecl + name: simdjson + metadata: + license-files: [LICENSE] + license: Apache-2.0 + lang: cpp diff --git a/config/pkg/ext/ext-snappy.yml b/config/pkg/ext/ext-snappy.yml new file mode 100644 index 000000000..7ddec2618 --- /dev/null +++ b/config/pkg/ext/ext-snappy.yml @@ -0,0 +1,18 @@ +ext-snappy: + type: php-extension + artifact: + source: + type: git + url: 'https://github.com/kjdev/php-ext-snappy' + rev: master + extract: php-src/ext/snappy + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - snappy + suggests: + - ext-apcu + lang: cpp + php-extension: + arg-type@unix: '--enable-snappy --with-snappy-includedir=@build_root_path@' diff --git a/config/pkg/ext/ext-spx.yml b/config/pkg/ext/ext-spx.yml new file mode 100644 index 000000000..a379cdd4d --- /dev/null +++ b/config/pkg/ext/ext-spx.yml @@ -0,0 +1,14 @@ +ext-spx: + type: php-extension + artifact: + source: + type: pie + repo: noisebynorthwest/php-spx + extract: php-src/ext/spx + metadata: + license-files: [LICENSE] + license: GPL-3.0-or-later + depends: + - ext-zlib + php-extension: + arg-type: '--enable-SPX@shared_suffix@' diff --git a/config/pkg/ext/ext-sqlsrv.yml b/config/pkg/ext/ext-sqlsrv.yml new file mode 100644 index 000000000..603d7a93a --- /dev/null +++ b/config/pkg/ext/ext-sqlsrv.yml @@ -0,0 +1,15 @@ +ext-sqlsrv: + type: php-extension + artifact: + source: + type: pecl + name: sqlsrv + metadata: + license-files: [LICENSE] + license: MIT + depends@linux: + - unixodbc + - ext-pcntl + depends@macos: + - unixodbc + lang: cpp diff --git a/config/pkg/ext/ext-ssh2.yml b/config/pkg/ext/ext-ssh2.yml new file mode 100644 index 000000000..14c9bf327 --- /dev/null +++ b/config/pkg/ext/ext-ssh2.yml @@ -0,0 +1,15 @@ +ext-ssh2: + type: php-extension + artifact: + source: + type: pecl + name: ssh2 + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - libssh2 + - ext-openssl + - ext-zlib + php-extension: + arg-type: with-path diff --git a/config/pkg/ext/ext-swoole.yml b/config/pkg/ext/ext-swoole.yml new file mode 100644 index 000000000..b6499e85e --- /dev/null +++ b/config/pkg/ext/ext-swoole.yml @@ -0,0 +1,72 @@ +ext-swoole: + type: php-extension + artifact: + source: + type: ghtar + repo: swoole/swoole-src + extract: php-src/ext/swoole + match: v6\.+ + prefer-stable: true + metadata: + license-files: [LICENSE] + license: Apache-2.0 + depends: + - libcares + - brotli + - nghttp2 + - zlib + - ext-openssl + - ext-curl + suggests: + - zstd + - ext-sockets + - ext-swoole-hook-pgsql + - ext-swoole-hook-mysql + - ext-swoole-hook-sqlite + - ext-swoole-hook-odbc + suggests@linux: + - zstd + - liburing + - ext-sockets + - ext-swoole-hook-pgsql + - ext-swoole-hook-mysql + - ext-swoole-hook-sqlite + - ext-swoole-hook-odbc + lang: cpp + php-extension: + arg-type: custom +ext-swoole-hook-mysql: + type: php-extension + depends: + - ext-mysqlnd + - ext-pdo + - ext-pdo_mysql + suggests: + - ext-mysqli + php-extension: + arg-type: none + display-name: swoole +ext-swoole-hook-odbc: + type: php-extension + depends: + - ext-pdo + - unixodbc + php-extension: + arg-type: none + display-name: swoole +ext-swoole-hook-pgsql: + type: php-extension + depends: + - ext-pgsql + - ext-pdo + php-extension: + arg-type: none + display-name: swoole +ext-swoole-hook-sqlite: + type: php-extension + depends: + - ext-sqlite3 + - ext-pdo + php-extension: + arg-type: none + display-name: swoole diff --git a/config/pkg/ext/ext-swow.yml b/config/pkg/ext/ext-swow.yml new file mode 100644 index 000000000..11592cd0b --- /dev/null +++ b/config/pkg/ext/ext-swow.yml @@ -0,0 +1,18 @@ +ext-swow: + type: php-extension + artifact: + source: + extract: php-src/ext/swow-src + type: ghtar + repo: swow/swow + prefer-stable: true + metadata: + license: Apache-2.0 + license-files: [LICENSE] + suggests: + - openssl + - curl + - ext-openssl + - ext-curl + php-extension: + arg-type: custom diff --git a/config/pkg/ext/ext-trader.yml b/config/pkg/ext/ext-trader.yml new file mode 100644 index 000000000..8e16afbbe --- /dev/null +++ b/config/pkg/ext/ext-trader.yml @@ -0,0 +1,14 @@ +ext-trader: + type: php-extension + artifact: + source: + type: pecl + name: trader + metadata: + license-files: [LICENSE] + license: BSD-2-Clause + php-extension: + support: + BSD: wip + Windows: wip + arg-type: enable diff --git a/config/pkg/ext/ext-uuid.yml b/config/pkg/ext/ext-uuid.yml new file mode 100644 index 000000000..68080531d --- /dev/null +++ b/config/pkg/ext/ext-uuid.yml @@ -0,0 +1,16 @@ +ext-uuid: + type: php-extension + artifact: + source: + type: pecl + name: uuid + metadata: + license-files: [LICENSE] + license: LGPL-2.1-only + depends: + - libuuid + php-extension: + support: + Windows: wip + BSD: wip + arg-type: with-path diff --git a/config/pkg/ext/ext-uv.yml b/config/pkg/ext/ext-uv.yml new file mode 100644 index 000000000..f1a3031bf --- /dev/null +++ b/config/pkg/ext/ext-uv.yml @@ -0,0 +1,18 @@ +ext-uv: + type: php-extension + artifact: + source: + type: pecl + name: uv + prefer-stable: false + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - libuv + - ext-sockets + php-extension: + support: + Windows: wip + BSD: wip + arg-type: with-path diff --git a/config/pkg/ext/ext-xdebug.yml b/config/pkg/ext/ext-xdebug.yml new file mode 100644 index 000000000..0374e573b --- /dev/null +++ b/config/pkg/ext/ext-xdebug.yml @@ -0,0 +1,14 @@ +ext-xdebug: + type: php-extension + artifact: + source: + type: pie + repo: xdebug/xdebug + metadata: + license-files: [LICENSE] + license: Xdebug-1.03 + php-extension: + zend-extension: true + build-static: false + build-shared: true + build-with-php: false diff --git a/config/pkg/ext/ext-xhprof.yml b/config/pkg/ext/ext-xhprof.yml new file mode 100644 index 000000000..b075f65bd --- /dev/null +++ b/config/pkg/ext/ext-xhprof.yml @@ -0,0 +1,18 @@ +ext-xhprof: + type: php-extension + artifact: + source: + type: pecl + name: xhprof + extract: php-src/ext/xhprof-src + metadata: + license-files: [LICENSE] + license: Apache-2.0 + depends: + - ext-ctype + php-extension: + support: + Windows: wip + BSD: wip + arg-type: enable + build-with-php: true diff --git a/config/pkg/ext/ext-xlswriter.yml b/config/pkg/ext/ext-xlswriter.yml new file mode 100644 index 000000000..24d2fa3ce --- /dev/null +++ b/config/pkg/ext/ext-xlswriter.yml @@ -0,0 +1,18 @@ +ext-xlswriter: + type: php-extension + artifact: + source: + type: pecl + name: xlswriter + metadata: + license-files: [LICENSE] + license: BSD-2-Clause + depends: + - ext-zlib + - ext-zip + suggests: + - openssl + php-extension: + support: + BSD: wip + arg-type: custom diff --git a/config/pkg/ext/ext-xz.yml b/config/pkg/ext/ext-xz.yml new file mode 100644 index 000000000..0d625ad29 --- /dev/null +++ b/config/pkg/ext/ext-xz.yml @@ -0,0 +1,15 @@ +ext-xz: + type: php-extension + artifact: + source: + type: git + url: 'https://github.com/codemasher/php-ext-xz' + rev: main + extract: php-src/ext/xz + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends: + - xz + php-extension: + arg-type: with-path diff --git a/config/pkg/ext/ext-yac.yml b/config/pkg/ext/ext-yac.yml new file mode 100644 index 000000000..e10bea064 --- /dev/null +++ b/config/pkg/ext/ext-yac.yml @@ -0,0 +1,14 @@ +ext-yac: + type: php-extension + artifact: + source: + type: pecl + name: yac + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends@unix: + - fastlz + - ext-igbinary + php-extension: + arg-type@unix: '--enable-yac@shared_suffix@ --enable-igbinary --enable-json --with-system-fastlz' diff --git a/config/pkg/ext/ext-yaml.yml b/config/pkg/ext/ext-yaml.yml new file mode 100644 index 000000000..a60b62547 --- /dev/null +++ b/config/pkg/ext/ext-yaml.yml @@ -0,0 +1,16 @@ +ext-yaml: + type: php-extension + artifact: + source: + type: git + url: 'https://github.com/php/pecl-file_formats-yaml' + rev: php7 + extract: php-src/ext/yaml + metadata: + license-files: [LICENSE] + license: MIT + depends: + - libyaml + php-extension: + arg-type@unix: with-path + arg-type@windows: with diff --git a/config/pkg/ext/ext-zip.yml b/config/pkg/ext/ext-zip.yml new file mode 100644 index 000000000..a5a9e4b54 --- /dev/null +++ b/config/pkg/ext/ext-zip.yml @@ -0,0 +1,17 @@ +ext-zip: + type: php-extension + artifact: + source: + type: pecl + name: zip + extract: ext-zip + metadata: + license-files: [LICENSE] + license: PHP-3.01 + depends@unix: + - libzip + php-extension: + support: + BSD: wip + arg-type: custom + arg-type@windows: enable diff --git a/config/pkg/ext/ext-zstd.yml b/config/pkg/ext/ext-zstd.yml new file mode 100644 index 000000000..1f004f131 --- /dev/null +++ b/config/pkg/ext/ext-zstd.yml @@ -0,0 +1,15 @@ +ext-zstd: + type: php-extension + artifact: + source: + type: git + url: 'https://github.com/kjdev/php-ext-zstd' + rev: master + extract: php-src/ext/zstd + metadata: + license-files: [LICENSE] + license: MIT + depends: + - zstd + php-extension: + arg-type: '--enable-zstd --with-libzstd=@build_root_path@' diff --git a/config/pkg/lib/ncurses.yml b/config/pkg/lib/ncurses.yml index cbc1ba676..6ce20f917 100644 --- a/config/pkg/lib/ncurses.yml +++ b/config/pkg/lib/ncurses.yml @@ -1,12 +1,10 @@ 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] + artifact: ncurses static-libs@unix: - libncurses.a +ncursesw: + type: library + artifact: ncurses + static-libs@unix: + - libncursesw.a diff --git a/config/pkg/lib/curl.yml b/config/pkg/target/curl.yml similarity index 91% rename from config/pkg/lib/curl.yml rename to config/pkg/target/curl.yml index f183b21e0..4daba8c14 100644 --- a/config/pkg/lib/curl.yml +++ b/config/pkg/target/curl.yml @@ -1,5 +1,5 @@ curl: - type: library + type: target artifact: source: type: ghrel @@ -29,5 +29,7 @@ curl: - SystemConfiguration headers: - curl + static-bins@unix: + - curl static-libs@unix: - libcurl.a diff --git a/config/pkg/target/go-xcaddy.yml b/config/pkg/target/go-xcaddy.yml index 89cb4cd02..deafb37d6 100644 --- a/config/pkg/target/go-xcaddy.yml +++ b/config/pkg/target/go-xcaddy.yml @@ -2,5 +2,11 @@ go-xcaddy: type: target artifact: binary: custom + env: + GOROOT: '{pkg_root_path}/go-xcaddy' + GOBIN: '{pkg_root_path}/go-xcaddy/bin' + GOPATH: '{pkg_root_path}/go-xcaddy/go' + path@unix: + - '{pkg_root_path}/go-xcaddy/bin' static-bins: - xcaddy diff --git a/config/pkg/target/htop.yml b/config/pkg/target/htop.yml new file mode 100644 index 000000000..fcefa70aa --- /dev/null +++ b/config/pkg/target/htop.yml @@ -0,0 +1,10 @@ +htop: + type: target + artifact: + source: + type: ghrel + repo: htop-dev/htop + match: htop.+\.tar\.xz + prefer-stable: true + depends: + - ncursesw diff --git a/config/pkg/target/protoc.yml b/config/pkg/target/protoc.yml new file mode 100644 index 000000000..b45fb335a --- /dev/null +++ b/config/pkg/target/protoc.yml @@ -0,0 +1,8 @@ +protoc: + type: target + artifact: + binary: + linux-x86_64: { type: ghrel, repo: protocolbuffers/protobuf, match: 'protoc-([0-9.]+)-linux-x86_64\.zip', extract: '{pkg_root_path}/protoc' } + linux-aarch64: { type: ghrel, repo: protocolbuffers/protobuf, match: 'protoc-([0-9.]+)-linux-aarch_64\.zip', extract: '{pkg_root_path}/protoc' } + path: + - '{pkg_root_path}/protoc/bin' diff --git a/config/pkg/target/rust.yml b/config/pkg/target/rust.yml new file mode 100644 index 000000000..f2e107380 --- /dev/null +++ b/config/pkg/target/rust.yml @@ -0,0 +1,6 @@ +rust: + type: target + artifact: + binary: custom + path: + - '{pkg_root_path}/rust/bin' diff --git a/config/source.json b/config/source.json index 114118bb4..040197cb1 100644 --- a/config/source.json +++ b/config/source.json @@ -361,7 +361,7 @@ }, "gmp": { "type": "filelist", - "url": "https://gmplib.org/download/gmp/", + "url": "https://ftp.gnu.org/gnu/gmp/", "regex": "/href=\"(?gmp-(?[^\"]+)\\.tar\\.xz)\"/", "provide-pre-built": true, "alt": { diff --git a/src/Package/Artifact/php_src.php b/src/Package/Artifact/php_src.php index ae9488d69..119f9056e 100644 --- a/src/Package/Artifact/php_src.php +++ b/src/Package/Artifact/php_src.php @@ -7,7 +7,6 @@ use Package\Target\php; use StaticPHP\Attribute\Artifact\AfterSourceExtract; use StaticPHP\Attribute\PatchDescription; -use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\FileSystem; use StaticPHP\Util\SourcePatcher; @@ -52,16 +51,6 @@ public function patchGDWin32(): void } } - #[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', '>')); - $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'); - } - #[AfterSourceExtract('php-src')] #[PatchDescription('Add LICENSE file to IMAP extension if missing')] public function patchImapLicense(): void diff --git a/src/Package/Artifact/rust.php b/src/Package/Artifact/rust.php new file mode 100644 index 000000000..e5c9f5259 --- /dev/null +++ b/src/Package/Artifact/rust.php @@ -0,0 +1,85 @@ +executeCurl('https://static.rust-lang.org/dist/channel-rust-stable.toml', retries: $downloader->getRetry()); + // parse toml by regex since we want to avoid adding a toml parser dependency just for this + $cnt = preg_match_all('/^version = "([^"]+)"$/m', $toml_config ?: '', $matches); + if (!$cnt) { + throw new DownloaderException('Failed to parse Rust version from channel config'); + } + $versions = $matches[1]; + // strip version num \d.\d.\d (some version number is like "x.x.x (abcdefg 1970-01-01)" + $versions = array_filter(array_map(fn ($v) => preg_match('/^(\d+\.\d+\.\d+)/', $v, $m) ? $m[1] : null, $versions)); + usort($versions, 'version_compare'); + $latest_version = end($versions); + if (!$latest_version) { + throw new DownloaderException('Could not determine latest Rust version'); + } + + // merge download link + $download_url = "https://static.rust-lang.org/dist/rust-{$latest_version}-{$arch}-unknown-linux-{$distro}.tar.xz"; + $path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . basename($download_url); + default_shell()->executeCurlDownload($download_url, $path, retries: $downloader->getRetry()); + return DownloadResult::archive(basename($path), ['url' => $download_url, 'version' => $latest_version], extract: PKG_ROOT_PATH . '/rust-install', verified: false, version: $latest_version); + } + + #[CustomBinaryCheckUpdate('rust', [ + 'linux-x86_64', + 'linux-aarch64', + ])] + public function checkUpdateBinary(?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult + { + $toml_config = default_shell()->executeCurl('https://static.rust-lang.org/dist/channel-rust-stable.toml', retries: $downloader->getRetry()); + $cnt = preg_match_all('/^version = "([^"]+)"$/m', $toml_config ?: '', $matches); + if (!$cnt) { + throw new DownloaderException('Failed to parse Rust version from channel config'); + } + $versions = array_filter(array_map(fn ($v) => preg_match('/^(\d+\.\d+\.\d+)/', $v, $m) ? $m[1] : null, $matches[1])); + usort($versions, 'version_compare'); + $latest_version = end($versions); + if (!$latest_version) { + throw new DownloaderException('Could not determine latest Rust version'); + } + return new CheckUpdateResult( + old: $old_version, + new: $latest_version, + needUpdate: $old_version === null || $latest_version !== $old_version, + ); + } + + #[AfterBinaryExtract('rust', [ + 'linux-x86_64', + 'linux-aarch64', + ])] + public function postExtractRust(string $target_path): void + { + $prefix = PKG_ROOT_PATH . '/rust'; + shell()->exec("cd {$target_path} && ./install.sh --prefix={$prefix}"); + } +} diff --git a/src/Package/Artifact/zig.php b/src/Package/Artifact/zig.php index b42eee3ae..95520aa4b 100644 --- a/src/Package/Artifact/zig.php +++ b/src/Package/Artifact/zig.php @@ -26,6 +26,9 @@ public function downBinary(ArtifactDownloader $downloader): DownloadResult $index_json = default_shell()->executeCurl('https://ziglang.org/download/index.json', retries: $downloader->getRetry()); $index_json = json_decode($index_json ?: '', true); $latest_version = null; + if ($index_json === null) { + throw new DownloaderException('Failed to fetch Zig version index'); + } foreach ($index_json as $version => $data) { if ($version !== 'master') { $latest_version = $version; diff --git a/src/Package/Extension/dba.php b/src/Package/Extension/dba.php new file mode 100644 index 000000000..d16d979d1 --- /dev/null +++ b/src/Package/Extension/dba.php @@ -0,0 +1,28 @@ +getLibraryPackage('qdbm')) ? (" --with-qdbm={$qdbm->getBuildRootPath()}") : ''; + return '--enable-dba' . ($shared ? '=shared' : '') . $qdbm; + } + + #[CustomPhpConfigureArg('Windows')] + public function getWindowsConfigureArg(PackageInstaller $installer): string + { + $qdbm = $installer->getLibraryPackage('qdbm') ? ' --with-qdbm' : ''; + return '--with-dba' . $qdbm; + } +} diff --git a/src/Package/Extension/dio.php b/src/Package/Extension/dio.php new file mode 100644 index 000000000..70ac387e3 --- /dev/null +++ b/src/Package/Extension/dio.php @@ -0,0 +1,23 @@ +getSourceDir()}/php_dio.h")) { + FileSystem::writeFile("{$this->getSourceDir()}/php_dio.h", FileSystem::readFile("{$this->getSourceDir()}/src/php_dio.h")); + } + } +} diff --git a/src/Package/Extension/event.php b/src/Package/Extension/event.php new file mode 100644 index 000000000..db1192745 --- /dev/null +++ b/src/Package/Extension/event.php @@ -0,0 +1,46 @@ +getBuilder()->getBuildRootPath()}"; + if ($installer->getLibraryPackage('openssl')) { + $arg .= " --with-event-openssl={$this->getBuilder()->getBuildRootPath()}"; + } + if ($installer->getPhpExtensionPackage('ext-sockets')) { + $arg .= ' --enable-event-sockets'; + } else { + $arg .= ' --disable-event-sockets'; + } + return $arg; + } + + #[BeforeStage('php', [php::class, 'makeForUnix'], 'ext-event')] + #[PatchDescription('Prevent event extension compile error on macOS')] + public function patchBeforeMake(PackageInstaller $installer): void + { + // Prevent event extension compile error on macOS + if (SystemTarget::getTargetOS() === 'Darwin') { + $php_src = $installer->getTargetPackage('php')->getSourceDir(); + FileSystem::replaceFileRegex("{$php_src}/main/php_config.h", '/^#define HAVE_OPENPTY 1$/m', ''); + } + } +} diff --git a/src/Package/Extension/excimer.php b/src/Package/Extension/excimer.php new file mode 100644 index 000000000..9780a2ecd --- /dev/null +++ b/src/Package/Extension/excimer.php @@ -0,0 +1,19 @@ +')); + $ver_id = php::getPHPVersionID(return_null_if_failed: true); + spc_skip_if($ver_id === null || $ver_id < 80316); + spc_skip_if(LinuxUtil::getOSRelease()['dist'] !== 'centos'); + SourcePatcher::patchFile('ffi_centos7_fix_O3_strncmp.patch', SOURCE_PATH . '/php-src'); + } +} diff --git a/src/Package/Extension/gd.php b/src/Package/Extension/gd.php new file mode 100644 index 000000000..5e815b5da --- /dev/null +++ b/src/Package/Extension/gd.php @@ -0,0 +1,26 @@ +getLibraryPackage('freetype') ? ' --with-freetype' : ''; + $arg .= $installer->getLibraryPackage('libjpeg') ? ' --with-jpeg' : ''; + $arg .= $installer->getLibraryPackage('libwebp') ? ' --with-webp' : ''; + $arg .= $installer->getLibraryPackage('libavif') ? ' --with-avif' : ''; + return $arg; + } +} diff --git a/src/Package/Extension/glfw.php b/src/Package/Extension/glfw.php index 61d722e14..2a9c7ee51 100644 --- a/src/Package/Extension/glfw.php +++ b/src/Package/Extension/glfw.php @@ -6,7 +6,6 @@ use Package\Target\php; use StaticPHP\Attribute\Package\BeforeStage; -use StaticPHP\Attribute\Package\CustomPhpConfigureArg; use StaticPHP\Attribute\Package\Extension; use StaticPHP\Attribute\PatchDescription; use StaticPHP\Package\PhpExtensionPackage; @@ -49,11 +48,4 @@ public function patchBeforeConfigure(): void 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/Extension/grpc.php b/src/Package/Extension/grpc.php new file mode 100644 index 000000000..c3b08f161 --- /dev/null +++ b/src/Package/Extension/grpc.php @@ -0,0 +1,70 @@ +getSourceDir()}/src/php/ext/grpc/call.c", + 'zend_exception_get_default(TSRMLS_C),', + 'zend_ce_exception,', + ); + + // custom config.m4 content for grpc extension, to prevent building libgrpc.a again + $config_m4 = <<<'M4' +PHP_ARG_ENABLE(grpc, [whether to enable grpc support], [AS_HELP_STRING([--enable-grpc], [Enable grpc support])]) + +if test "$PHP_GRPC" != "no"; then + PHP_ADD_INCLUDE(PHP_EXT_SRCDIR()/include) + PHP_ADD_INCLUDE(PHP_EXT_SRCDIR()/src/php/ext/grpc) + GRPC_LIBDIR=@@build_lib_path@@ + PHP_ADD_LIBPATH($GRPC_LIBDIR) + PHP_ADD_LIBRARY(grpc,,GRPC_SHARED_LIBADD) + LIBS="-lpthread $LIBS" + PHP_ADD_LIBRARY(pthread) + + case $host in + *darwin*) + PHP_ADD_LIBRARY(c++,1,GRPC_SHARED_LIBADD) + ;; + *) + PHP_ADD_LIBRARY(stdc++,1,GRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY(rt,,GRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY(rt) + ;; + esac + + PHP_NEW_EXTENSION(grpc, @grpc_c_files@, $ext_shared, , -DGRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK=1) + PHP_SUBST(GRPC_SHARED_LIBADD) + PHP_INSTALL_HEADERS([ext/grpc], [php_grpc.h]) +fi +M4; + $replace = get_pack_replace(); + // load grpc c files from src/php/ext/grpc + $c_files = glob("{$this->getSourceDir()}/src/php/ext/grpc/*.c"); + $replace['@grpc_c_files@'] = implode(" \\\n ", array_map(fn ($f) => 'src/php/ext/grpc/' . basename($f), $c_files)); + $config_m4 = str_replace(array_keys($replace), array_values($replace), $config_m4); + file_put_contents("{$this->getSourceDir()}/config.m4", $config_m4); + + copy("{$this->getSourceDir()}/src/php/ext/grpc/php_grpc.h", "{$this->getSourceDir()}/php_grpc.h"); + } +} diff --git a/src/Package/Extension/imagick.php b/src/Package/Extension/imagick.php new file mode 100644 index 000000000..2d2aa0aa1 --- /dev/null +++ b/src/Package/Extension/imagick.php @@ -0,0 +1,21 @@ +getBuildRootPath() . $disable_omp; + } +} diff --git a/src/Package/Extension/imap.php b/src/Package/Extension/imap.php new file mode 100644 index 000000000..e9879b48b --- /dev/null +++ b/src/Package/Extension/imap.php @@ -0,0 +1,55 @@ +getOption('enable-zts')) { + throw new WrongUsageException('ext-imap is not thread safe, do not build it with ZTS builds'); + } + } + + #[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-imap')] + public function patchBeforeBuildconf(PackageInstaller $installer): void + { + if ($installer->getLibraryPackage('openssl')) { + // sometimes imap with openssl does not contain zlib (required by openssl) + // we need to add it manually + FileSystem::replaceFileStr("{$this->getSourceDir()}/config.m4", 'TST_LIBS="$DLIBS $IMAP_SHARED_LIBADD"', 'TST_LIBS="$DLIBS $IMAP_SHARED_LIBADD -lz"'); + } + // c-client is built with PASSWDTYPE=nul so libcrypt is not referenced. + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/config.m4", + " PHP_CHECK_LIBRARY(crypt, crypt,\n [\n PHP_ADD_LIBRARY(crypt,, IMAP_SHARED_LIBADD)\n AC_DEFINE(HAVE_LIBCRYPT,1,[ ])\n ])", + ' dnl Skipped: crypt check not needed (c-client built with PASSWDTYPE=nul)' + ); + } + + #[CustomPhpConfigureArg('Darwin')] + #[CustomPhpConfigureArg('Linux')] + public function getUnixConfigureArg(PackageInstaller $installer, PackageBuilder $builder): string + { + $arg = "--with-imap={$builder->getBuildRootPath()}"; + if (($ssl = $installer->getLibraryPackage('openssl')) !== null) { + $arg .= " --with-imap-ssl={$ssl->getBuildRootPath()}"; + } + return $arg; + } +} diff --git a/src/Package/Extension/maxminddb.php b/src/Package/Extension/maxminddb.php new file mode 100644 index 000000000..bda8d34c7 --- /dev/null +++ b/src/Package/Extension/maxminddb.php @@ -0,0 +1,30 @@ +getSourceDir()}/config.m4")) { + return; + } + // move ext/maxminddb/ext/* to ext/maxminddb/ + $files = FileSystem::scanDirFiles("{$this->getSourceDir()}/ext", false, true); + foreach ($files as $file) { + rename("{$this->getSourceDir()}/ext/{$file}", "{$this->getSourceDir()}/{$file}"); + } + } +} diff --git a/src/Package/Extension/memcache.php b/src/Package/Extension/memcache.php new file mode 100644 index 000000000..a9c58b768 --- /dev/null +++ b/src/Package/Extension/memcache.php @@ -0,0 +1,75 @@ +isBuildStatic()) { + return false; + } + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/config9.m4", + 'if test -d $abs_srcdir/src ; then', + 'if test -d $abs_srcdir/main ; then' + ); + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/config9.m4", + 'export CPPFLAGS="$CPPFLAGS $INCLUDES"', + 'export CPPFLAGS="$CPPFLAGS $INCLUDES -I$abs_srcdir/main"' + ); + // add for in-tree building + file_put_contents( + "{$this->getSourceDir()}/php_memcache.h", + <<<'EOF' +#ifndef PHP_MEMCACHE_H +#define PHP_MEMCACHE_H + +extern zend_module_entry memcache_module_entry; +#define phpext_memcache_ptr &memcache_module_entry + +#endif +EOF + ); + return true; + } + + #[BeforeStage('ext-memcache', [self::class, 'configureForUnix'])] + #[PatchDescription('Fix memcache extension compile error when building as shared')] + public function patchBeforeSharedConfigure(): bool + { + if (!$this->isBuildShared()) { + return false; + } + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/config9.m4", + 'if test -d $abs_srcdir/main ; then', + 'if test -d $abs_srcdir/src ; then', + ); + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/config9.m4", + 'export CPPFLAGS="$CPPFLAGS $INCLUDES -I$abs_srcdir/main"', + 'export CPPFLAGS="$CPPFLAGS $INCLUDES"', + ); + return true; + } + + public function getSharedExtensionEnv(): array + { + $parent = parent::getSharedExtensionEnv(); + $parent['CFLAGS'] .= ' -std=c17'; + return $parent; + } +} diff --git a/src/Package/Extension/memcached.php b/src/Package/Extension/memcached.php new file mode 100644 index 000000000..0453e8ecf --- /dev/null +++ b/src/Package/Extension/memcached.php @@ -0,0 +1,30 @@ +getLibraryPackage('zlib')->getBuildRootPath() . ' ' . + '--with-libmemcached-dir=' . $installer->getLibraryPackage('libmemcached')->getBuildRootPath() . ' ' . + '--disable-memcached-sasl ' . + '--enable-memcached-json ' . + ($installer->getLibraryPackage('zstd') ? '--with-zstd ' : '') . + ($installer->getPhpExtensionPackage('ext-igbinary') ? '--enable-memcached-igbinary ' : '') . + ($installer->getPhpExtensionPackage('ext-session') ? '--enable-memcached-session ' : '') . + ($installer->getPhpExtensionPackage('ext-msgpack') ? '--enable-memcached-msgpack ' : '') . + '--with-system-fastlz'; + } +} diff --git a/src/Package/Extension/mongodb.php b/src/Package/Extension/mongodb.php new file mode 100644 index 000000000..3434491d6 --- /dev/null +++ b/src/Package/Extension/mongodb.php @@ -0,0 +1,38 @@ +getLibraryPackage('openssl')) { + $arg .= '--with-mongodb-ssl=openssl'; + } + $arg .= $installer->getLibraryPackage('icu') ? ' --with-mongodb-icu=yes ' : ' --with-mongodb-icu=no '; + $arg .= $installer->getLibraryPackage('zstd') ? ' --with-mongodb-zstd=yes ' : ' --with-mongodb-zstd=no '; + // $arg .= $installer->getLibraryPackage('snappy') ? ' --with-mongodb-snappy=yes ' : ' --with-mongodb-snappy=no '; + $arg .= $installer->getLibraryPackage('zlib') ? ' --with-mongodb-zlib=yes ' : ' --with-mongodb-zlib=bundled '; + return clean_spaces($arg); + } + + public function getSharedExtensionEnv(): array + { + $parent = parent::getSharedExtensionEnv(); + $parent['CFLAGS'] .= ' -std=c17'; + return $parent; + } +} diff --git a/src/Package/Extension/opcache.php b/src/Package/Extension/opcache.php new file mode 100644 index 000000000..93cb0a9ff --- /dev/null +++ b/src/Package/Extension/opcache.php @@ -0,0 +1,76 @@ += 8.0 !'); + } + } + + #[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-opcache')] + #[PatchDescription('Fix static opcache build for PHP 8.2.0 to 8.4.x')] + public function patchBeforeBuildconf(PackageInstaller $installer): bool + { + $version = php::getPHPVersion(); + $php_src = $installer->getTargetPackage('php')->getSourceDir(); + if (file_exists("{$php_src}/.opcache_patched")) { + return false; + } + // if 8.2.0 <= PHP_VERSION < 8.2.23, we need to patch from legacy patch file + if (version_compare($version, '8.2.0', '>=') && version_compare($version, '8.2.23', '<')) { + SourcePatcher::patchFile('spc_fix_static_opcache_before_80222.patch', $php_src); + } + // if 8.3.0 <= PHP_VERSION < 8.3.11, we need to patch from legacy patch file + elseif (version_compare($version, '8.3.0', '>=') && version_compare($version, '8.3.11', '<')) { + SourcePatcher::patchFile('spc_fix_static_opcache_before_80310.patch', $php_src); + } + // if 8.3.12 <= PHP_VERSION < 8.5.0-dev, we need to patch from legacy patch file + elseif (version_compare($version, '8.5.0-dev', '<')) { + SourcePatcher::patchPhpSrc(items: ['static_opcache']); + } + // PHP 8.5.0-dev and later supports static opcache without patching + else { + return false; + } + return file_put_contents($php_src . '/.opcache_patched', '1') !== false; + } + + #[CustomPhpConfigureArg('Darwin')] + #[CustomPhpConfigureArg('Linux')] + public function getUnixConfigureArg(bool $shared, PackageBuilder $builder): string + { + $phpVersionID = php::getPHPVersionID(); + $opcache_jit = ' --enable-opcache-jit'; + if ((SystemTarget::getTargetOS() === 'Linux' && + SystemTarget::getLibc() === 'musl' && + $builder->getOption('enable-zts') && + SystemTarget::getTargetArch() === 'x86_64' && + $phpVersionID < 80500) || + $builder->getOption('disable-opcache-jit') + ) { + $opcache_jit = ' --disable-opcache-jit'; + } + return '--enable-opcache' . ($shared ? '=shared' : '') . $opcache_jit; + } +} diff --git a/src/Package/Extension/opentelemetry.php b/src/Package/Extension/opentelemetry.php new file mode 100644 index 000000000..632d12571 --- /dev/null +++ b/src/Package/Extension/opentelemetry.php @@ -0,0 +1,21 @@ +getOption('enable-zts')) { + throw new WrongUsageException('ext-parallel must be built with ZTS builds. Use "--enable-zts" option!'); + } + } + + #[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-parallel')] + #[PatchDescription('Fix parallel m4 hardcoded PHP_VERSION check')] + public function patchBeforeBuildconf(): bool + { + FileSystem::replaceFileRegex("{$this->getSourceDir()}/config.m4", '/PHP_VERSION=.*/m', ''); + return true; + } +} diff --git a/src/Package/Extension/password_argon2.php b/src/Package/Extension/password_argon2.php new file mode 100644 index 000000000..77122405d --- /dev/null +++ b/src/Package/Extension/password_argon2.php @@ -0,0 +1,37 @@ +execWithResult(BUILD_ROOT_PATH . '/bin/php -n -r "assert(defined(\'PASSWORD_ARGON2I\'));"'); + if ($ret !== 0) { + throw new ValidationException('extension ' . $this->getName() . ' failed sanity check', validation_module: 'password_argon2 function check'); + } + } + + #[CustomPhpConfigureArg('Linux')] + #[CustomPhpConfigureArg('Darwin')] + public function getConfigureArg(PackageInstaller $installer, PackageBuilder $builder): string + { + if ($installer->getLibraryPackage('openssl') !== null) { + if (php::getPHPVersionID() >= 80500 || (php::getPHPVersionID() >= 80400 && !$builder->getOption('enable-zts'))) { + return '--without-password-argon2'; // use --with-openssl-argon2 in openssl extension instead + } + } + return '--with-password-argon2'; + } +} diff --git a/src/Package/Extension/pdo_odbc.php b/src/Package/Extension/pdo_odbc.php new file mode 100644 index 000000000..f8835d134 --- /dev/null +++ b/src/Package/Extension/pdo_odbc.php @@ -0,0 +1,35 @@ +getSourceDir()}/config.m4", 'PDO_ODBC_LDFLAGS="$pdo_odbc_def_ldflags', 'PDO_ODBC_LDFLAGS="-liconv $pdo_odbc_def_ldflags'); + } + + #[CustomPhpConfigureArg('Linux')] + #[CustomPhpConfigureArg('Darwin')] + public function getUnixConfigureArg(bool $shared): string + { + return '--with-pdo-odbc=' . ($shared ? 'shared,' : '') . 'unixODBC,' . BUILD_ROOT_PATH; + } + + #[CustomPhpConfigureArg('Windows')] + public function getWindowsConfigureArg(bool $shared): string + { + return '--with-pdo-odbc'; + } +} diff --git a/src/Package/Extension/pdo_sqlite.php b/src/Package/Extension/pdo_sqlite.php new file mode 100644 index 000000000..b0429f628 --- /dev/null +++ b/src/Package/Extension/pdo_sqlite.php @@ -0,0 +1,25 @@ +getTargetPackage('php')->getSourceDir()}/configure", + '/sqlite3_column_table_name=yes/', + 'sqlite3_column_table_name=no' + ); + } +} diff --git a/src/Package/Extension/pgsql.php b/src/Package/Extension/pgsql.php new file mode 100644 index 000000000..6e2b8f0b5 --- /dev/null +++ b/src/Package/Extension/pgsql.php @@ -0,0 +1,48 @@ += 80400) { + $libfiles = new SPCConfigUtil(['libs_only_deps' => true, 'absolute_libs' => true])->getPackageDepsConfig('postgresql', array_keys($installer->getResolvedPackages()), $builder->getOption('with-suggests'))['libs']; + $libfiles = str_replace("{$builder->getLibDir()}/lib", '-l', $libfiles); + $libfiles = str_replace('.a', '', $libfiles); + return '--with-pgsql' . ($shared ? '=shared' : '') . + ' PGSQL_CFLAGS=-I' . $builder->getIncludeDir() . + ' PGSQL_LIBS="-L' . $builder->getLibDir() . ' ' . $libfiles . '"'; + } + return '--with-pgsql=' . ($shared ? 'shared,' : '') . $builder->getBuildRootPath(); + } + + #[CustomPhpConfigureArg('Windows')] + public function getWindowsConfigureArg(bool $shared, PackageBuilder $builder): string + { + if (php::getPHPVersionID() >= 80400) { + return '--with-pgsql'; + } + return "--with-pgsql={$builder->getBuildRootPath()}"; + } + + public function getSharedExtensionEnv(): array + { + $parent = parent::getSharedExtensionEnv(); + $parent['CFLAGS'] .= ' -std=c17 -Wno-int-conversion'; + return $parent; + } +} diff --git a/src/Package/Extension/phar.php b/src/Package/Extension/phar.php index dd9b1dff9..2220f7278 100644 --- a/src/Package/Extension/phar.php +++ b/src/Package/Extension/phar.php @@ -9,6 +9,8 @@ use StaticPHP\Attribute\Package\BeforeStage; use StaticPHP\Attribute\Package\Extension; use StaticPHP\Attribute\PatchDescription; +use StaticPHP\Package\PhpExtensionPackage; +use StaticPHP\Util\FileSystem; use StaticPHP\Util\SourcePatcher; #[Extension('phar')] @@ -26,4 +28,24 @@ public function afterMicroUnixBuild(): void { SourcePatcher::unpatchMicroPhar(); } + + #[BeforeStage('ext-phar', 'build')] + public function beforeBuildShared(PhpExtensionPackage $pkg): void + { + FileSystem::replaceFileStr( + "{$pkg->getSourceDir()}/config.m4", + ['$ext_dir/phar.1', '$ext_dir/phar.phar.1'], + ['${ext_dir}phar.1', '${ext_dir}phar.phar.1'] + ); + } + + #[AfterStage('ext-phar', 'build')] + public function afterBuildShared(PhpExtensionPackage $pkg): void + { + FileSystem::replaceFileStr( + "{$pkg->getSourceDir()}/config.m4", + ['${ext_dir}phar.1', '${ext_dir}phar.phar.1'], + ['$ext_dir/phar.1', '$ext_dir/phar.phar.1'] + ); + } } diff --git a/src/Package/Extension/protobuf.php b/src/Package/Extension/protobuf.php new file mode 100644 index 000000000..2c3dd036a --- /dev/null +++ b/src/Package/Extension/protobuf.php @@ -0,0 +1,28 @@ +getPhpExtensionPackage('ext-grpc'); + // protobuf conflicts with grpc + if ($grpc?->isBuildStatic()) { + throw new ValidationException('protobuf conflicts with grpc, please remove grpc or protobuf extension'); + } + } +} diff --git a/src/Package/Extension/rar.php b/src/Package/Extension/rar.php new file mode 100644 index 000000000..2fc20ed14 --- /dev/null +++ b/src/Package/Extension/rar.php @@ -0,0 +1,27 @@ += 15.0)')] + public function patchBeforeBuildconf(): void + { + // workaround for newer Xcode clang (>= 15.0) + if (SystemTarget::getTargetOS() === 'Darwin') { + FileSystem::replaceFileStr("{$this->getSourceDir()}/config.m4", '-Wall -fvisibility=hidden', '-Wall -Wno-incompatible-function-pointer-types -fvisibility=hidden'); + } + } +} diff --git a/src/Package/Extension/rdkafka.php b/src/Package/Extension/rdkafka.php new file mode 100644 index 000000000..4bb28ee57 --- /dev/null +++ b/src/Package/Extension/rdkafka.php @@ -0,0 +1,55 @@ +getSourceDir()}/config.m4", "-L\$RDKAFKA_DIR/\$PHP_LIBDIR -lm\n", "-L\$RDKAFKA_DIR/\$PHP_LIBDIR -lm \$RDKAFKA_LIBS\n"); + FileSystem::replaceFileStr("{$this->getSourceDir()}/config.m4", "-L\$RDKAFKA_DIR/\$PHP_LIBDIR -lm\"\n", '-L$RDKAFKA_DIR/$PHP_LIBDIR -lm $RDKAFKA_LIBS"'); + FileSystem::replaceFileStr("{$this->getSourceDir()}/config.m4", 'PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,', 'AC_CHECK_LIB([$LIBNAME], [$LIBSYMBOL],'); + return true; + } + + #[BeforeStage('php', [php::class, 'makeForUnix'], 'ext-rdkafka')] + #[PatchDescription('Patch rdkafka extension source code to fix build errors with inline builds')] + public function patchBeforeMake(): bool + { + // when compiling rdkafka with inline builds, it shows some errors, I don't know why. + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/rdkafka.c", + "#ifdef HAS_RD_KAFKA_TRANSACTIONS\n#include \"kafka_error_exception.h\"\n#endif", + '#include "kafka_error_exception.h"' + ); + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/kafka_error_exception.h", + ['#ifdef HAS_RD_KAFKA_TRANSACTIONS', '#endif'], + '' + ); + return true; + } + + #[CustomPhpConfigureArg('Darwin')] + #[CustomPhpConfigureArg('Linux')] + public function getUnixConfigureArg(bool $shared, PackageBuilder $builder): string + { + $pkgconf_libs = new SPCConfigUtil(['no_php' => true, 'libs_only_deps' => true])->getExtensionConfig($this); + return '--with-rdkafka=' . ($shared ? 'shared,' : '') . $builder->getBuildRootPath() . " RDKAFKA_LIBS=\"{$pkgconf_libs['libs']}\""; + } +} diff --git a/src/Package/Extension/redis.php b/src/Package/Extension/redis.php new file mode 100644 index 000000000..bfc5cc5e7 --- /dev/null +++ b/src/Package/Extension/redis.php @@ -0,0 +1,47 @@ +isBuildStatic()) { + $arg .= $installer->getPhpExtensionPackage('session')?->isBuildStatic() ? ' --enable-redis-session' : ' --disable-redis-session'; + $arg .= $installer->getPhpExtensionPackage('igbinary')?->isBuildStatic() ? ' --enable-redis-igbinary' : ' --disable-redis-igbinary'; + $arg .= $installer->getPhpExtensionPackage('msgpack')?->isBuildStatic() ? ' --enable-redis-msgpack' : ' --disable-redis-msgpack'; + } else { + $arg .= $installer->getPhpExtensionPackage('session') ? ' --enable-redis-session' : ' --disable-redis-session'; + $arg .= $installer->getPhpExtensionPackage('igbinary') ? ' --enable-redis-igbinary' : ' --disable-redis-igbinary'; + $arg .= $installer->getPhpExtensionPackage('msgpack') ? ' --enable-redis-msgpack' : ' --disable-redis-msgpack'; + } + if ($zstd = $installer->getLibraryPackage('zstd')) { + $arg .= ' --enable-redis-zstd --with-libzstd="' . $zstd->getBuildRootPath() . '"'; + } + if ($liblz4 = $installer->getLibraryPackage('liblz4')) { + $arg .= ' --enable-redis-lz4 --with-liblz4="' . $liblz4->getBuildRootPath() . '"'; + } + return $arg; + } + + #[CustomPhpConfigureArg('Windows')] + public function getWindowsConfigureArg(bool $shared, PackageInstaller $installer): string + { + $arg = '--enable-redis'; + $arg .= $installer->getPhpExtensionPackage('session') ? ' --enable-redis-session' : ' --disable-redis-session'; + $arg .= $installer->getPhpExtensionPackage('igbinary') ? ' --enable-redis-igbinary' : ' --disable-redis-igbinary'; + return $arg; + } +} diff --git a/src/Package/Extension/simdjson.php b/src/Package/Extension/simdjson.php new file mode 100644 index 000000000..e04c415ac --- /dev/null +++ b/src/Package/Extension/simdjson.php @@ -0,0 +1,70 @@ +getTargetPackage('php'); + $php_ver = php::getPHPVersionID(); + FileSystem::replaceFileRegex( + "{$this->getSourceDir()}/config.m4", + '/php_version=(`.*`)$/m', + "php_version={$php_ver}" + ); + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/config.m4", + 'if test -z "$PHP_CONFIG"; then', + 'if false; then' + ); + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/config.w32", + "'yes',", + 'PHP_SIMDJSON_SHARED,' + ); + return true; + } + + public function getSharedExtensionEnv(): array + { + $env = parent::getSharedExtensionEnv(); + if (ApplicationContext::get(ToolchainInterface::class) instanceof ZigToolchain) { + $extra = getenv('SPC_COMPILER_EXTRA'); + if (!str_contains((string) $extra, '-lstdc++')) { + f_putenv('SPC_COMPILER_EXTRA=' . clean_spaces($extra . ' -lstdc++')); + } + $env['CFLAGS'] .= ' -Xclang -target-feature -Xclang +evex512'; + $env['CXXFLAGS'] .= ' -Xclang -target-feature -Xclang +evex512'; + } + return $env; + } + + #[BeforeStage('php', [php::class, 'makeForUnix'], 'ext-simdjson')] + public function patchBeforeMake(): void + { + if (!ApplicationContext::get(ToolchainInterface::class) instanceof ZigToolchain) { + return; + } + $extra_cflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') ?: ''; + GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . trim($extra_cflags . ' -Xclang -target-feature -Xclang +evex512')); + $extra_cxxflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS') ?: ''; + GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS=' . trim($extra_cxxflags . ' -Xclang -target-feature -Xclang +evex512')); + } +} diff --git a/src/Package/Extension/snmp.php b/src/Package/Extension/snmp.php new file mode 100644 index 000000000..d161c6026 --- /dev/null +++ b/src/Package/Extension/snmp.php @@ -0,0 +1,34 @@ +getSourceDir()}/config.m4"); + } + $libs = implode(' ', PkgConfigUtil::getLibsArray('netsnmp')); + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/config.m4", + 'PHP_EVAL_LIBLINE([$SNMP_LIBS], [SNMP_SHARED_LIBADD])', + "SNMP_LIBS=\"{$libs}\"\nPHP_EVAL_LIBLINE([\$SNMP_LIBS], [SNMP_SHARED_LIBADD])" + ); + return true; + } +} diff --git a/src/Package/Extension/spx.php b/src/Package/Extension/spx.php new file mode 100644 index 000000000..bb230ec94 --- /dev/null +++ b/src/Package/Extension/spx.php @@ -0,0 +1,52 @@ +getSourceDir()}/config.m4", + 'CFLAGS="$CFLAGS -Werror -Wall -O3 -pthread -std=gnu90"', + 'CFLAGS="$CFLAGS -pthread"' + ); + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/src/php_spx.h", + "extern zend_module_entry spx_module_entry;\n", + "extern zend_module_entry spx_module_entry;;\n#define phpext_spx_ptr &spx_module_entry\n" + ); + FileSystem::copy("{$this->getSourceDir()}/src/php_spx.h", "{$this->getSourceDir()}/php_spx.h"); + return true; + } + + #[BeforeStage('php', [php::class, 'configureForUnix'], 'ext-spx')] + #[PatchDescription('Fix spx extension compile error when configuring')] + public function patchBeforeConfigure(): void + { + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/Makefile.frag", + '@cp -r assets/web-ui/*', + "@cp -r {$this->getSourceDir()}/assets/web-ui/*", + ); + } + + public function getSharedExtensionEnv(): array + { + $env = parent::getSharedExtensionEnv(); + $env['SPX_SHARED_LIBADD'] = $env['LIBS']; + return $env; + } +} diff --git a/src/Package/Extension/swoole.php b/src/Package/Extension/swoole.php new file mode 100644 index 000000000..c269ba544 --- /dev/null +++ b/src/Package/Extension/swoole.php @@ -0,0 +1,150 @@ +getPhpExtensionPackage('swoole-hook-odbc') && $installer->getPhpExtensionPackage('pdo_odbc')?->isBuildStatic()) { + throw new WrongUsageException('swoole-hook-odbc provides pdo_odbc, if you enable odbc hook for swoole, you must remove pdo_odbc extension.'); + } + // swoole-hook-pgsql conflicts with pdo_pgsql + if ($installer->getPhpExtensionPackage('swoole-hook-pgsql') && $installer->getPhpExtensionPackage('pdo_pgsql')?->isBuildStatic()) { + throw new WrongUsageException('swoole-hook-pgsql provides pdo_pgsql, if you enable pgsql hook for swoole, you must remove pdo_pgsql extension.'); + } + // swoole-hook-sqlite conflicts with pdo_sqlite + if ($installer->getPhpExtensionPackage('swoole-hook-sqlite') && $installer->getPhpExtensionPackage('pdo_sqlite')?->isBuildStatic()) { + throw new WrongUsageException('swoole-hook-sqlite provides pdo_sqlite, if you enable sqlite hook for swoole, you must remove pdo_sqlite extension.'); + } + } + + #[BeforeStage('php', [php::class, 'makeForUnix'], 'ext-swoole')] + #[PatchDescription('Fix maximum version check for Swoole 6.2')] + public function patchBeforeMake(): void + { + FileSystem::replaceFileStr($this->getSourceDir() . '/ext-src/php_swoole_private.h', 'PHP_VERSION_ID > 80500', 'PHP_VERSION_ID >= 80600'); + } + + #[BeforeStage('php', [php::class, 'makeForUnix'], 'ext-swoole')] + #[PatchDescription('Fix swoole with event extension conflict bug on macOS')] + public function patchBeforeMake2(): void + { + if (SystemTarget::getTargetOS() === 'Darwin') { + // Fix swoole with event extension conflict bug + $util_path = shell()->execWithResult('xcrun --show-sdk-path', false)[1][0] . '/usr/include/util.h'; + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/thirdparty/php/standard/proc_open.cc", + 'include ', + "include \"{$util_path}\"", + ); + } + } + + #[CustomPhpConfigureArg('Darwin')] + #[CustomPhpConfigureArg('Linux')] + public function getUnixConfigureArg(bool $shared, PackageBuilder $builder, PackageInstaller $installer): string + { + // enable swoole + $arg = '--enable-swoole' . ($shared ? '=shared' : ''); + + // commonly used feature: coroutine-time + $arg .= ' --enable-swoole-coro-time --with-pic'; + + $arg .= $builder->getOption('enable-zts') ? ' --enable-swoole-thread --disable-thread-context' : ' --disable-swoole-thread --enable-thread-context'; + + // required features: curl, openssl (but curl hook is buggy for php 8.0) + $arg .= php::getPHPVersionID() >= 80100 ? ' --enable-swoole-curl' : ' --disable-swoole-curl'; + $arg .= ' --enable-openssl'; + + // additional features that only require libraries + $arg .= $installer->getLibraryPackage('libcares') ? ' --enable-cares' : ''; + $arg .= $installer->getLibraryPackage('brotli') ? (' --enable-brotli --with-brotli-dir=' . BUILD_ROOT_PATH) : ''; + $arg .= $installer->getLibraryPackage('nghttp2') ? (' --with-nghttp2-dir=' . BUILD_ROOT_PATH) : ''; + $arg .= $installer->getLibraryPackage('zstd') ? ' --enable-zstd' : ''; + $arg .= $installer->getLibraryPackage('liburing') ? ' --enable-iouring' : ''; + $arg .= $installer->getPhpExtensionPackage('sockets') ? ' --enable-sockets' : ''; + + // enable additional features that require the pdo extension, but conflict with pdo_* extensions + // to make sure everything works as it should, this is done in fake addon extensions + $arg .= $installer->getPhpExtensionPackage('swoole-hook-pgsql') ? ' --enable-swoole-pgsql' : ' --disable-swoole-pgsql'; + $arg .= $installer->getPhpExtensionPackage('swoole-hook-mysql') ? ' --enable-mysqlnd' : ' --disable-mysqlnd'; + $arg .= $installer->getPhpExtensionPackage('swoole-hook-sqlite') ? ' --enable-swoole-sqlite' : ' --disable-swoole-sqlite'; + if ($installer->getPhpExtensionPackage('swoole-hook-odbc')) { + $config = new SPCConfigUtil()->getLibraryConfig($installer->getLibraryPackage('unixodbc')); + $arg .= " --with-swoole-odbc=unixODBC,{$builder->getBuildRootPath()} SWOOLE_ODBC_LIBS=\"{$config['libs']}\""; + } + + // Get version from source directory + $ver = null; + $file = SOURCE_PATH . '/php-src/ext/swoole/include/swoole_version.h'; + // Match #define SWOOLE_VERSION "5.1.3" + $pattern = '/#define SWOOLE_VERSION "(.+)"/'; + if (preg_match($pattern, file_get_contents($file), $matches)) { + $ver = $matches[1]; + } + + if ($ver && $ver >= '6.1.0') { + $arg .= ' --enable-swoole-stdext'; + } + + if (SystemTarget::getTargetOS() === 'Darwin') { + $arg .= ' ac_cv_lib_pthread_pthread_barrier_init=no'; + } + + return $arg; + } + + #[AfterStage('php', [php::class, 'smokeTestCliForUnix'], 'ext-swoole-hook-mysql')] + public function mysqlTest(PackageInstaller $installer): void + { + [$ret, $out] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -n' . $this->getSharedExtensionLoadString() . ' --ri "swoole"', false); + $out = implode('', $out); + if ($ret !== 0) { + throw new ValidationException("extension {$this->getName()} failed compile check: php-cli returned {$ret}", validation_module: 'extension swoole_hook_mysql sanity check'); + } + // mysqlnd + if ($installer->getPhpExtensionPackage('swoole-hook-mysql') && !str_contains($out, 'mysqlnd')) { + throw new ValidationException('swoole mysql hook is not enabled correctly.', validation_module: 'Extension swoole mysql hook availability check'); + } + // coroutine_odbc + if ($installer->getPhpExtensionPackage('swoole-hook-odbc') && !str_contains($out, 'coroutine_odbc')) { + throw new ValidationException('swoole odbc hook is not enabled correctly.', validation_module: 'Extension swoole odbc hook availability check'); + } + // coroutine_pgsql + if ($installer->getPhpExtensionPackage('swoole-hook-pgsql') && !str_contains($out, 'coroutine_pgsql')) { + throw new ValidationException( + 'swoole pgsql hook is not enabled correctly.', + validation_module: 'Extension swoole pgsql hook availability check' + ); + } + // coroutine_sqlite + if ($installer->getPhpExtensionPackage('swoole-hook-sqlite') && !str_contains($out, 'coroutine_sqlite')) { + throw new ValidationException( + 'swoole sqlite hook is not enabled correctly.', + validation_module: 'Extension swoole sqlite hook availability check' + ); + } + } +} diff --git a/src/Package/Extension/swow.php b/src/Package/Extension/swow.php new file mode 100644 index 000000000..333a3ed7b --- /dev/null +++ b/src/Package/Extension/swow.php @@ -0,0 +1,44 @@ +getLibraryPackage('openssl') ? ' --enable-swow-ssl' : ' --disable-swow-ssl'; + $arg .= $installer->getLibraryPackage('curl') ? ' --enable-swow-curl' : ' --disable-swow-curl'; + return $arg; + } + + #[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-swow')] + #[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-swow')] + public function patchBeforeBuildconf(PackageInstaller $installer): bool + { + $php_src = $installer->getTargetPackage('php')->getSourceDir(); + if (php::getPHPVersionID() >= 80000 && !is_link("{$php_src}/ext/swow")) { + if (PHP_OS_FAMILY === 'Windows') { + f_passthru("cd {$php_src}/ext && mklink /D swow swow-src\\ext"); + } else { + f_passthru("cd {$php_src}/ext && ln -s swow-src/ext swow"); + } + } + // replace AC_DEFUN([SWOW_PKG_CHECK_MODULES] to AC_DEFUN([SWOW_PKG_CHECK_MODULES_STATIC] + FileSystem::replaceFileStr($this->getSourceDir() . '/ext/config.m4', 'AC_DEFUN([SWOW_PKG_CHECK_MODULES]', 'AC_DEFUN([SWOW_PKG_CHECK_MODULES_STATIC]'); + return false; + } +} diff --git a/src/Package/Extension/trader.php b/src/Package/Extension/trader.php new file mode 100644 index 000000000..546b073a1 --- /dev/null +++ b/src/Package/Extension/trader.php @@ -0,0 +1,23 @@ +getSourceDir()}/config.m4", 'PHP_TA', 'PHP_TRADER'); + return true; + } +} diff --git a/src/Package/Extension/uv.php b/src/Package/Extension/uv.php new file mode 100644 index 000000000..869f4ad99 --- /dev/null +++ b/src/Package/Extension/uv.php @@ -0,0 +1,36 @@ +getSourceDir()}/Makefile", '/^(LDFLAGS =.*)$/m', '$1 -luv -ldl -lrt -pthread'); + return true; + } +} diff --git a/src/Package/Extension/xhprof.php b/src/Package/Extension/xhprof.php new file mode 100644 index 000000000..91c23facf --- /dev/null +++ b/src/Package/Extension/xhprof.php @@ -0,0 +1,35 @@ +getTargetPackage('php')->getSourceDir(); + $link = "{$php_src}/ext/xhprof"; + if (!is_link($link)) { + shell()->cd("{$php_src}/ext")->exec('ln -s xhprof-src/extension xhprof'); + + // patch config.m4 + FileSystem::replaceFileStr( + "{$this->getSourceDir()}/extension/config.m4", + 'if test -f $phpincludedir/ext/pcre/php_pcre.h; then', + 'if test -f $abs_srcdir/ext/pcre/php_pcre.h; then' + ); + return true; + } + return false; + } +} diff --git a/src/Package/Extension/xlswriter.php b/src/Package/Extension/xlswriter.php new file mode 100644 index 000000000..b2f25716e --- /dev/null +++ b/src/Package/Extension/xlswriter.php @@ -0,0 +1,25 @@ +getLibraryPackage('openssl')) { + $arg .= ' --with-openssl=' . $installer->getLibraryPackage('openssl')->getBuildRootPath(); + } + return $arg; + } +} diff --git a/src/Package/Extension/yac.php b/src/Package/Extension/yac.php new file mode 100644 index 000000000..4bf2cf664 --- /dev/null +++ b/src/Package/Extension/yac.php @@ -0,0 +1,25 @@ +getSourceDir()}/storage/allocator/yac_allocator.h", 'defined(HAVE_SHM_MMAP_ANON)', 'defined(YAC_ALLOCATOR_H)'); + FileSystem::replaceFileStr("{$this->getSourceDir()}/serializer/igbinary.c", '#ifdef YAC_ENABLE_IGBINARY', '#if 1'); + FileSystem::replaceFileStr("{$this->getSourceDir()}/serializer/json.c", '#if YAC_ENABLE_JSON', '#if 1'); + return true; + } +} diff --git a/src/Package/Extension/zip.php b/src/Package/Extension/zip.php new file mode 100644 index 000000000..dc2d29c56 --- /dev/null +++ b/src/Package/Extension/zip.php @@ -0,0 +1,20 @@ += 80400 ? '' : ' --with-zlib-dir=' . $builder->getBuildRootPath(); + return '--with-zlib' . $zlib_dir; + } +} diff --git a/src/Package/Library/grpc.php b/src/Package/Library/grpc.php index 86cddcc06..0e2d191af 100644 --- a/src/Package/Library/grpc.php +++ b/src/Package/Library/grpc.php @@ -48,6 +48,7 @@ public function buildUnix(ToolchainInterface $toolchain, LibraryPackage $lib): v '-DgRPC_ZLIB_PROVIDER=package', '-DgRPC_CARES_PROVIDER=package', '-DgRPC_SSL_PROVIDER=package', + '-DCMAKE_SKIP_INSTALL_RPATH=ON', ); if (PHP_OS_FAMILY === 'Linux' && $toolchain->isStatic() && !LinuxUtil::isMuslDist()) { diff --git a/src/Package/Library/imap.php b/src/Package/Library/imap.php index 607d78ee2..3c9f261c9 100644 --- a/src/Package/Library/imap.php +++ b/src/Package/Library/imap.php @@ -4,8 +4,6 @@ namespace Package\Library; -use Package\Target\php; -use StaticPHP\Attribute\Package\AfterStage; use StaticPHP\Attribute\Package\BuildFor; use StaticPHP\Attribute\Package\Library; use StaticPHP\Attribute\Package\PatchBeforeBuild; @@ -19,15 +17,6 @@ #[Library('imap')] class imap { - #[AfterStage('php', [php::class, 'patchUnixEmbedScripts'], 'imap')] - #[PatchDescription('Fix missing -lcrypt in php-config libs on glibc systems')] - public function afterPatchScripts(): void - { - if (SystemTarget::getLibc() === 'glibc') { - FileSystem::replaceFileRegex(BUILD_BIN_PATH . '/php-config', '/^libs="(.*)"$/m', 'libs="$1 -lcrypt"'); - } - } - #[PatchBeforeBuild] #[PatchDescription('Patch imap build system for Linux and macOS compatibility')] public function patchBeforeBuild(LibraryPackage $lib): void @@ -66,14 +55,24 @@ public function buildLinux(LibraryPackage $lib, PackageInstaller $installer): vo } $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()->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') + // PASSWDTYPE=nul avoids any crypt() symbol reference in c-client.a; + // zig-cc 0.15+ uses paths_first strategy and cannot find libcrypt outside of buildroot. + ->exec("yes | make slx {$ssl_options} PASSWDTYPE=nul EXTRACFLAGS='-fPIC -Wno-implicit-function-declaration -Wno-incompatible-function-pointer-types' {$extraLibs}"); + } catch (\Throwable $e) { + // slx target also builds bundled tools (mtest, etc.) which may fail to link -lcrypt dynamically + // (e.g. with zig-cc). We only need c-client.a, so tolerate the failure if it was built. + if (!file_exists("{$lib->getSourceDir()}/c-client/c-client.a")) { + throw $e; + } + } try { shell() ->exec("cp -rf {$lib->getSourceDir()}/c-client/c-client.a {$lib->getLibDir()}/libc-client.a") @@ -94,16 +93,24 @@ public function buildDarwin(LibraryPackage $lib, PackageInstaller $installer): v $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()->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}'" + ); + } catch (\Throwable $e) { + // osx target also builds bundled tools (mtest, etc.) which may fail to link. + // We only need c-client.a, so tolerate the failure if it was built. + if (!file_exists("{$lib->getSourceDir()}/c-client/c-client.a")) { + throw $e; + } + } try { shell() ->exec("cp -rf {$lib->getSourceDir()}/c-client/c-client.a {$lib->getLibDir()}/libc-client.a") diff --git a/src/Package/Library/ncurses.php b/src/Package/Library/ncurses.php index c7c39dc1f..dd591a6f9 100644 --- a/src/Package/Library/ncurses.php +++ b/src/Package/Library/ncurses.php @@ -13,6 +13,7 @@ use StaticPHP\Util\FileSystem; #[Library('ncurses')] +#[Library('ncursesw')] class ncurses { #[BuildFor('Darwin')] @@ -21,37 +22,48 @@ public function build(LibraryPackage $package, ToolchainInterface $toolchain): v { $dirdiff = new DirDiff(BUILD_BIN_PATH); - UnixAutoconfExecutor::create($package) + $ac = UnixAutoconfExecutor::create($package) ->appendEnv([ 'LDFLAGS' => $toolchain->isStatic() ? '-static' : '', - ]) - ->configure( - '--enable-overwrite', - '--with-curses-h', - '--enable-pc-files', - '--enable-echo', - '--disable-widec', - '--with-normal', - '--with-ticlib', - '--without-tests', - '--without-dlsym', - '--without-debug', - '--enable-symlinks', - "--bindir={$package->getBinDir()}", - "--includedir={$package->getIncludeDir()}", - "--libdir={$package->getLibDir()}", - "--prefix={$package->getBuildRootPath()}", - ) + ]); + $wide = $package->getName() === 'ncurses' ? ['--disable-widec'] : []; + // Include standard system terminfo paths as fallback so binaries linking this ncurses + // (e.g. htop) can find terminfo on any target system without needing TERMINFO_DIRS set. + $terminfo_dirs = implode(':', [ + "{$package->getBuildRootPath()}/share/terminfo", + '/etc/terminfo', + '/lib/terminfo', + '/usr/share/terminfo', + ]); + $ac->configure( + '--enable-overwrite', + '--with-curses-h', + '--enable-pc-files', + '--enable-echo', + '--with-normal', + '--with-ticlib', + '--without-tests', + '--without-dlsym', + '--without-debug', + '--enable-symlinks', + "--with-terminfo-dirs={$terminfo_dirs}", + "--bindir={$package->getBinDir()}", + "--includedir={$package->getIncludeDir()}", + "--libdir={$package->getLibDir()}", + "--prefix={$package->getBuildRootPath()}", + ...$wide, + ) ->make(); $new_files = $dirdiff->getIncrementFiles(true); foreach ($new_files as $file) { @unlink(BUILD_BIN_PATH . '/' . $file); } - shell()->cd(BUILD_ROOT_PATH)->exec('rm -rf share/terminfo'); - shell()->cd(BUILD_ROOT_PATH)->exec('rm -rf lib/terminfo'); + // shell()->cd(BUILD_ROOT_PATH)->exec('rm -rf share/terminfo'); + // shell()->cd(BUILD_ROOT_PATH)->exec('rm -rf lib/terminfo'); - $pkgconf_list = ['form.pc', 'menu.pc', 'ncurses++.pc', 'ncurses.pc', 'panel.pc', 'tic.pc']; + $suffix = $package->getName() === 'ncursesw' ? 'w' : ''; + $pkgconf_list = ["form{$suffix}.pc", "menu{$suffix}.pc", "ncurses++{$suffix}.pc", "ncurses{$suffix}.pc", "panel{$suffix}.pc", "tic{$suffix}.pc"]; $package->patchPkgconfPrefix($pkgconf_list); foreach ($pkgconf_list as $pkgconf) { diff --git a/src/Package/Library/postgresql.php b/src/Package/Library/postgresql.php index 18893d0ec..682b79e28 100644 --- a/src/Package/Library/postgresql.php +++ b/src/Package/Library/postgresql.php @@ -119,8 +119,7 @@ public function buildUnix(PackageInstaller $installer, PackageBuilder $builder): // 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/*.so*") ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.dylib"); FileSystem::replaceFileStr("{$this->getLibDir()}/pkgconfig/libpq.pc", '-lldap', '-lldap -llber'); diff --git a/src/Package/Library/curl.php b/src/Package/Target/curl.php similarity index 81% rename from src/Package/Library/curl.php rename to src/Package/Target/curl.php index 283c765a1..dbfa8f7a1 100644 --- a/src/Package/Library/curl.php +++ b/src/Package/Target/curl.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace Package\Library; +namespace Package\Target; use StaticPHP\Attribute\Package\BuildFor; -use StaticPHP\Attribute\Package\Library; use StaticPHP\Attribute\Package\PatchBeforeBuild; +use StaticPHP\Attribute\Package\Target; use StaticPHP\Attribute\PatchDescription; use StaticPHP\Package\LibraryPackage; use StaticPHP\Runtime\Executor\UnixCMakeExecutor; use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\FileSystem; -#[Library('curl')] +#[Target('curl')] class curl { #[PatchBeforeBuild] @@ -48,14 +48,22 @@ public function build(LibraryPackage $lib): void ->optionalPackage('idn2', ...cmake_boolean_args('CURL_USE_IDN2')) ->optionalPackage('libcares', '-DENABLE_ARES=ON') ->addConfigureArgs( - '-DBUILD_CURL_EXE=OFF', + '-DBUILD_CURL_EXE=ON', '-DBUILD_LIBCURL_DOCS=OFF', ) ->build(); // patch pkgconf $lib->patchPkgconfPrefix(['libcurl.pc']); + // curl's CMake embeds krb5 link flags directly without following Requires.private chain, + // so -lkrb5support (from mit-krb5.pc Libs.private) is missing from libcurl.pc. + $pc_path = "{$lib->getLibDir()}/pkgconfig/libcurl.pc"; + if (str_contains(FileSystem::readFile($pc_path), '-lgssapi_krb5')) { + FileSystem::replaceFileRegex($pc_path, '/-lcom_err$/m', '-lcom_err -lkrb5support'); + } shell()->cd("{$lib->getLibDir()}/cmake/CURL/") ->exec("sed -ie 's|\"/lib/libcurl.a\"|\"{$lib->getLibDir()}/libcurl.a\"|g' CURLTargets-release.cmake"); + + $lib->setOutput('Static curl executable path', BUILD_BIN_PATH . '/curl'); } } diff --git a/src/Package/Target/go_xcaddy.php b/src/Package/Target/go_xcaddy.php deleted file mode 100644 index 0f8c75537..000000000 --- a/src/Package/Target/go_xcaddy.php +++ /dev/null @@ -1,26 +0,0 @@ -removeConfigureArgs('--disable-shared', '--enable-static') + ->exec('./autogen.sh') + ->addConfigureArgs($toolchain->isStatic() ? '--enable-static' : '--disable-static') + ->configure() + ->make(with_clean: false); + } +} diff --git a/src/Package/Target/php.php b/src/Package/Target/php.php index 54d41dc5c..38e2ad91b 100644 --- a/src/Package/Target/php.php +++ b/src/Package/Target/php.php @@ -27,6 +27,7 @@ use StaticPHP\Runtime\SystemTarget; use StaticPHP\Toolchain\Interface\ToolchainInterface; use StaticPHP\Toolchain\ToolchainManager; +use StaticPHP\Util\DependencyResolver; use StaticPHP\Util\FileSystem; use StaticPHP\Util\SourcePatcher; use StaticPHP\Util\V2CompatLayer; @@ -215,6 +216,32 @@ public function resolveBuild(TargetPackage $package, PackageInstaller $installer } } + // Mark transitive PHP extension dependencies of static/shared extensions as static too. + // For static extensions: their ext deps must also be static. + // For shared extensions: their ext deps that are not themselves shared must be compiled + // into the static PHP build so their headers and symbols are available when linking the .so. + $all_input_ext_pkgs = array_map(fn ($x) => "ext-{$x}", array_values(array_unique([...$static_extensions, ...$shared_extensions]))); + if (!empty($all_input_ext_pkgs)) { + $transitive_deps = DependencyResolver::resolve($all_input_ext_pkgs, include_suggests: (bool) $package->getBuildOption('with-suggests', false)); + foreach ($transitive_deps as $dep_name) { + if (!str_starts_with($dep_name, 'ext-') || !PackageLoader::hasPackage($dep_name)) { + continue; + } + $dep_extname = substr($dep_name, 4); + if (in_array($dep_extname, $shared_extensions)) { + continue; // already designated as shared + } + $dep_instance = PackageLoader::getPackage($dep_name); + if (!$dep_instance instanceof PhpExtensionPackage || $dep_instance->isBuildStatic() || $dep_instance->isBuildShared()) { + continue; + } + $dep_config = PackageConfig::get($dep_name, 'php-extension', []); + if (($dep_config['build-static'] ?? true) !== false) { + $dep_instance->setBuildStatic(); + } + } + } + // building shared extensions need embed SAPI if (!empty($shared_extensions) && !$package->getBuildOption('build-embed', false) && $package->getName() === 'php') { $installer->addBuildPackage('php-embed'); @@ -266,7 +293,8 @@ public function info(Package $package, PackageInstaller $installer): array $installer->isPackageResolved('php-embed') ? 'embed' : null, $installer->isPackageResolved('frankenphp') ? 'frankenphp' : null, ]); - $static_extensions = array_filter($installer->getResolvedPackages(), fn ($x) => $x instanceof PhpExtensionPackage && $x->isBuildStatic()); + $static_extensions = array_filter($installer->getResolvedPackages(), fn ($x) => $x instanceof PhpExtensionPackage && + $x->isBuildStatic()); $shared_extensions = parse_extension_list($package->getBuildOption('build-shared') ?? []); $install_packages = array_filter($installer->getResolvedPackages(), fn ($x) => $x->getType() !== 'php-extension' && $x->getName() !== 'php' && !str_starts_with($x->getName(), 'php-')); return [ diff --git a/src/Package/Target/php/frankenphp.php b/src/Package/Target/php/frankenphp.php index 8b2fb81d4..f513242b2 100644 --- a/src/Package/Target/php/frankenphp.php +++ b/src/Package/Target/php/frankenphp.php @@ -91,6 +91,7 @@ public function buildFrankenphpForUnix(TargetPackage $package, PackageInstaller 'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' . '-ldflags \"-linkmode=external ' . $extLdFlags . ' ' . '-X \'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy\' ' . + '-X \'github.com/caddyserver/caddy/v2.CustomBinaryName=frankenphp\' ' . '-X \'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ' . "v{$frankenphp_version} PHP {$libphp_version} Caddy'\\\" " . "-tags={$muslTags}nobadger,nomysql,nopgx{$no_brotli}{$no_watcher}", diff --git a/src/Package/Target/php/unix.php b/src/Package/Target/php/unix.php index 888ae8bda..f16d879f6 100644 --- a/src/Package/Target/php/unix.php +++ b/src/Package/Target/php/unix.php @@ -32,10 +32,14 @@ trait unix { #[BeforeStage('php', [self::class, 'buildconfForUnix'], 'php')] + #[PatchDescription('Patch SPC_MICRO_PATCHES defined patches (e.g. cli_checks, disable_huge_page)')] #[PatchDescription('Patch configure.ac for musl and musl-toolchain')] #[PatchDescription('Let php m4 tools use static pkg-config')] public function patchBeforeBuildconf(TargetPackage $package): void { + // php-src patches from micro (reads SPC_MICRO_PATCHES env var) + SourcePatcher::patchPhpSrc(); + // patch configure.ac for musl and musl-toolchain $musl = SystemTarget::getTargetOS() === 'Linux' && SystemTarget::getLibc() === 'musl'; FileSystem::backupFile(SOURCE_PATH . '/php-src/configure.ac'); @@ -47,6 +51,11 @@ public function patchBeforeBuildconf(TargetPackage $package): void // let php m4 tools use static pkg-config FileSystem::replaceFileStr("{$package->getSourceDir()}/build/php.m4", 'PKG_CHECK_MODULES(', 'PKG_CHECK_MODULES_STATIC('); + + // also patch extension config.m4 files (they call PKG_CHECK_MODULES directly, not via php.m4) + foreach (glob("{$package->getSourceDir()}/ext/*/*.m4") as $m4file) { + FileSystem::replaceFileStr($m4file, 'PKG_CHECK_MODULES(', 'PKG_CHECK_MODULES_STATIC('); + } } #[Stage] @@ -108,11 +117,15 @@ public function configureForUnix(TargetPackage $package, PackageInstaller $insta $static_extension_str = $this->makeStaticExtensionString($installer); + // reuse the same make vars so configure conftest links use the same LIBS (incl. -framework flags) + $vars = $this->makeVars($installer); + // run ./configure with args $this->seekPhpSrcLogFileOnException(fn () => shell()->cd($package->getSourceDir())->setEnv([ 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), 'CPPFLAGS' => "-I{$package->getIncludeDir()}", 'LDFLAGS' => "-L{$package->getLibDir()} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), + 'LIBS' => $vars['EXTRA_LIBS'] ?? '', ])->exec("{$cmd} {$args} {$static_extension_str}"), $package->getSourceDir()); } @@ -148,6 +161,25 @@ public function tryPatchMakefileUnix(): void shell()->cd(SOURCE_PATH . '/php-src')->exec('sed -i "s|//lib|/lib|g" Makefile'); } + #[BeforeStage('php', [self::class, 'makeForUnix'], 'php')] + #[PatchDescription('Patch info.c to hide configure command in release builds')] + public function patchInfoCForRelease(): void + { + if (str_contains((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), '-release')) { + FileSystem::replaceFileLineContainsString( + SOURCE_PATH . '/php-src/ext/standard/info.c', + '#ifdef CONFIGURE_COMMAND', + '#ifdef NO_CONFIGURE_COMMAND', + ); + } else { + FileSystem::replaceFileLineContainsString( + SOURCE_PATH . '/php-src/ext/standard/info.c', + '#ifdef NO_CONFIGURE_COMMAND', + '#ifdef CONFIGURE_COMMAND', + ); + } + } + #[Stage] public function makeForUnix(TargetPackage $package, PackageInstaller $installer): void { @@ -435,7 +467,27 @@ public function build(TargetPackage $package): void $package->runStage([$this, 'makeForUnix']); $package->runStage([$this, 'unixBuildSharedExt']); - $package->runStage([$this, 'smokeTestForUnix']); + } + + #[Stage('postInstall')] + public function postInstall(TargetPackage $package, PackageInstaller $installer): void + { + if ($package->getName() === 'frankenphp') { + $package->runStage([$this, 'smokeTestFrankenphpForUnix']); + return; + } + if ($package->getName() !== 'php') { + return; + } + if (SystemTarget::isUnix()) { + if ($installer->interactive) { + InteractiveTerm::indicateProgress('Running PHP smoke tests'); + } + $package->runStage([$this, 'smokeTestForUnix']); + if ($installer->interactive) { + InteractiveTerm::finish('PHP smoke tests passed'); + } + } } /** @@ -714,6 +766,7 @@ private function makeVars(PackageInstaller $installer): array return array_filter([ 'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), + 'EXTRA_CXXFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS'), 'EXTRA_LDFLAGS_PROGRAM' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') . "{$config['ldflags']} {$static} {$pie}", 'EXTRA_LDFLAGS' => $config['ldflags'], 'EXTRA_LIBS' => $libs, diff --git a/src/Package/Target/php/windows.php b/src/Package/Target/php/windows.php index e77b88cd1..74e746e2b 100644 --- a/src/Package/Target/php/windows.php +++ b/src/Package/Target/php/windows.php @@ -22,6 +22,13 @@ trait windows { + #[BeforeStage('php', [self::class, 'buildconfForWindows'])] + #[PatchDescription('Patch for fixing win32 xml related extensions builds')] + public function beforeBuildconfWin(TargetPackage $package): void + { + FileSystem::replaceFileStr("{$package->getSourceDir()}/win32/build/config.w32", 'dllmain.c ', ''); + } + #[Stage] public function buildconfForWindows(TargetPackage $package): void { diff --git a/src/SPC/builder/extension/ffi.php b/src/SPC/builder/extension/ffi.php index 985477232..8e192beab 100644 --- a/src/SPC/builder/extension/ffi.php +++ b/src/SPC/builder/extension/ffi.php @@ -5,11 +5,21 @@ namespace SPC\builder\extension; use SPC\builder\Extension; +use SPC\builder\linux\SystemUtil; +use SPC\store\SourcePatcher; use SPC\util\CustomExt; #[CustomExt('ffi')] class ffi extends Extension { + public function patchBeforeBuildconf(): bool + { + if (PHP_OS_FAMILY === 'Linux' && SystemUtil::getOSRelease()['dist'] === 'centos') { + return SourcePatcher::patchFfiCentos7FixO3strncmp(); + } + return false; + } + public function getUnixConfigureArg(bool $shared = false): string { return '--with-ffi' . ($shared ? '=shared' : '') . ' --enable-zend-signals'; diff --git a/src/SPC/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php index 0a1e02998..7317c4f9d 100644 --- a/src/SPC/store/SourcePatcher.php +++ b/src/SPC/store/SourcePatcher.php @@ -22,7 +22,7 @@ public static function init(): void FileSystem::addSourceExtractHook('swoole', [__CLASS__, 'patchSwoole']); FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchPhpLibxml212']); // migrated FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchGDWin32']); // migrated - FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchFfiCentos7FixO3strncmp']); // 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']); diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php index 27e8bb891..02ba87554 100644 --- a/src/SPC/store/source/PhpSource.php +++ b/src/SPC/store/source/PhpSource.php @@ -6,11 +6,17 @@ use JetBrains\PhpStorm\ArrayShape; use SPC\exception\DownloaderException; +use SPC\exception\SPCException; use SPC\store\Downloader; class PhpSource extends CustomSourceBase { - public const NAME = 'php-src'; + public const string NAME = 'php-src'; + + public const array WEB_PHP_DOMAINS = [ + 'https://www.php.net', + 'https://phpmirror.static-php.dev', + ]; public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void { @@ -28,21 +34,26 @@ public function fetch(bool $force = false, ?array $config = null, int $lock_as = #[ArrayShape(['type' => 'string', 'path' => 'string', 'rev' => 'string', 'url' => 'string'])] public function getLatestPHPInfo(string $major_version): array { - // 查找最新的小版本号 - $info = json_decode(Downloader::curlExec( - url: "https://www.php.net/releases/index.php?json&version={$major_version}", - retries: (int) getenv('SPC_DOWNLOAD_RETRIES') ?: 0 - ), true); - if (!isset($info['version'])) { - throw new DownloaderException("Version {$major_version} not found."); + foreach (self::WEB_PHP_DOMAINS as $domain) { + try { + $info = json_decode(Downloader::curlExec( + url: "{$domain}/releases/index.php?json&version={$major_version}", + retries: (int) getenv('SPC_DOWNLOAD_RETRIES') ?: 0 + ), true); + if (!isset($info['version'])) { + throw new DownloaderException("Version {$major_version} not found."); + } + $version = $info['version']; + return [ + 'type' => 'url', + 'url' => "{$domain}/distributions/php-{$version}.tar.xz", + ]; + } catch (SPCException) { + logger()->warning('Failed to fetch latest PHP version for major version {$major_version} from {$domain}, trying next mirror if available.'); + continue; + } } - - $version = $info['version']; - - // 从官网直接下载 - return [ - 'type' => 'url', - 'url' => "https://www.php.net/distributions/php-{$version}.tar.xz", - ]; + // exception if all mirrors failed + throw new DownloaderException("Failed to fetch latest PHP version for major version {$major_version} from all tried mirrors."); } } diff --git a/src/StaticPHP/Artifact/Artifact.php b/src/StaticPHP/Artifact/Artifact.php index 841775e36..0b5d8a6de 100644 --- a/src/StaticPHP/Artifact/Artifact.php +++ b/src/StaticPHP/Artifact/Artifact.php @@ -292,8 +292,11 @@ public function getDownloadConfig(string $type): mixed */ public function getSourceDir(): string { - // defined in config - $extract = $this->config['source']['extract'] ?? null; + // Prefer cache extract path, fall back to config + $cache_info = ApplicationContext::get(ArtifactCache::class)->getSourceInfo($this->name); + $extract = is_string($cache_info['extract'] ?? null) + ? $cache_info['extract'] + : ($this->config['source']['extract'] ?? null); if ($extract === null) { return FileSystem::convertPath(SOURCE_PATH . '/' . $this->name); diff --git a/src/StaticPHP/Artifact/ArtifactCache.php b/src/StaticPHP/Artifact/ArtifactCache.php index 7626831af..5a2c8bac7 100644 --- a/src/StaticPHP/Artifact/ArtifactCache.php +++ b/src/StaticPHP/Artifact/ArtifactCache.php @@ -203,6 +203,17 @@ public function getBinaryInfo(string $artifact_name, string $platform): ?array return $this->cache[$artifact_name]['binary'][$platform] ?? null; } + /** + * Get all binary cache entries for an artifact, keyed by platform string. + * + * @param string $artifact_name Artifact name + * @return array Map of platform → cache info (may be empty) + */ + public function getAllBinaryInfo(string $artifact_name): array + { + return $this->cache[$artifact_name]['binary'] ?? []; + } + /** * Get the full path to the cached file/directory. * diff --git a/src/StaticPHP/Artifact/ArtifactDownloader.php b/src/StaticPHP/Artifact/ArtifactDownloader.php index a9a259157..6cc57439e 100644 --- a/src/StaticPHP/Artifact/ArtifactDownloader.php +++ b/src/StaticPHP/Artifact/ArtifactDownloader.php @@ -106,7 +106,7 @@ class ArtifactDownloader * no-shallow-clone?: bool * } $options Downloader options */ - public function __construct(protected array $options = []) + public function __construct(protected array $options = [], public readonly bool $interactive = true) { // Allow setting concurrency via options $this->parallel = max(1, (int) ($options['parallel'] ?? 1)); @@ -273,12 +273,10 @@ public function setParallel(int $parallel): static /** * Download all artifacts, with optional parallel processing. - * - * @param bool $interactive Enable interactive mode with Ctrl+C handling */ - public function download(bool $interactive = true): void + public function download(): void { - if ($interactive) { + if ($this->interactive) { Shell::passthruCallback(function () { InteractiveTerm::advance(); }); @@ -311,7 +309,7 @@ public function download(bool $interactive = true): void $count = count($this->artifacts); $artifacts_str = implode(',', array_map(fn ($x) => '' . ConsoleColor::yellow($x->getName()), $this->artifacts)); // mute the first line if not interactive - if ($interactive) { + if ($this->interactive) { InteractiveTerm::notice("Downloading {$count} artifacts: {$artifacts_str} ..."); } try { @@ -329,19 +327,19 @@ public function download(bool $interactive = true): void $skipped = []; foreach ($this->artifacts as $artifact) { ++$current; - if ($this->downloadWithType($artifact, $current, $count, interactive: $interactive) === SPC_DOWNLOAD_STATUS_SKIPPED) { + if ($this->downloadWithType($artifact, $current, $count) === SPC_DOWNLOAD_STATUS_SKIPPED) { $skipped[] = $artifact->getName(); continue; } $this->_before_files = FileSystem::scanDirFiles(DOWNLOAD_PATH, false, true, true) ?: []; } - if ($interactive) { + if ($this->interactive) { $skip_msg = !empty($skipped) ? ' (Skipped ' . count($skipped) . ' artifacts for being already downloaded)' : ''; InteractiveTerm::success("Downloaded all {$count} artifacts.{$skip_msg}\n", true); } } } finally { - if ($interactive) { + if ($this->interactive) { Shell::passthruCallback(null); keyboard_interrupt_unregister(); } @@ -537,7 +535,7 @@ private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_nam return $dl->checkUpdate($artifact_name, $platform_config, null, $this); } - private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false, bool $interactive = true): int + private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false): int { $queue = $this->generateQueue($artifact); // already downloaded @@ -558,7 +556,7 @@ private function downloadWithType(Artifact $artifact, int $current, int $total, }; $try_h = $try ? 'Try downloading' : 'Downloading'; logger()->info("{$try_h} artifact '{$artifact->getName()}' {$item['display']} ..."); - if ($parallel === false && $interactive) { + if ($parallel === false && $this->interactive) { InteractiveTerm::indicateProgress("[{$current}/{$total}] Downloading artifact " . ConsoleColor::green($artifact->getName()) . " {$item['display']} from {$type_display_name} ..."); } // is valid download type @@ -597,13 +595,13 @@ private function downloadWithType(Artifact $artifact, int $current, int $total, } // process lock ApplicationContext::get(ArtifactCache::class)->lock($artifact, $item['lock'], $lock, SystemTarget::getCurrentPlatformString()); - if ($parallel === false && $interactive) { + if ($parallel === false && $this->interactive) { $ver = $lock->hasVersion() ? (' (' . ConsoleColor::yellow($lock->version) . ')') : ''; InteractiveTerm::finish('Downloaded ' . ($verified ? 'and verified ' : '') . 'artifact ' . ConsoleColor::green($artifact->getName()) . $ver . " {$item['display']} ."); } return SPC_DOWNLOAD_STATUS_SUCCESS; } catch (DownloaderException|ExecutionException $e) { - if ($parallel === false && $interactive) { + if ($parallel === false && $this->interactive) { InteractiveTerm::finish("Download artifact {$artifact->getName()} {$item['display']} failed !", false); InteractiveTerm::error("Failed message: {$e->getMessage()}", true); } diff --git a/src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php b/src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php index 61517a9e0..e473c0caf 100644 --- a/src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php +++ b/src/StaticPHP/Artifact/Downloader/Type/GitHubTarball.php @@ -61,7 +61,7 @@ public function getGitHubTarballInfo(string $name, string $repo, string $rel_typ $filename = $matches['filename']; } else { $basename = $basename ?? basename($repo); - $filename = "{$basename}-" . ($rel_type === 'releases' ? $data['tag_name'] : $data['name']) . '.tar.gz'; + $filename = "{$basename}-" . ($rel_type === 'releases' ? ($data['tag_name'] ?? $data['name']) : $data['name']) . '.tar.gz'; } return [$url, $filename]; } diff --git a/src/StaticPHP/Artifact/Downloader/Type/PECL.php b/src/StaticPHP/Artifact/Downloader/Type/PECL.php index 78ceed3a8..df2da341f 100644 --- a/src/StaticPHP/Artifact/Downloader/Type/PECL.php +++ b/src/StaticPHP/Artifact/Downloader/Type/PECL.php @@ -54,7 +54,7 @@ protected function fetchPECLInfo(string $name, array $config, ArtifactDownloader $versions = []; logger()->debug('Matched ' . count($matches['version']) . " releases for {$name} from PECL"); foreach ($matches['version'] as $i => $version) { - if ($matches['state'][$i] !== 'stable') { + if ($matches['state'][$i] !== 'stable' && ($config['prefer-stable'] ?? true) === true) { continue; } $versions[$version] = $peclName . '-' . $version . '.tgz'; diff --git a/src/StaticPHP/Command/Dev/PackageInfoCommand.php b/src/StaticPHP/Command/Dev/PackageInfoCommand.php index 7c8691993..ba621c5e9 100644 --- a/src/StaticPHP/Command/Dev/PackageInfoCommand.php +++ b/src/StaticPHP/Command/Dev/PackageInfoCommand.php @@ -4,10 +4,14 @@ namespace StaticPHP\Command\Dev; +use StaticPHP\Artifact\ArtifactCache; use StaticPHP\Command\BaseCommand; use StaticPHP\Config\ArtifactConfig; use StaticPHP\Config\PackageConfig; +use StaticPHP\DI\ApplicationContext; +use StaticPHP\Registry\PackageLoader; use StaticPHP\Registry\Registry; +use StaticPHP\Runtime\SystemTarget; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -34,18 +38,26 @@ public function handle(): int } $pkgConfig = PackageConfig::get($packageName); - $artifactConfig = ArtifactConfig::get($packageName); + // Resolve the actual artifact name: + // - string field → named reference (e.g. php → php-src) + // - array field → inline artifact, key is package name + // - null → no artifact, or may match by package name + $artifactField = $pkgConfig['artifact'] ?? null; + $artifactName = is_string($artifactField) ? $artifactField : $packageName; + $artifactConfig = ArtifactConfig::get($artifactName); $pkgInfo = Registry::getPackageConfigInfo($packageName); - $artifactInfo = Registry::getArtifactConfigInfo($packageName); + $artifactInfo = Registry::getArtifactConfigInfo($artifactName); + $annotationInfo = PackageLoader::getPackageAnnotationInfo($packageName); + $cacheInfo = $this->resolveCacheInfo($artifactName, $artifactConfig); if ($this->getOption('json')) { - return $this->outputJson($packageName, $pkgConfig, $artifactConfig, $pkgInfo, $artifactInfo); + return $this->outputJson($packageName, $artifactName, $pkgConfig, $artifactConfig, $pkgInfo, $artifactInfo, $annotationInfo, $cacheInfo); } - return $this->outputTerminal($packageName, $pkgConfig, $artifactConfig, $pkgInfo, $artifactInfo); + return $this->outputTerminal($packageName, $pkgConfig, $artifactConfig, $pkgInfo, $artifactInfo, $annotationInfo, $cacheInfo); } - private function outputJson(string $name, array $pkgConfig, ?array $artifactConfig, ?array $pkgInfo, ?array $artifactInfo): int + private function outputJson(string $name, string $artifactName, array $pkgConfig, ?array $artifactConfig, ?array $pkgInfo, ?array $artifactInfo, ?array $annotationInfo, ?array $cacheInfo): int { $data = [ 'name' => $name, @@ -55,15 +67,24 @@ private function outputJson(string $name, array $pkgConfig, ?array $artifactConf ]; if ($artifactConfig !== null) { + $data['artifact_name'] = $artifactName !== $name ? $artifactName : null; $data['artifact_config_file'] = $artifactInfo ? $this->toRelativePath($artifactInfo['config']) : null; $data['artifact'] = $this->splitArtifactConfig($artifactConfig); } + if ($annotationInfo !== null) { + $data['annotations'] = $annotationInfo; + } + + if ($cacheInfo !== null) { + $data['cache'] = $cacheInfo; + } + $this->output->writeln(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); return static::SUCCESS; } - private function outputTerminal(string $name, array $pkgConfig, ?array $artifactConfig, ?array $pkgInfo, ?array $artifactInfo): int + private function outputTerminal(string $name, array $pkgConfig, ?array $artifactConfig, ?array $pkgInfo, ?array $artifactInfo, ?array $annotationInfo, ?array $cacheInfo): int { $type = $pkgConfig['type'] ?? 'unknown'; $registry = $pkgInfo['registry'] ?? 'unknown'; @@ -86,12 +107,15 @@ private function outputTerminal(string $name, array $pkgConfig, ?array $artifact // Artifact config if ($artifactConfig !== null) { $artifactFile = $artifactInfo ? $this->toRelativePath($artifactInfo['config']) : 'unknown'; - $this->output->writeln("── Artifact Config ── file: {$artifactFile}"); - - // Check if artifact config is inline (embedded in pkg config) or separate - $inlineArtifact = $pkgConfig['artifact'] ?? null; - if (is_array($inlineArtifact)) { + $artifactField = $pkgConfig['artifact'] ?? null; + if (is_string($artifactField)) { + // Named reference: show the artifact name it points to + $this->output->writeln("── Artifact Config ── artifact: {$artifactField} file: {$artifactFile}"); + } elseif (is_array($artifactField)) { + $this->output->writeln("── Artifact Config ── file: {$artifactFile}"); $this->output->writeln(' (inline in package config)'); + } else { + $this->output->writeln("── Artifact Config ── file: {$artifactFile}"); } $split = $this->splitArtifactConfig($artifactConfig); @@ -107,9 +131,122 @@ private function outputTerminal(string $name, array $pkgConfig, ?array $artifact $this->output->writeln(''); } + // Annotation section + $this->outputAnnotationSection($name, $annotationInfo); + + // Cache status section + $this->outputCacheSection($cacheInfo); + return static::SUCCESS; } + private function outputAnnotationSection(string $packageName, ?array $annotationInfo): void + { + if ($annotationInfo === null) { + $this->output->writeln('── Annotations ── (no annotation class registered)'); + $this->output->writeln(''); + return; + } + + $shortClass = $this->classBaseName($annotationInfo['class']); + $this->output->writeln("── Annotations ── class: {$shortClass}"); + $this->output->writeln(" {$annotationInfo['class']}"); + + // Method-level hooks + $methods = $annotationInfo['methods']; + if (!empty($methods)) { + $this->output->writeln(''); + $this->output->writeln(' Method hooks:'); + foreach ($methods as $methodName => $attrs) { + $attrList = implode(' ', array_map(fn ($a) => $this->formatAttr($a), $attrs)); + $this->output->writeln(" {$methodName}() {$attrList}"); + } + } + + // Before-stage hooks targeting this package (inbound) + $beforeStages = $annotationInfo['before_stages']; + if (!empty($beforeStages)) { + $this->output->writeln(''); + $this->output->writeln(' Before-stage hooks (inbound):'); + foreach ($beforeStages as $stage => $hooks) { + foreach ($hooks as $hook) { + $source = $this->classBaseName($hook['class']) . '::' . $hook['method'] . '()'; + $cond = $hook['only_when'] !== null ? " (only_when: {$hook['only_when']})" : ''; + $this->output->writeln(" {$stage} ← {$source}{$cond}"); + } + } + } + + // After-stage hooks targeting this package (inbound) + $afterStages = $annotationInfo['after_stages']; + if (!empty($afterStages)) { + $this->output->writeln(''); + $this->output->writeln(' After-stage hooks (inbound):'); + foreach ($afterStages as $stage => $hooks) { + foreach ($hooks as $hook) { + $source = $this->classBaseName($hook['class']) . '::' . $hook['method'] . '()'; + $cond = $hook['only_when'] !== null ? " (only_when: {$hook['only_when']})" : ''; + $this->output->writeln(" {$stage} ← {$source}{$cond}"); + } + } + } + + // Outbound hooks: stages this package's class registers on other packages (exclude self-hooks) + $outboundBefore = $annotationInfo['outbound_before_stages'] ?? []; + $outboundAfter = $annotationInfo['outbound_after_stages'] ?? []; + // Filter out entries targeting the same package — those are already shown inbound + $outboundBefore = array_filter($outboundBefore, fn ($pkg) => $pkg !== $packageName, ARRAY_FILTER_USE_KEY); + $outboundAfter = array_filter($outboundAfter, fn ($pkg) => $pkg !== $packageName, ARRAY_FILTER_USE_KEY); + if (!empty($outboundBefore) || !empty($outboundAfter)) { + $this->output->writeln(''); + $this->output->writeln(' Hooks on other packages (outbound):'); + foreach ($outboundBefore as $targetPkg => $stages) { + foreach ($stages as $stage => $hooks) { + foreach ($hooks as $hook) { + $cond = $hook['only_when'] !== null ? " (only_when: {$hook['only_when']})" : ''; + $this->output->writeln(" #[BeforeStage] → {$targetPkg} {$stage} {$hook['method']}(){$cond}"); + } + } + } + foreach ($outboundAfter as $targetPkg => $stages) { + foreach ($stages as $stage => $hooks) { + foreach ($hooks as $hook) { + $cond = $hook['only_when'] !== null ? " (only_when: {$hook['only_when']})" : ''; + $this->output->writeln(" #[AfterStage] → {$targetPkg} {$stage} {$hook['method']}(){$cond}"); + } + } + } + } + + $this->output->writeln(''); + } + + /** + * Format a single attribute entry (from annotation_map) as a colored inline string. + * + * @param array{attr: string, args: array} $attr + */ + private function formatAttr(array $attr): string + { + $name = $attr['attr']; + $args = $attr['args']; + if (empty($args)) { + return "#[{$name}]"; + } + $argStr = implode(', ', array_map( + fn ($v) => is_string($v) ? "'{$v}'" : (string) $v, + array_values($args) + )); + return "#[{$name}({$argStr})]"; + } + + /** Return the trailing class name component without the namespace. */ + private function classBaseName(string $fqcn): string + { + $parts = explode('\\', $fqcn); + return end($parts); + } + /** * Split artifact config into logical sections for cleaner display. * @@ -190,4 +327,91 @@ private function toRelativePath(string $absolutePath): string } return $normalized; } + + /** + * Build cache status data for display/JSON. + * Returns null when there is no artifact config for this package. + */ + private function resolveCacheInfo(string $name, ?array $artifactConfig): ?array + { + if ($artifactConfig === null) { + return null; + } + $cache = ApplicationContext::get(ArtifactCache::class); + $currentPlatform = SystemTarget::getCurrentPlatformString(); + $hasSource = array_key_exists('source', $artifactConfig) || array_key_exists('source-mirror', $artifactConfig); + $hasBinary = array_key_exists('binary', $artifactConfig) || array_key_exists('binary-mirror', $artifactConfig); + return [ + 'current_platform' => $currentPlatform, + 'has_source' => $hasSource, + 'has_binary' => $hasBinary, + 'source' => $hasSource ? [ + 'downloaded' => $cache->isSourceDownloaded($name), + 'info' => $cache->getSourceInfo($name), + ] : null, + 'binary' => $hasBinary ? $cache->getAllBinaryInfo($name) : null, + ]; + } + + private function outputCacheSection(?array $cacheInfo): void + { + if ($cacheInfo === null) { + $this->output->writeln('── Cache Status ── (no artifact config)'); + $this->output->writeln(''); + return; + } + + $platform = $cacheInfo['current_platform']; + $this->output->writeln("── Cache Status ── current platform: {$platform}"); + + // Source + $this->output->writeln(''); + $this->output->writeln(' source:'); + if (!$cacheInfo['has_source']) { + $this->output->writeln(' ─ not applicable'); + } elseif ($cacheInfo['source']['downloaded'] && $cacheInfo['source']['info'] !== null) { + $this->output->writeln(' ✓ downloaded ' . $this->formatCacheEntry($cacheInfo['source']['info'])); + } else { + $this->output->writeln(' ✗ not downloaded'); + } + + // Binary + $this->output->writeln(''); + $this->output->writeln(' binary:'); + if (!$cacheInfo['has_binary']) { + $this->output->writeln(' ─ not applicable'); + } elseif (empty($cacheInfo['binary'])) { + $this->output->writeln(" ✗ {$platform} (current — not cached)"); + } else { + $allBinary = $cacheInfo['binary']; + foreach ($allBinary as $binPlatform => $binInfo) { + $isCurrent = $binPlatform === $platform; + $tag = $isCurrent ? ' (current)' : ''; + if ($binInfo !== null) { + $this->output->writeln(" ✓ {$binPlatform}{$tag} " . $this->formatCacheEntry($binInfo)); + } else { + $this->output->writeln(" ✗ {$binPlatform}{$tag}"); + } + } + // Show current platform if not already listed + if (!array_key_exists($platform, $allBinary)) { + $this->output->writeln(" ✗ {$platform} (current — not cached)"); + } + } + + $this->output->writeln(''); + } + + private function formatCacheEntry(array $info): string + { + $type = $info['cache_type'] ?? '?'; + $version = $info['version'] !== null ? " {$info['version']}" : ''; + $time = isset($info['time']) ? ' ' . date('Y-m-d H:i', (int) $info['time']) : ''; + $file = match ($type) { + 'archive', 'file' => isset($info['filename']) ? " {$info['filename']}" : '', + 'git', 'local' => isset($info['dirname']) ? " {$info['dirname']}" : '', + default => '', + }; + return "[{$type}]{$version}{$time}{$file}"; + } } diff --git a/src/StaticPHP/Command/InstallPackageCommand.php b/src/StaticPHP/Command/InstallPackageCommand.php index 230322618..864fd3796 100644 --- a/src/StaticPHP/Command/InstallPackageCommand.php +++ b/src/StaticPHP/Command/InstallPackageCommand.php @@ -34,9 +34,9 @@ public function configure(): void public function handle(): int { ApplicationContext::set('elephant', true); - $installer = new PackageInstaller([...$this->input->getOptions(), 'dl-prefer-binary' => true]); + $installer = new PackageInstaller([...$this->input->getOptions(), 'dl-prefer-binary' => true], true); $installer->addInstallPackage($this->input->getArgument('package')); - $installer->run(true, true); + $installer->run(true); return static::SUCCESS; } } diff --git a/src/StaticPHP/Config/ConfigValidator.php b/src/StaticPHP/Config/ConfigValidator.php index f011482c7..919de86df 100644 --- a/src/StaticPHP/Config/ConfigValidator.php +++ b/src/StaticPHP/Config/ConfigValidator.php @@ -16,6 +16,7 @@ class ConfigValidator public const array PACKAGE_FIELD_TYPES = [ // package fields 'type' => ConfigType::STRING, + 'description' => ConfigType::STRING, 'depends' => ConfigType::LIST_ARRAY, // @ 'suggests' => ConfigType::LIST_ARRAY, // @ 'artifact' => [self::class, 'validateArtifactField'], // STRING or OBJECT @@ -39,10 +40,14 @@ class ConfigValidator 'static-libs' => ConfigType::LIST_ARRAY, // @ 'pkg-configs' => ConfigType::LIST_ARRAY, 'static-bins' => ConfigType::LIST_ARRAY, // @ + 'path' => ConfigType::LIST_ARRAY, // @ + 'env' => ConfigType::ASSOC_ARRAY, // @ + 'append-env' => ConfigType::ASSOC_ARRAY, // @ ]; public const array PACKAGE_FIELDS = [ 'type' => true, + 'description' => false, 'depends' => false, // @ 'suggests' => false, // @ 'artifact' => false, @@ -58,6 +63,9 @@ class ConfigValidator 'static-libs' => false, // @ 'pkg-configs' => false, 'static-bins' => false, // @ + 'path' => false, // @ + 'env' => false, // @ + 'append-env' => false, // @ ]; public const array SUFFIX_ALLOWED_FIELDS = [ @@ -66,6 +74,9 @@ class ConfigValidator 'headers', 'static-libs', 'static-bins', + 'path', + 'env', + 'append-env', ]; public const array PHP_EXTENSION_FIELDS = [ @@ -89,7 +100,7 @@ class ConfigValidator 'bitbuckettag' => [['repo'], ['extract']], 'local' => [['dirname'], ['extract']], 'pie' => [['repo'], ['extract']], - 'pecl' => [['name'], ['extract']], + 'pecl' => [['name'], ['extract', 'prefer-stable']], 'php-release' => [['domain'], ['extract']], 'custom' => [[], ['func']], ]; diff --git a/src/StaticPHP/Config/PackageConfig.php b/src/StaticPHP/Config/PackageConfig.php index c4f22a528..0e2d0af1c 100644 --- a/src/StaticPHP/Config/PackageConfig.php +++ b/src/StaticPHP/Config/PackageConfig.php @@ -23,6 +23,7 @@ public static function loadFromDir(string $dir, string $registry_name): array if (!is_dir($dir)) { throw new WrongUsageException("Directory {$dir} does not exist, cannot load pkg.json config."); } + $dir = rtrim($dir, '/'); $loaded = []; $files = FileSystem::scanDirFiles($dir, false); if (is_array($files)) { diff --git a/src/StaticPHP/Doctor/Doctor.php b/src/StaticPHP/Doctor/Doctor.php index fc69cc2a8..1239a30c8 100644 --- a/src/StaticPHP/Doctor/Doctor.php +++ b/src/StaticPHP/Doctor/Doctor.php @@ -18,7 +18,7 @@ readonly class Doctor { - public function __construct(private ?OutputInterface $output = null, private int $auto_fix = FIX_POLICY_PROMPT) + public function __construct(private ?OutputInterface $output = null, private int $auto_fix = FIX_POLICY_PROMPT, public readonly bool $interactive = true) { // debug shows all loaded doctor items $items = DoctorLoader::getDoctorItems(); @@ -53,13 +53,13 @@ public static function markPassed(): void * Check all valid check items. * @return bool true if all checks passed, false otherwise */ - public function checkAll(bool $interactive = true): bool + public function checkAll(): bool { - if ($interactive) { + if ($this->interactive) { InteractiveTerm::notice('Starting doctor checks ...'); } foreach ($this->getValidCheckList() as $check) { - if (!$this->checkItem($check, $interactive)) { + if (!$this->checkItem($check)) { return false; } } @@ -72,7 +72,7 @@ public function checkAll(bool $interactive = true): bool * @param CheckItem|string $check The check item to be checked * @return bool True if the check passed or was fixed, false otherwise */ - public function checkItem(CheckItem|string $check, bool $interactive = true): bool + public function checkItem(CheckItem|string $check): bool { if (is_string($check)) { $found = null; @@ -88,7 +88,7 @@ public function checkItem(CheckItem|string $check, bool $interactive = true): bo } $check = $found; } - $prepend = $interactive ? ' - ' : ''; + $prepend = $this->interactive ? ' - ' : ''; $this->output?->write("{$prepend}Checking {$check->item_name} ... "); // call check diff --git a/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php b/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php index b01b7b7b6..df3b5241c 100644 --- a/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php +++ b/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php @@ -59,8 +59,8 @@ public function checkMuslCrossMake(): ?CheckResult #[FixItem('fix-musl-wrapper')] public function fixMusl(): bool { - $downloader = new ArtifactDownloader(); - $downloader->add('musl-wrapper')->download(false); + $downloader = new ArtifactDownloader(interactive: false); + $downloader->add('musl-wrapper')->download(); $extractor = new ArtifactExtractor(ApplicationContext::get(ArtifactCache::class)); $extractor->extract('musl-wrapper'); @@ -96,9 +96,9 @@ public function fixMuslCrossMake(): bool Shell::passthruCallback(function () { InteractiveTerm::advance(); }); - $downloader = new ArtifactDownloader(); + $downloader = new ArtifactDownloader(interactive: false); $extractor = new ArtifactExtractor(ApplicationContext::get(ArtifactCache::class)); - $downloader->add('musl-toolchain')->download(false); + $downloader->add('musl-toolchain')->download(); $extractor->extract('musl-toolchain'); $pkg_root = PKG_ROOT_PATH . '/musl-toolchain'; f_passthru("{$prefix}cp -rf {$pkg_root}/* /usr/local/musl"); diff --git a/src/StaticPHP/Doctor/Item/PkgConfigCheck.php b/src/StaticPHP/Doctor/Item/PkgConfigCheck.php index 4a0ba498d..888651636 100644 --- a/src/StaticPHP/Doctor/Item/PkgConfigCheck.php +++ b/src/StaticPHP/Doctor/Item/PkgConfigCheck.php @@ -45,9 +45,9 @@ public function checkFunctional(): CheckResult public function fix(): bool { ApplicationContext::set('elephant', true); - $installer = new PackageInstaller(['dl-binary-only' => true]); + $installer = new PackageInstaller(['dl-binary-only' => true], interactive: false); $installer->addInstallPackage('pkg-config'); - $installer->run(false, true); + $installer->run(true); return true; } } diff --git a/src/StaticPHP/Doctor/Item/Re2cVersionCheck.php b/src/StaticPHP/Doctor/Item/Re2cVersionCheck.php index fce3350be..3316be3f9 100644 --- a/src/StaticPHP/Doctor/Item/Re2cVersionCheck.php +++ b/src/StaticPHP/Doctor/Item/Re2cVersionCheck.php @@ -30,9 +30,9 @@ public function checkRe2cVersion(): ?CheckResult #[FixItem('build-re2c')] public function buildRe2c(): bool { - $installer = new PackageInstaller(); + $installer = new PackageInstaller(interactive: false); $installer->addInstallPackage('re2c'); - $installer->run(false); + $installer->run(true); return true; } } diff --git a/src/StaticPHP/Doctor/Item/WindowsToolCheck.php b/src/StaticPHP/Doctor/Item/WindowsToolCheck.php index e6a042d3b..08e140f47 100644 --- a/src/StaticPHP/Doctor/Item/WindowsToolCheck.php +++ b/src/StaticPHP/Doctor/Item/WindowsToolCheck.php @@ -107,7 +107,7 @@ public function installPerl(): bool { $installer = new PackageInstaller(); $installer->addInstallPackage('strawberry-perl'); - $installer->run(false); + $installer->run(true); GlobalEnvManager::addPathIfNotExists(PKG_ROOT_PATH . '\strawberry-perl'); return true; } @@ -116,27 +116,27 @@ public function installPerl(): bool public function installSDK(): bool { FileSystem::removeDir(getenv('PHP_SDK_PATH')); - $installer = new PackageInstaller(); + $installer = new PackageInstaller(interactive: false); $installer->addInstallPackage('php-sdk-binary-tools'); - $installer->run(false); + $installer->run(true); return true; } #[FixItem('install-nasm')] public function installNasm(): bool { - $installer = new PackageInstaller(); + $installer = new PackageInstaller(interactive: false); $installer->addInstallPackage('nasm'); - $installer->run(false); + $installer->run(true); return true; } #[FixItem('install-vswhere')] public function installVSWhere(): bool { - $installer = new PackageInstaller(); + $installer = new PackageInstaller(interactive: false); $installer->addInstallPackage('vswhere'); - $installer->run(false); + $installer->run(true); return true; } } diff --git a/src/StaticPHP/Doctor/Item/ZigCheck.php b/src/StaticPHP/Doctor/Item/ZigCheck.php index c3a6aa9f3..baa6d4cbc 100644 --- a/src/StaticPHP/Doctor/Item/ZigCheck.php +++ b/src/StaticPHP/Doctor/Item/ZigCheck.php @@ -34,9 +34,9 @@ public function checkZig(): CheckResult #[FixItem('install-zig')] public function installZig(): bool { - $installer = new PackageInstaller(); + $installer = new PackageInstaller(interactive: false); $installer->addInstallPackage('zig'); - $installer->run(false); + $installer->run(true); return $installer->isPackageInstalled('zig'); } } diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 628900fa2..417c4e1b6 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -9,6 +9,7 @@ use StaticPHP\Artifact\ArtifactDownloader; use StaticPHP\Artifact\ArtifactExtractor; use StaticPHP\Artifact\DownloaderOptions; +use StaticPHP\Config\PackageConfig; use StaticPHP\DI\ApplicationContext; use StaticPHP\Exception\WrongUsageException; use StaticPHP\Registry\PackageLoader; @@ -46,7 +47,7 @@ class PackageInstaller /** @var null|BuildRootTracker buildroot file tracker for debugging purpose */ protected ?BuildRootTracker $tracker = null; - public function __construct(protected array $options = []) + public function __construct(protected array $options = [], public readonly bool $interactive = true) { ApplicationContext::set(PackageInstaller::class, $this); $builder = new PackageBuilder($options); @@ -143,7 +144,7 @@ public function printBuildPackageOutputs(): void /** * Run the package installation process. */ - public function run(bool $interactive = true, bool $disable_delay_msg = false): void + public function run(bool $disable_delay_msg = false): void { // apply build toolchain envs GlobalEnvManager::afterInit(); @@ -153,7 +154,7 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): $this->resolvePackages(); } - if ($interactive && !$disable_delay_msg) { + if ($this->interactive && !$disable_delay_msg) { // show install or build options in terminal with beautiful output $this->printInstallerInfo(); @@ -167,14 +168,17 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): // check download if ($this->download) { $downloaderOptions = DownloaderOptions::extractFromConsoleOptions($this->options, 'dl'); - $downloader = new ArtifactDownloader([...$downloaderOptions, 'source-only' => implode(',', array_map(fn ($x) => $x->getName(), $this->build_packages))]); - $downloader->addArtifacts($this->getArtifacts())->download($interactive); + $downloader = new ArtifactDownloader( + [...$downloaderOptions, 'source-only' => implode(',', array_map(fn ($x) => $x->getName(), $this->build_packages))], + $this->interactive + ); + $downloader->addArtifacts($this->getArtifacts())->download(); } else { logger()->notice('Skipping download (--no-download option enabled)'); } // extract sources - $this->extractSourceArtifacts(interactive: $interactive); + $this->extractSourceArtifacts(); // validate packages foreach ($this->packages as $package) { @@ -183,7 +187,7 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): } // build/install packages - if ($interactive) { + if ($this->interactive) { InteractiveTerm::notice('Building/Installing packages ...'); keyboard_interrupt_register(function () { InteractiveTerm::finish('Build/Install process interrupted by user!', false); @@ -198,7 +202,7 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): $has_source = $package->hasSource(); if (!$is_to_build && $should_use_binary) { // install binary - if ($interactive) { + if ($this->interactive) { InteractiveTerm::indicateProgress('Installing package: ' . ConsoleColor::yellow($package->getName())); } try { @@ -210,17 +214,17 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): } catch (\Throwable $e) { // Stop tracking on error $this->tracker?->stopTracking(); - if ($interactive) { + if ($this->interactive) { InteractiveTerm::finish('Installing binary package failed: ' . ConsoleColor::red($package->getName()), false); echo PHP_EOL; } throw $e; } - if ($interactive) { + if ($this->interactive) { InteractiveTerm::finish('Installed binary package: ' . ConsoleColor::green($package->getName()) . ($status === SPC_STATUS_ALREADY_INSTALLED ? ' (already installed, skipped)' : '')); } } elseif ($is_to_build && $has_build_stage || $has_source && $has_build_stage) { - if ($interactive) { + if ($this->interactive) { InteractiveTerm::indicateProgress('Building package: ' . ConsoleColor::yellow($package->getName())); } try { @@ -243,22 +247,20 @@ public function run(bool $interactive = true, bool $disable_delay_msg = false): } catch (\Throwable $e) { // Stop tracking on error $this->tracker?->stopTracking(); - if ($interactive) { + if ($this->interactive) { InteractiveTerm::finish('Building package failed: ' . ConsoleColor::red($package->getName()), false); echo PHP_EOL; } throw $e; } - if ($interactive) { + if ($this->interactive) { InteractiveTerm::finish('Built package: ' . ConsoleColor::green($package->getName()) . ($status === SPC_STATUS_ALREADY_BUILT ? ' (already built, skipped)' : '')); } } } - $this->dumpLicenseFiles($this->packages); - if ($interactive) { - InteractiveTerm::success('Exported package licenses', true); - } + // perform after-install actions and emit post-install events + $this->emitPostInstallEvents(); } public function isBuildPackage(Package|string $package): bool @@ -311,6 +313,17 @@ public function isPackageInstalled(Package|string $package_name): bool return false; } + public function emitPostInstallEvents(): void + { + foreach ($this->packages as $package) { + if ($package->hasStage('postInstall')) { + $package->runStage('postInstall'); + } + } + + $this->dumpLicenseFiles($this->packages); + } + /** * Returns the download status of all artifacts for the resolved packages. * @@ -368,7 +381,7 @@ public function getArtifacts(): array /** * Extract all artifacts for resolved packages. */ - public function extractSourceArtifacts(bool $interactive = true): void + public function extractSourceArtifacts(): void { FileSystem::createDir(SOURCE_PATH); $packages = array_values($this->packages); @@ -403,7 +416,7 @@ public function extractSourceArtifacts(bool $interactive = true): void } // Extract each artifact - if ($interactive) { + if ($this->interactive) { InteractiveTerm::notice('Extracting source for ' . count($artifacts) . ' artifacts: ' . implode(',', array_map(fn ($x) => ConsoleColor::yellow($x->getName()), $artifacts)) . ' ...'); InteractiveTerm::indicateProgress('Extracting artifacts'); } @@ -411,7 +424,7 @@ public function extractSourceArtifacts(bool $interactive = true): void try { V2CompatLayer::beforeExtsExtractHook(); foreach ($artifacts as $artifact) { - if ($interactive) { + if ($this->interactive) { InteractiveTerm::setMessage('Extracting source: ' . ConsoleColor::green($artifact->getName())); } if (($pkg = array_search($artifact->getName(), $pkg_artifact_map, true)) !== false) { @@ -423,12 +436,12 @@ public function extractSourceArtifacts(bool $interactive = true): void } } V2CompatLayer::afterExtsExtractHook(); - if ($interactive) { + if ($this->interactive) { InteractiveTerm::finish('Extracted all sources successfully.'); echo PHP_EOL; } } catch (\Throwable $e) { - if ($interactive) { + if ($this->interactive) { InteractiveTerm::finish('Artifact extraction failed!', false); echo PHP_EOL; } @@ -525,6 +538,9 @@ private function dumpLicenseFiles(array $packages): void } } $dumper->dump(BUILD_ROOT_PATH . '/license'); + if ($this->interactive) { + InteractiveTerm::success('Exported package licenses', true); + } } /** @@ -565,6 +581,30 @@ private function resolvePackages(): void foreach ($resolved_packages as $pkg_name) { $this->packages[$pkg_name] = PackageLoader::getPackage($pkg_name); } + + foreach ($this->packages as $package) { + $this->injectPackageEnvs($package); + } + } + + private function injectPackageEnvs(Package $package): void + { + $name = $package->getName(); + + $paths = PackageConfig::get($name, 'path', []); + foreach ($paths as $path) { + GlobalEnvManager::addPathIfNotExists(FileSystem::replacePathVariable($path)); + } + + $envs = PackageConfig::get($name, 'env', []); + foreach ($envs as $k => $v) { + GlobalEnvManager::putenv("{$k}=" . FileSystem::replacePathVariable((string) $v)); + } + + $append_envs = PackageConfig::get($name, 'append-env', []); + foreach ($append_envs as $k => $v) { + GlobalEnvManager::appendEnv($k, FileSystem::replacePathVariable((string) $v)); + } } private function handlePhpTargetPackage(TargetPackage $package): void diff --git a/src/StaticPHP/Package/PhpExtensionPackage.php b/src/StaticPHP/Package/PhpExtensionPackage.php index 06b622093..baaa27531 100644 --- a/src/StaticPHP/Package/PhpExtensionPackage.php +++ b/src/StaticPHP/Package/PhpExtensionPackage.php @@ -94,7 +94,7 @@ public function getPhpConfigureArg(string $os, bool $shared): string '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}", - 'custom' => '', + 'custom', 'none' => '', default => $arg_type, }; // customize argument from config string @@ -148,11 +148,11 @@ public function buildShared(): void /** * Get the dist name used for `--ri` check in smoke test. - * Reads from config `dist-name` field, defaults to extension name. + * Reads from config `display-name` field, defaults to extension name. */ public function getDistName(): string { - return $this->extension_config['dist-name'] ?? $this->getExtensionName(); + return $this->extension_config['display-name'] ?? $this->getExtensionName(); } /** @@ -166,7 +166,7 @@ public function runSmokeTestCliUnix(): void } $distName = $this->getDistName(); - // empty dist-name → no --ri check (e.g. password_argon2) + // empty display-name → no --ri check (e.g. password_argon2) if ($distName === '') { return; } @@ -327,38 +327,11 @@ public function registerDefaultStages(): void } } - /** - * Splits a given string of library flags into static and shared libraries. - * - * @param string $allLibs A space-separated string of library flags (e.g., -lxyz). - * @return array an array containing two elements: the first is a space-separated string - * of static library flags, and the second is a space-separated string - * of shared library flags - */ - protected function splitLibsIntoStaticAndShared(string $allLibs): array - { - $staticLibString = ''; - $sharedLibString = ''; - $libs = explode(' ', $allLibs); - foreach ($libs as $lib) { - $staticLib = BUILD_LIB_PATH . '/lib' . str_replace('-l', '', $lib) . '.a'; - if (str_starts_with($lib, BUILD_LIB_PATH . '/lib') && str_ends_with($lib, '.a')) { - $staticLib = $lib; - } - if ($lib === '-lphp' || !file_exists($staticLib)) { - $sharedLibString .= " {$lib}"; - } else { - $staticLibString .= " {$lib}"; - } - } - return [trim($staticLibString), trim($sharedLibString)]; - } - /** * Builds the `-d extension_dir=... -d extension=...` string for all resolved shared extensions. * Used in CLI smoke test to load shared extension dependencies at runtime. */ - private function getSharedExtensionLoadString(): string + public function getSharedExtensionLoadString(): string { $sharedExts = array_filter( $this->getInstaller()->getResolvedPackages(PhpExtensionPackage::class), @@ -382,6 +355,33 @@ private function getSharedExtensionLoadString(): string return $ret; } + /** + * Splits a given string of library flags into static and shared libraries. + * + * @param string $allLibs A space-separated string of library flags (e.g., -lxyz). + * @return array an array containing two elements: the first is a space-separated string + * of static library flags, and the second is a space-separated string + * of shared library flags + */ + protected function splitLibsIntoStaticAndShared(string $allLibs): array + { + $staticLibString = ''; + $sharedLibString = ''; + $libs = explode(' ', $allLibs); + foreach ($libs as $lib) { + $staticLib = BUILD_LIB_PATH . '/lib' . str_replace('-l', '', $lib) . '.a'; + if (str_starts_with($lib, BUILD_LIB_PATH . '/lib') && str_ends_with($lib, '.a')) { + $staticLib = $lib; + } + if ($lib === '-lphp' || !file_exists($staticLib)) { + $sharedLibString .= " {$lib}"; + } else { + $staticLibString .= " {$lib}"; + } + } + return [trim($staticLibString), trim($sharedLibString)]; + } + /** * Escape PHP test file content for inline `-r` usage. * Strips Track loaded classes to prevent duplicates */ private static array $loaded_classes = []; + /** + * Annotation metadata keyed by package name, capturing the defining class and its method-level attributes. + * + * @var array}>>}> + */ + private static array $annotation_map = []; + + /** + * Source metadata for #[BeforeStage] hooks, keyed by target package name → stage name. + * + * @var array>> + */ + private static array $before_stage_meta = []; + + /** + * Source metadata for #[AfterStage] hooks, keyed by target package name → stage name. + * + * @var array>> + */ + private static array $after_stage_meta = []; + + /** + * Reverse index of #[BeforeStage] hooks, keyed by registering class → target package → stage. + * Enables O(1) "outbound hook" lookup: what stages does a given class hook into on other packages? + * + * @var array>>> + */ + private static array $class_before_stage_meta = []; + + /** + * Reverse index of #[AfterStage] hooks, keyed by registering class → target package → stage. + * + * @var array>>> + */ + private static array $class_after_stage_meta = []; + public static function initPackageInstances(): void { if (self::$packages !== null) { @@ -213,8 +249,19 @@ public static function loadFromClass(mixed $class): void Validate::class => $pkg->setValidateCallback([$instance_class, $method->getName()]), default => null, }; + + // Capture annotation metadata for inspection (dev:info, future event-trace commands) + $meta_attr = self::annotationShortName($method_attribute->getName()); + if ($meta_attr !== null) { + self::$annotation_map[$pkg->getName()]['methods'][$method->getName()][] = [ + 'attr' => $meta_attr, + 'args' => self::annotationArgs($method_instance), + ]; + } } } + // Record which class defines this package (set once; IS_REPEATABLE may loop more than once) + self::$annotation_map[$pkg->getName()]['class'] ??= $class_name; // register package self::$packages[$pkg->getName()] = $pkg; } @@ -260,6 +307,63 @@ public static function getAllAfterStages(): array return self::$after_stages; } + /** + * Get annotation metadata for a specific package. + * + * Returns null if no annotation class was loaded for this package (config-only package). + * The returned structure includes the defining class name, per-method attribute list, + * inbound BeforeStage/AfterStage hooks targeting this package, and outbound hooks that + * this package's class registers on other packages. + * + * @return null|array{ + * class: string, + * methods: array}>>, + * before_stages: array>, + * after_stages: array>, + * outbound_before_stages: array>>, + * outbound_after_stages: array>> + * } + */ + public static function getPackageAnnotationInfo(string $name): ?array + { + $class_info = self::$annotation_map[$name] ?? null; + if ($class_info === null) { + return null; + } + $class = $class_info['class']; + return [ + 'class' => $class, + 'methods' => $class_info['methods'], + 'before_stages' => self::$before_stage_meta[$name] ?? [], + 'after_stages' => self::$after_stage_meta[$name] ?? [], + 'outbound_before_stages' => self::$class_before_stage_meta[$class] ?? [], + 'outbound_after_stages' => self::$class_after_stage_meta[$class] ?? [], + ]; + } + + /** + * Get all annotation metadata keyed by package name. + * Useful for future event-trace commands or cross-package inspection. + * + * @return array + */ + public static function getAllAnnotations(): array + { + $result = []; + foreach (self::$annotation_map as $name => $info) { + $class = $info['class']; + $result[$name] = [ + 'class' => $class, + 'methods' => $info['methods'], + 'before_stages' => self::$before_stage_meta[$name] ?? [], + 'after_stages' => self::$after_stage_meta[$name] ?? [], + 'outbound_before_stages' => self::$class_before_stage_meta[$class] ?? [], + 'outbound_after_stages' => self::$class_after_stage_meta[$class] ?? [], + ]; + } + return $result; + } + public static function getBeforeStageCallbacks(string $package_name, string $stage): iterable { // match condition @@ -385,6 +489,16 @@ private static function addBeforeStage(\ReflectionMethod $method, ?Package $pkg, } $package_name = $method_instance->package_name === '' ? $pkg->getName() : $method_instance->package_name; self::$before_stages[$package_name][$stage][] = [[$instance_class, $method->getName()], $method_instance->only_when_package_resolved]; + $registering_class = get_class($instance_class); + self::$before_stage_meta[$package_name][$stage][] = [ + 'class' => $registering_class, + 'method' => $method->getName(), + 'only_when' => $method_instance->only_when_package_resolved, + ]; + self::$class_before_stage_meta[$registering_class][$package_name][$stage][] = [ + 'method' => $method->getName(), + 'only_when' => $method_instance->only_when_package_resolved, + ]; } private static function addAfterStage(\ReflectionMethod $method, ?Package $pkg, mixed $instance_class, object $method_instance): void @@ -400,5 +514,49 @@ private static function addAfterStage(\ReflectionMethod $method, ?Package $pkg, } $package_name = $method_instance->package_name === '' ? $pkg->getName() : $method_instance->package_name; self::$after_stages[$package_name][$stage][] = [[$instance_class, $method->getName()], $method_instance->only_when_package_resolved]; + $registering_class = get_class($instance_class); + self::$after_stage_meta[$package_name][$stage][] = [ + 'class' => $registering_class, + 'method' => $method->getName(), + 'only_when' => $method_instance->only_when_package_resolved, + ]; + self::$class_after_stage_meta[$registering_class][$package_name][$stage][] = [ + 'method' => $method->getName(), + 'only_when' => $method_instance->only_when_package_resolved, + ]; + } + + /** + * Map a fully-qualified attribute class name to a short display name for metadata storage. + * Returns null for attributes that are not tracked in the annotation map. + */ + private static function annotationShortName(string $attr): ?string + { + return match ($attr) { + Stage::class => 'Stage', + BuildFor::class => 'BuildFor', + PatchBeforeBuild::class => 'PatchBeforeBuild', + CustomPhpConfigureArg::class => 'CustomPhpConfigureArg', + InitPackage::class => 'InitPackage', + ResolveBuild::class => 'ResolveBuild', + Info::class => 'Info', + Validate::class => 'Validate', + default => null, + }; + } + + /** + * Extract the meaningful constructor arguments from an attribute instance as a key-value array. + * + * @return array + */ + private static function annotationArgs(object $inst): array + { + return match (true) { + $inst instanceof Stage => array_filter(['function' => $inst->function], fn ($v) => $v !== null), + $inst instanceof BuildFor => ['os' => $inst->os], + $inst instanceof CustomPhpConfigureArg => array_filter(['os' => $inst->os], fn ($v) => $v !== ''), + default => [], + }; } } diff --git a/src/StaticPHP/Runtime/Executor/UnixAutoconfExecutor.php b/src/StaticPHP/Runtime/Executor/UnixAutoconfExecutor.php index 41bc6e784..c59859cf4 100644 --- a/src/StaticPHP/Runtime/Executor/UnixAutoconfExecutor.php +++ b/src/StaticPHP/Runtime/Executor/UnixAutoconfExecutor.php @@ -20,8 +20,6 @@ class UnixAutoconfExecutor extends Executor protected array $configure_args = []; - protected array $ignore_args = []; - protected PackageInstaller $installer; public function __construct(protected LibraryPackage $package, ?PackageInstaller $installer = null) @@ -40,6 +38,8 @@ public function __construct(protected LibraryPackage $package, ?PackageInstaller if (!$this->package->hasStage('build')) { throw new SPCInternalException("Package {$this->package->getName()} does not have a build stage defined."); } + + $this->configure_args = $this->getDefaultConfigureArgs(); } /** @@ -48,18 +48,12 @@ public function __construct(protected LibraryPackage $package, ?PackageInstaller public function configure(...$args): static { // remove all the ignored args - $args = array_merge($args, $this->getDefaultConfigureArgs(), $this->configure_args); - $args = array_diff($args, $this->ignore_args); + $args = array_merge($args, $this->configure_args); $configure_args = implode(' ', $args); InteractiveTerm::setMessage('Building package: ' . ConsoleColor::yellow($this->package->getName()) . ' (./configure)'); return $this->seekLogFileOnException(fn () => $this->shell->exec("./configure {$configure_args}")); } - public function getConfigureArgsString(): string - { - return implode(' ', array_merge($this->getDefaultConfigureArgs(), $this->configure_args)); - } - /** * Run make * @@ -134,7 +128,7 @@ public function addConfigureArgs(...$args): static */ public function removeConfigureArgs(...$args): static { - $this->ignore_args = [...$this->ignore_args, ...$args]; + $this->configure_args = array_diff($this->configure_args, $args); return $this; } diff --git a/src/StaticPHP/Toolchain/ZigToolchain.php b/src/StaticPHP/Toolchain/ZigToolchain.php index 344ce3e9c..e817abd75 100644 --- a/src/StaticPHP/Toolchain/ZigToolchain.php +++ b/src/StaticPHP/Toolchain/ZigToolchain.php @@ -67,6 +67,9 @@ public function afterInit(): void $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}"); } + // zig-cc/clang treats strlcpy/strlcat as compiler builtins, so configure link tests pass (HAVE_STRLCPY=1) + $extra_vars = getenv('SPC_EXTRA_PHP_VARS') ?: ''; + GlobalEnvManager::putenv("SPC_EXTRA_PHP_VARS=ac_cv_func_strlcpy=no ac_cv_func_strlcat=no {$extra_vars}"); } public function getCompilerInfo(): ?string diff --git a/src/StaticPHP/Util/DependencyResolver.php b/src/StaticPHP/Util/DependencyResolver.php index 2db74abd5..129468f92 100644 --- a/src/StaticPHP/Util/DependencyResolver.php +++ b/src/StaticPHP/Util/DependencyResolver.php @@ -45,6 +45,35 @@ public static function resolve(array $packages, array $dependency_overrides = [] } } + // Build a lookup set of explicitly requested packages for the promotion step below. + $input_package_set = []; + foreach ($packages as $pkg) { + $input_package_set[is_string($pkg) ? $pkg : $pkg->getName()] = true; + } + + // Virtual-target packages (e.g. php-fpm) are built as part of their real parent's + // build step, so any dependency they declare must be available before the real parent + // is built. Promote those deps directly onto the real parent's dependency list so + // that the topological sort places them before the parent. + // Only applies to virtual-targets that are in the input request — if a virtual-target + // is not being built, its deps must not be injected into the parent. + foreach ($dep_list_clean as $pkg_name => $pkg_item) { + if (!isset($input_package_set[$pkg_name]) || PackageConfig::get($pkg_name, 'type') !== 'virtual-target') { + continue; + } + foreach ($pkg_item['depends'] as $dep_name) { + if (isset($dep_list_clean[$dep_name]) && PackageConfig::get($dep_name, 'type') !== 'virtual-target') { + // $dep_name is the real parent; add all other deps of this virtual-target to it + $other_deps = array_values(array_filter($pkg_item['depends'], fn ($d) => $d !== $dep_name)); + if (!empty($other_deps)) { + $dep_list_clean[$dep_name]['depends'] = array_values(array_unique( + array_merge($dep_list_clean[$dep_name]['depends'], $other_deps) + )); + } + } + } + } + $resolved = self::doVisitPlat($packages, $dep_list_clean); // Build reverse dependency map if $why is requested diff --git a/src/StaticPHP/Util/FileSystem.php b/src/StaticPHP/Util/FileSystem.php index c8da5353d..3015b4891 100644 --- a/src/StaticPHP/Util/FileSystem.php +++ b/src/StaticPHP/Util/FileSystem.php @@ -481,7 +481,7 @@ public static function replaceFileLineContainsString(string $file, string $find, public static function fullpath(string $path, string $relative_path_base): string { if (FileSystem::isRelativePath($path)) { - $path = $relative_path_base . DIRECTORY_SEPARATOR . $path; + $path = rtrim($relative_path_base, '/') . DIRECTORY_SEPARATOR . $path; } if (!file_exists($path)) { throw new FileSystemException("Path does not exist: {$path}"); diff --git a/src/StaticPHP/Util/GlobalEnvManager.php b/src/StaticPHP/Util/GlobalEnvManager.php index 86fcc6524..5b4b16b25 100644 --- a/src/StaticPHP/Util/GlobalEnvManager.php +++ b/src/StaticPHP/Util/GlobalEnvManager.php @@ -112,6 +112,17 @@ public static function addPathIfNotExists(string $path): void } } + public static function appendEnv(string $key, string $value): void + { + $existing = getenv($key); + if ($existing !== false && $existing !== '') { + $separator = SystemTarget::isUnix() ? ':' : ';'; + self::putenv("{$key}={$value}{$separator}{$existing}"); + } else { + self::putenv("{$key}={$value}"); + } + } + /** * Initialize the toolchain after the environment variables are set. * The toolchain or environment availability check is done here. diff --git a/src/StaticPHP/Util/SPCConfigUtil.php b/src/StaticPHP/Util/SPCConfigUtil.php index 32ef3bc64..8b6fe6b37 100644 --- a/src/StaticPHP/Util/SPCConfigUtil.php +++ b/src/StaticPHP/Util/SPCConfigUtil.php @@ -389,7 +389,9 @@ private function getLibsString(array $packages, bool $use_short_libs = true): st } if (in_array('imap', $packages) && SystemTarget::getTargetOS() === 'Linux' && SystemTarget::getLibc() === 'glibc') { - $lib_names[] = '-lcrypt'; + if (file_exists(BUILD_LIB_PATH . '/libcrypt.a')) { + $lib_names[] = '-lcrypt'; + } } if (!$use_short_libs) { $lib_names = array_map(fn ($l) => $this->getFullLibName($l), $lib_names); diff --git a/src/StaticPHP/Util/SourcePatcher.php b/src/StaticPHP/Util/SourcePatcher.php index 6a16f041f..b4e2e1c7b 100644 --- a/src/StaticPHP/Util/SourcePatcher.php +++ b/src/StaticPHP/Util/SourcePatcher.php @@ -209,9 +209,6 @@ public static function patchPhpSrc(?array $items = null): bool $patch_dir = $tmp_dir; } $php_package = PackageLoader::getTargetPackage('php'); - if (!file_exists("{$php_package->getSourceDir()}/sapi/micro/php_micro.c")) { - return false; - } $ver_file = "{$php_package->getSourceDir()}/main/php_version.h"; if (!file_exists($ver_file)) { throw new PatchException('php-src patcher (original micro patches)', 'Patch failed, cannot find php source files'); diff --git a/src/StaticPHP/Util/System/UnixUtil.php b/src/StaticPHP/Util/System/UnixUtil.php index aca50d9e4..4a41c5244 100644 --- a/src/StaticPHP/Util/System/UnixUtil.php +++ b/src/StaticPHP/Util/System/UnixUtil.php @@ -74,10 +74,10 @@ public static function getDynamicExportedSymbols(string $lib_file): ?string throw new SPCInternalException("The symbol file {$symbol_file} does not exist, please check if nm command is available."); } // https://github.com/ziglang/zig/issues/24662 - if (ApplicationContext::get(ToolchainInterface::class) instanceof ZigToolchain) { - return '-Wl,--export-dynamic'; + $toolchain = ApplicationContext::get(ToolchainInterface::class); + if ($toolchain instanceof ZigToolchain) { + return '-Wl,--export-dynamic'; // needs release 0.16, can be removed then } - // macOS if (SystemTarget::getTargetOS() !== 'Linux') { return "-Wl,-exported_symbols_list,{$symbol_file}"; }