diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..fa651c2
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,17 @@
+{
+ "name": "Pskel (for Codespaces)",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "ms-vscode.cpptools",
+ "ms-vscode.cpptools-extension-pack",
+ "maelvalais.autoconf",
+ "ms-azuretools.vscode-docker",
+ "editorconfig.editorconfig",
+ "markis.code-coverage"
+ ]
+ }
+ },
+ "dockerComposeFile": "./../compose.yaml",
+ "service": "shell"
+}
diff --git a/.devcontainer/local/devcontainer.json b/.devcontainer/local/devcontainer.json
new file mode 100644
index 0000000..f7491f2
--- /dev/null
+++ b/.devcontainer/local/devcontainer.json
@@ -0,0 +1,21 @@
+{
+ "name": "Pskel (for Local)",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "ms-vscode.cpptools",
+ "ms-vscode.cpptools-extension-pack",
+ "maelvalais.autoconf",
+ "ms-azuretools.vscode-docker",
+ "editorconfig.editorconfig",
+ "markis.code-coverage"
+ ]
+ }
+ },
+ "dockerComposeFile": "./../../compose.yaml",
+ "service": "shell",
+ "mounts": [
+ "source=${localWorkspaceFolder},target=/workspaces/pskel,type=bind,consistency=cached"
+ ],
+ "workspaceFolder": "/workspaces/pskel"
+}
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..723dc5c
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,4 @@
+**/.git
+**/README.md
+**/LICENSE
+**/.editorconfig
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..10f7f6b
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,46 @@
+# https://editorconfig.org/
+
+root = true
+
+[*]
+trim_trailing_whitespace = true
+insert_final_newline = true
+end_of_line = lf
+charset = utf-8
+tab_width = 4
+
+[{*.{awk,bat,c,cpp,d,dasc,h,l,re,skl,w32,y},Makefile*}]
+indent_size = 4
+indent_style = tab
+
+[*.{dtd,html,inc,php,phpt,rng,wsdl,xml,xsd,xsl}]
+indent_size = 4
+indent_style = space
+
+[*.{ac,m4,sh,yml}]
+indent_size = 2
+indent_style = space
+
+[*.md]
+indent_style = space
+max_line_length = 80
+
+[COMMIT_EDITMSG]
+indent_size = 4
+indent_style = space
+max_line_length = 80
+
+[*.patch]
+trim_trailing_whitespace = false
+
+[*.json]
+indent_size = 2
+indent_style = space
+
+[compose.yaml]
+indent_size = 2
+indent_style = space
+
+[Dockerfile]
+indent_size = 2
+indent_style = space
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..2bbfda4
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ - package-ecosystem: "docker"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/octocov.yml b/.github/octocov.yml
new file mode 100644
index 0000000..8c11969
--- /dev/null
+++ b/.github/octocov.yml
@@ -0,0 +1,17 @@
+coverage:
+ paths:
+ - ../lcov.info
+ acceptable: current >= 60%
+testExecutionTime:
+ if: false
+comment:
+ if: is_pull_request
+diff:
+ datastores:
+ - artifact://${GITHUB_REPOSITORY}
+comment:
+ if: is_pull_request
+report:
+ if: is_default_branch
+ datastores:
+ - artifact://${GITHUB_REPOSITORY}
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..59133f3
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,94 @@
+name: CI
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ schedule:
+ - cron: '0 0 * * 1'
+permissions:
+ contents: write
+ pull-requests: write
+jobs:
+ Linux:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ platform: ['linux/amd64', 'linux/arm64/v8', 'linux/s390x']
+ version: ['8.1', '8.2', '8.3']
+ type: ['cli', 'zts']
+ distro: ['bookworm', 'alpine']
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+ - name: Setup QEMU
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: "arm64,s390x"
+ - name: Setup buildx
+ uses: docker/setup-buildx-action@v3
+ - name: Build container
+ run: |
+ docker compose build --pull --no-cache --build-arg PLATFORM="${{ matrix.platform }}" --build-arg IMAGE="php" --build-arg TAG="${{ matrix.version }}-${{ matrix.type }}-${{ matrix.distro }}"
+ - name: Test extension with Bundled PHP
+ run: |
+ docker compose run --rm shell pskel test
+ - name: Test extension with PHP Debug Build
+ if: matrix.platform == 'linux/amd64'
+ run: |
+ docker compose run --rm shell pskel test debug
+ - name: Test extension with Valgrind
+ if: matrix.platform == 'linux/amd64'
+ run: |
+ docker compose run --rm shell pskel test valgrind
+ - name: Test extension with LLVM Sanitizer (MemorySanitizer)
+ if: matrix.platform == 'linux/amd64' && matrix.distro != 'alpine'
+ run: |
+ docker compose run --rm shell pskel test msan
+ - name: Test extension with LLVM Sanitizer (AddressSanitizer)
+ if: matrix.platform == 'linux/amd64' && matrix.distro != 'alpine'
+ run: |
+ docker compose run --rm shell pskel test asan
+ - name: Test extension with LLVM Sanitizer (UndefinedBehaviorSanitizer)
+ if: matrix.platform == 'linux/amd64' && matrix.distro != 'alpine'
+ run: |
+ docker compose run --rm shell pskel test ubsan
+ # Windows:
+ # runs-on: windows-2022
+ # defaults:
+ # run:
+ # shell: cmd
+ # strategy:
+ # matrix:
+ # platform: ["x64"]
+ # version: ["8.1", "8.2", "8.3"]
+ # ts: ["nts", "ts"]
+ # steps:
+ # - name: Checkout
+ # uses: actions/checkout@v4
+ # - name: Setup PHP
+ # id: setup-php
+ # uses: php/setup-php-sdk@v0.8
+ # with:
+ # platform: ${{ matrix.platform }}
+ # version: ${{ matrix.version }}
+ # ts: ${{ matrix.ts }}
+ # - name: Enable developer command prompt
+ # uses: ilammy/msvc-dev-cmd@v1
+ # with:
+ # platform: ${{ matrix.platform }}
+ # toolset: ${{ steps.setup-php.outputs.toolset }}
+ # - name: phpize
+ # working-directory: ext
+ # run: phpize
+ # - name: configure
+ # working-directory: ext
+ # run: configure --enable-SKELETON_NAME --with-prefix=${{ steps.setup-php.outputs.prefix }}
+ # - name: make
+ # working-directory: ext
+ # run: nmake
+ # - name: test
+ # working-directory: ext
+ # run: nmake test TESTS="--show-diff tests"
diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml
new file mode 100644
index 0000000..89ca889
--- /dev/null
+++ b/.github/workflows/coverage.yaml
@@ -0,0 +1,65 @@
+name: Coverage
+permissions:
+ contents: write
+ pull-requests: write
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ types:
+ - opened
+ - synchronize
+ - reopened
+jobs:
+ Linux:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ version: ['8.3']
+ type: ['cli', 'zts']
+ distro: ['bookworm']
+ outputs:
+ matrix: ${{ toJson(matrix) }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+ - name: Setup buildx
+ uses: docker/setup-buildx-action@v3
+ - name: Build container
+ run: |
+ docker compose build --pull --no-cache --build-arg PLATFORM="linux/amd64" --build-arg IMAGE="php" --build-arg TAG="${{ matrix.version }}-${{ matrix.type }}-${{ matrix.distro }}"
+ - name: Test with gcov
+ run: |
+ docker compose run -v "$(pwd)/ext:/ext" --rm shell pskel coverage
+ - name: Upload coverage to artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-${{ matrix.version }}-${{ matrix.type }}-${{ matrix.distro }}
+ path: ${{ github.workspace }}/ext/lcov.info
+ Coverage:
+ needs: [Linux]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Download coverage artifacts
+ uses: actions/download-artifact@v4
+ - name: Merge coverages
+ run: |
+ sudo apt-get install -y "lcov"
+ LCOV_FILES="$(find . -name "lcov.info")"
+ CMD="$(which "lcov")"
+ for LCOV_FILE in ${LCOV_FILES}; do
+ CMD+=" -a ${LCOV_FILE}"
+ done
+ CMD+=" -o lcov.info"
+ echo "Merging coverages: ${LCOV_FILES}"
+ ${CMD}
+ - name: Report coverage
+ uses: k1LoW/octocov-action@v1
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ config: .github/octocov.yml
diff --git a/.gitignore b/.gitignore
index d7cc116..09b9410 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,45 +1,4 @@
-*.lo
-*.la
-*.dep
-.libs
-acinclude.m4
-aclocal.m4
-autom4te.cache
-build
-config.guess
-config.h
-config.h.in
-config.h.in~
-config.log
-config.nice
-config.status
-config.sub
-configure
-configure~
-configure.ac
-configure.in
-include
-install-sh
-libtool
-ltmain.sh
-Makefile
-Makefile.fragments
-Makefile.global
-Makefile.objects
-missing
-mkinstalldirs
-modules
-php_test_results_*.txt
-phpt.*
-run-test-info.php
-run-tests.php
-tests/**/*.diff
-tests/**/*.out
-tests/**/*.php
-tests/**/*.exp
-tests/**/*.log
-tests/**/*.sh
-tests/**/*.db
-tests/**/*.mem
-tmp-php.ini
-xpass-*.tgz
+/ext/*.dep
+/ext/*~
+*.DS_Store
+lcov.info
diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
new file mode 100644
index 0000000..a65a6c5
--- /dev/null
+++ b/.vscode/c_cpp_properties.json
@@ -0,0 +1,21 @@
+{
+ "configurations": [
+ {
+ "name": "Linux",
+ "includePath": [
+ "${workspaceFolder}/**",
+ "/usr/local/include/php",
+ "/usr/local/include/php/TSRM",
+ "/usr/local/include/php/Zend",
+ "/usr/local/include/php/ext",
+ "/usr/local/include/php/include",
+ "/usr/local/include/php/main",
+ "/usr/local/include/php/sapi"
+ ],
+ "defines": [],
+ "compilerPath": "/usr/bin/gcc",
+ "cStandard": "c99"
+ }
+ ],
+ "version": 4
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..968d4dc
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,29 @@
+{
+ "files.associations": {
+ "*.phpt": "php",
+ "*.c": "c",
+ "*.h": "c"
+ },
+ "files.exclude": {
+ "**/.git": true,
+ "**/.DS_Store": true,
+ "**/Thumbs.db": true,
+ "**/.libs": true,
+ "**/*.cache": true,
+ "ext/**/*.dep": true,
+ "ext/**/*.la": true,
+ "ext/**/*.lo": true,
+ "ext/build": true,
+ "ext/config.h": true,
+ "ext/config.h.*": true,
+ "ext/config.nice": true,
+ "ext/config.status": true,
+ "ext/configure.ac": true,
+ "ext/configure~": true,
+ "ext/libtool": true,
+ "ext/Makefile.*": true,
+ },
+ "markiscodecoverage.enableDecorations": true,
+ "markiscodecoverage.enableOnStartup": true,
+ "markiscodecoverage.searchCriteria": "ext/lcov.info"
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..72abe24
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,52 @@
+ARG PLATFORM=${BUILDPLATFORM:-linux/amd64}
+ARG IMAGE=php
+ARG TAG=8.3-cli-bookworm
+
+FROM --platform=${PLATFORM} ${IMAGE}:${TAG}
+
+ENV USE_ZEND_ALLOC=0
+ENV USE_TRACKED_ALLOC=1
+ENV ZEND_DONT_UNLOAD_MODULES=1
+ENV LC_ALL="C"
+
+RUN docker-php-source extract \
+ && if test -f "/etc/debian_version"; then \
+ echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm main" > "/etc/apt/sources.list.d/llvm.list" \
+ && echo "deb-src http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm main" >> "/etc/apt/sources.list.d/llvm.list" \
+ && curl -fsSL "https://apt.llvm.org/llvm-snapshot.gpg.key" -o "/etc/apt/trusted.gpg.d/apt.llvm.org.asc" \
+ && apt-get update \
+ && DEBIAN_FRONTEND="noninteractive" apt-get install -y "bison" "re2c" "zlib1g-dev" "libsqlite3-dev" "libxml2-dev" \
+ "autoconf" "pkg-config" "make" "gcc" "valgrind" "rsync" "git" "ssh" \
+ "clang-20" \
+ "lcov" "gzip" \
+ "vim" \
+ && update-alternatives --install "/usr/bin/clang" clang "/usr/bin/clang-20" 100 \
+ && update-alternatives --install "/usr/bin/clang++" clang++ "/usr/bin/clang++-20" 100; \
+ else \
+ apk add --no-cache "bison" "zlib-dev" "sqlite-dev" "libxml2-dev" \
+ "autoconf" "pkgconfig" "make" "gcc" "g++" "valgrind" "valgrind-dev" \
+ "musl-dev" "rsync" "git" "openssh" \
+ "patch" "lcov" "gzip" \
+ "vim"; \
+ fi
+
+COPY ./pskel.sh /usr/local/bin/pskel
+COPY ./patches /patches
+COPY ./ext /ext
+
+# ----
+
+ARG CFLAGS=""
+
+RUN if test -f "/etc/debian_version"; then \
+ apt-get update && apt-get install -y "libtool"; \
+ else \
+ apk add --no-cache "libtool" "automake" "linux-headers"; \
+ fi \
+ && git clone --depth=1 --branch="v4.4.36" "https://github.com/besser82/libxcrypt.git" "/libxcrypt" \
+ && cd "/libxcrypt" \
+ && ./autogen.sh \
+ && CFLAGS="${CFLAGS}" ./configure --enable-hashes="all" \
+ && make -j"$(nproc)" \
+ && make install \
+ && cd -
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..4e3de7a
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,22 @@
+services:
+ shell:
+ build:
+ context: ./
+ dockerfile: ./Dockerfile
+ cap_add:
+ - SYS_ADMIN
+ security_opt:
+ - seccomp:unconfined
+ privileged: true
+ tty: true
+ # depends_on:
+ # - mysql
+ # mysql:
+ # image: mysql:8.0
+ # environment:
+ # MYSQL_ROOT_PASSWORD: testing
+ # MYSQL_DATABASE: testing
+ # MYSQL_USER: testing
+ # MYSQL_PASSWORD: testing
+ # volumes:
+ # - ./docker/mysql/etc/mysql/conf.d/my.cnf:/etc/mysql/conf.d/my.cnf
diff --git a/ext/.gitignore b/ext/.gitignore
new file mode 100644
index 0000000..d7cc116
--- /dev/null
+++ b/ext/.gitignore
@@ -0,0 +1,45 @@
+*.lo
+*.la
+*.dep
+.libs
+acinclude.m4
+aclocal.m4
+autom4te.cache
+build
+config.guess
+config.h
+config.h.in
+config.h.in~
+config.log
+config.nice
+config.status
+config.sub
+configure
+configure~
+configure.ac
+configure.in
+include
+install-sh
+libtool
+ltmain.sh
+Makefile
+Makefile.fragments
+Makefile.global
+Makefile.objects
+missing
+mkinstalldirs
+modules
+php_test_results_*.txt
+phpt.*
+run-test-info.php
+run-tests.php
+tests/**/*.diff
+tests/**/*.out
+tests/**/*.php
+tests/**/*.exp
+tests/**/*.log
+tests/**/*.sh
+tests/**/*.db
+tests/**/*.mem
+tmp-php.ini
+xpass-*.tgz
diff --git a/bench.php b/ext/bench.php
similarity index 100%
rename from bench.php
rename to ext/bench.php
diff --git a/config.m4 b/ext/config.m4
similarity index 100%
rename from config.m4
rename to ext/config.m4
diff --git a/php_xpass.h b/ext/php_xpass.h
similarity index 100%
rename from php_xpass.h
rename to ext/php_xpass.h
diff --git a/tests/sha512.phpt b/ext/tests/sha512.phpt
similarity index 100%
rename from tests/sha512.phpt
rename to ext/tests/sha512.phpt
diff --git a/tests/xpass.phpt b/ext/tests/xpass.phpt
similarity index 100%
rename from tests/xpass.phpt
rename to ext/tests/xpass.phpt
diff --git a/tests/yescrypt.phpt b/ext/tests/yescrypt.phpt
similarity index 100%
rename from tests/yescrypt.phpt
rename to ext/tests/yescrypt.phpt
diff --git a/xpass.c b/ext/xpass.c
similarity index 100%
rename from xpass.c
rename to ext/xpass.c
diff --git a/package.xml b/package.xml
index a4f9d1e..ab4c630 100644
--- a/package.xml
+++ b/package.xml
@@ -32,18 +32,20 @@ distributions, using extended crypt library (libxcrypt):
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
diff --git a/patches/ltmain.sh.patch b/patches/ltmain.sh.patch
new file mode 100644
index 0000000..70d0822
--- /dev/null
+++ b/patches/ltmain.sh.patch
@@ -0,0 +1,11 @@
+--- ext/build/ltmain.sh 2024-08-20 13:47:39.489351765 +0000
++++ ext/build/ltmain.sh 2024-08-20 13:51:02.825496572 +0000
+@@ -3467,7 +3467,7 @@ EOF
+ tempremovelist=`$echo "$output_objdir/*"`
+ for p in $tempremovelist; do
+ case $p in
+- *.$objext)
++ *.$objext | *.gcno)
+ ;;
+ $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/${libname}${release}.*)
+ if test "X$precious_files_regex" != "X"; then
diff --git a/pskel.sh b/pskel.sh
new file mode 100755
index 0000000..5621c27
--- /dev/null
+++ b/pskel.sh
@@ -0,0 +1,213 @@
+#!/bin/sh
+
+set -e
+
+get_ext_dir() {
+ PSKEL_EXT_DIR="/ext"
+
+ if [ -d "${CODESPACE_VSCODE_FOLDER}" ]; then
+ echo "[Pskel] GitHub Codespace workspace detected, using \"${CODESPACE_VSCODE_FOLDER}/ext\"." >&2
+ PSKEL_EXT_DIR="${CODESPACE_VSCODE_FOLDER}/ext"
+ elif [ -d "/workspaces/pskel/ext" ]; then
+ echo "[Pskel] Development Containers workspace detected, using \"/workspaces/pskel/ext\"." >&2
+ PSKEL_EXT_DIR="/workspaces/pskel/ext"
+ elif [ -f "/ext/.gitkeep" ] && [ "$(cat "/ext/.gitkeep")" = "pskel_uninitialized" ] && [ "${1}" != "--no-init" ]; then
+ echo "[Pskel] Uninitialized project detected, initializing default skeleton." >&2
+ cmd_init "skeleton" >&2
+ fi
+
+ echo "${PSKEL_EXT_DIR}"
+}
+
+cmd_usage() {
+ cat << EOF
+Usage: ${0} [task] ...
+
+Available commands:
+ init create new extension
+ test test extension
+ build build PHP runtime
+ coverage generate code coverage
+EOF
+}
+
+cmd_init() {
+ case "${1}" in
+ -h|--help)
+ cat << EOF
+Usage: ${0} init [extension_name] [ext_skel.php options...]
+EOF
+ return 0
+ ;;
+ "")
+ echo "Error: Extension name is required." >&2
+ return 1
+ ;;
+ esac
+
+ PSKEL_EXT_DIR="$(get_ext_dir --no-init)"
+ /usr/local/bin/php "/usr/src/php/ext/ext_skel.php" --ext "${1}" --dir "/tmp" "${@}"
+ rm "${PSKEL_EXT_DIR}/.gitkeep"
+ rsync -av "/tmp/${1}/" "${PSKEL_EXT_DIR}/"
+ rm -rf "/tmp/${1}"
+}
+
+cmd_test() {
+ case "${1}" in
+ -h|--help)
+ cat << EOF
+Usage: ${0} test [test_type|php_binary_name]
+Environment variables:
+ CFLAGS, CPPFLAGS: Compilation flags
+ TEST_PHP_ARGS: Test flags
+EOF
+ return 0
+ ;;
+ debug|gcov|valgrind)
+ CC="$(command -v "gcc")"
+ CXX="$(command -v "g++")"
+ case "${1}" in
+ debug) build_php_if_not_exists "debug";;
+ gcov)
+ CONFIGURE_OPTS="--enable-gcov"
+ build_php_if_not_exists "gcov"
+ CFLAGS="${CFLAGS} --coverage"
+ ;;
+ valgrind)
+ CONFIGURE_OPTS="--with-valgrind"
+ build_php_if_not_exists "valgrind"
+ TEST_PHP_ARGS="${TEST_PHP_ARGS} -m"
+ ;;
+ esac
+ CMD="$(basename "${CC}")-${1}-php"
+ ;;
+ msan|asan|ubsan)
+ CC="$(command -v "clang")"
+ CXX="$(command -v "clang++")"
+ case "${1}" in
+ msan)
+ CONFIGURE_OPTS="--enable-memory-sanitizer"
+ build_php_if_not_exists "msan"
+ CFLAGS="${CFLAGS} -fsanitize=memory"
+ LDFLAGS="${LDFLAGS} -fsanitize=memory"
+ ;;
+ asan)
+ CONFIGURE_OPTS="--enable-address-sanitizer"
+ build_php_if_not_exists "asan"
+ CFLAGS="${CFLAGS} -fsanitize=address"
+ LDFLAGS="${LDFLAGS} -fsanitize=address"
+ ;;
+ ubsan)
+ CONFIGURE_OPTS="--enable-undefined-sanitizer"
+ build_php_if_not_exists "ubsan"
+ CFLAGS="${CFLAGS} -fsanitize=undefined"
+ LDFLAGS="${LDFLAGS} -fsanitize=undefined"
+ ;;
+ esac
+ CMD="$(basename "${CC}")-${1}-php"
+ ;;
+ "")
+ CMD="php"
+ ;;
+ *)
+ CMD="${1}"
+ ;;
+ esac
+
+ for BIN in "${CMD}" "${CMD}ize" "${CMD}-config"; do
+ if ! command -v "${BIN}" >/dev/null 2>&1; then
+ echo "Error: Invalid argument '${CMD}', executable file not found" >&2
+ exit 1
+ fi
+ done
+
+ PSKEL_EXT_DIR="$(get_ext_dir)"
+
+ cd "${PSKEL_EXT_DIR}"
+ "${CMD}ize"
+ if [ "$("${CMD}" -r "echo PHP_VERSION_ID;")" -lt "80400" ]; then
+ patch "./build/ltmain.sh" "./../patches/ltmain.sh.patch"
+ echo "[Pskel] ltmain.sh patched" >&2
+ fi
+ CC="${CC}" CXX="${CXX}" CFLAGS="${CFLAGS}" CPPFLAGS="${CPPFLAGS}" LDFLAGS="${LDFLAGS}" ./configure --with-php-config="$(command -v "${CMD}-config")"
+ make clean
+ make -j"$(nproc)"
+ TEST_PHP_ARGS="${TEST_PHP_ARGS} --show-diff -q" make test
+ cd -
+}
+
+build_php_if_not_exists() {
+ if ! command -v "$(basename "${CC}")-${1}-php" >/dev/null 2>&1; then
+ CC="${CC}" \
+ CXX="${CXX}" \
+ CFLAGS="-DZEND_TRACK_ARENA_ALLOC" \
+ CPPFLAGS="${CFLAGS}" \
+ LDFLAGS="${LDFLAGS}" \
+ CONFIGURE_OPTS="${CONFIGURE_OPTS} --enable-debug $(php -r "echo PHP_ZTS === 1 ? '--enable-zts' : '';") --enable-option-checking=fatal --disable-phpdbg --disable-cgi --disable-fpm --enable-cli --without-pcre-jit --disable-opcache-jit --disable-zend-max-execution-timers" \
+ cmd_build "$(basename "${CC}")-${1}"
+ fi
+}
+
+cmd_build() {
+ case "${1}" in
+ -h|--help)
+ cat << EOF
+Usage: ${0} build [php_binary_prefix]
+Environment variables:
+ CFLAGS, CPPFLAGS: Compilation flags
+ CONFIGURE_OPTS: ./configure options
+EOF
+ return 0
+ ;;
+ ?*)
+ CONFIGURE_OPTS="--program-prefix=${1}- --includedir=/usr/local/include/${1}-php ${CONFIGURE_OPTS}"
+ ;;
+ esac
+
+ cd "/usr/src/php"
+ ./buildconf --force
+ ./configure ${CONFIGURE_OPTS}
+ make clean
+ make -j"$(nproc)"
+ make install
+ make clean
+ cd -
+}
+
+cmd_coverage() {
+ case "${1}" in
+ -h|--help)
+ cat << EOF
+Usage: ${0} coverage
+Environment variables:
+ LCOV_OPTS: lcov capture options
+EOF
+ return 0
+ ;;
+ esac
+
+ cmd_test "gcov"
+
+ PSKEL_EXT_DIR="$(get_ext_dir)"
+
+ lcov --capture --directory "${PSKEL_EXT_DIR}" ${LCOV_OPTS} --exclude "/usr/local/include/*" --output-file "${PSKEL_EXT_DIR}/lcov.info"
+ lcov --list "${PSKEL_EXT_DIR}/lcov.info"
+}
+
+if [ $# -eq 0 ]; then
+ cmd_usage
+ exit 1
+fi
+
+case "${1}" in
+ help) shift; cmd_usage;;
+ init) shift; cmd_init "${@}";;
+ test) shift; cmd_test "${@}";;
+ build) shift; cmd_build "${@}";;
+ coverage) shift; cmd_coverage "${@}";;
+ *)
+ echo "${0} error: invalid command: '${1}'" >&2
+ cmd_usage
+ exit 1
+ ;;
+esac