From ff4a3866f4703e2a176d8e17fff6ca8c44108b52 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 17 Feb 2025 22:14:00 +0100 Subject: [PATCH 01/18] Add docker image --- .github/workflows/build-phar.yml | 10 +++++++++ bin/docker/Dockerfile | 37 ++++++++++++++++++++++++++++++++ bin/docker/entrypoint.sh | 35 ++++++++++++++++++++++++++++++ bin/docker/php.ini | 36 +++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 bin/docker/Dockerfile create mode 100644 bin/docker/entrypoint.sh create mode 100644 bin/docker/php.ini diff --git a/.github/workflows/build-phar.yml b/.github/workflows/build-phar.yml index 379d7aac759..21df5828d8b 100644 --- a/.github/workflows/build-phar.yml +++ b/.github/workflows/build-phar.yml @@ -31,6 +31,7 @@ jobs: build-phar: permissions: contents: write # for release + packages: write needs: pre_job if: ${{ needs.pre_job.outputs.should_skip != 'true' }} runs-on: ubuntu-latest @@ -80,6 +81,15 @@ jobs: GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: Upload docker image + run: | + docker build . -t ghcr.io/vimeo/psalm:${{ github.ref_name }} --build-arg PSALM_REV=${{ github.ref_name }} -f bin/docker/Dockerfile + + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + docker tag ghcr.io/vimeo/psalm:${{ github.ref_name }} ghcr.io/vimeo/psalm:latest + docker push ghcr.io/vimeo/psalm:${{ github.ref_name }} ghcr.io/vimeo/psalm:latest + - name: Upload release assets if: ${{ github.event_name == 'release' }} uses: svenstaro/upload-release-action@v2 diff --git a/bin/docker/Dockerfile b/bin/docker/Dockerfile new file mode 100644 index 00000000000..24d2da627b5 --- /dev/null +++ b/bin/docker/Dockerfile @@ -0,0 +1,37 @@ +FROM php:8.4-cli + +# This line invalidates cache when master branch changes +ADD https://github.com/vimeo/psalm/commits/master.atom /dev/null + +ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ + +RUN sed 's/-O2/-O3/g' -i /usr/local/bin/install-php-extensions && \ + chmod +x /usr/local/bin/install-php-extensions && \ + install-php-extensions pcntl mbstring xml dom igbinary opcache && \ + rm /usr/local/bin/install-php-extensions + +RUN apt-get update && apt-get -y --no-install-recommends install git unzip && apt-get clean && rm -rf /var/lib/apt/lists/* + +ADD bin/docker/php.ini /usr/local/lib/php.ini + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +ARG PSALM_REV=dev-master +RUN COMPOSER_ALLOW_SUPERUSER=1 \ + COMPOSER_HOME="/composer" \ + composer global require vimeo/psalm:${PSALM_REV} --prefer-dist --no-progress --dev && \ + rm /usr/bin/composer /usr/local/bin/phpdbg /usr/local/bin/php-cgi /usr/local/lib/libphp.so + +ENV PATH /composer/vendor/bin:${PATH} + +# Add entrypoint script + +COPY ./bin/docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Squash into single layer +FROM scratch +COPY --from=0 / / + +WORKDIR "/app" +#ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/bin/docker/entrypoint.sh b/bin/docker/entrypoint.sh new file mode 100644 index 00000000000..c61cfaf5ea5 --- /dev/null +++ b/bin/docker/entrypoint.sh @@ -0,0 +1,35 @@ +#!/bin/sh -l +set -e + +TAINT_ANALYSIS="" +if [ "$INPUT_SECURITY_ANALYSIS" = "true" ]; then + TAINT_ANALYSIS="--taint-analysis" +fi + +REPORT="" +if [ ! -z "$INPUT_REPORT_FILE" ]; then + REPORT="--report=$INPUT_REPORT_FILE" +fi + +SHOW_INFO="" +if [ "$INPUT_SHOW_INFO" = "true" ]; then + SHOW_INFO="--show-info=true" +fi + +PHP_VERSION="" +if [ -n "$INPUT_PHP_VERSION" ]; then + PHP_VERSION="--php-version=$INPUT_PHP_VERSION" +fi + +if [ -n "$INPUT_RELATIVE_DIR" ] +then + if [ -d "$INPUT_RELATIVE_DIR" ]; then + echo "changing directory into $INPUT_RELATIVE_DIR" + cd "$INPUT_RELATIVE_DIR" + else + echo "given relative_dir not existing" + exit 1 + fi +fi + +/composer/vendor/bin/psalm --force-jit --no-cache $TAINT_ANALYSIS $REPORT $SHOW_INFO $PHP_VERSION $* diff --git a/bin/docker/php.ini b/bin/docker/php.ini new file mode 100644 index 00000000000..21b5fce6ffe --- /dev/null +++ b/bin/docker/php.ini @@ -0,0 +1,36 @@ +memory_limit = -1 +zend.assertions = -1 +display_errors = On +display_startup_errors = On + +ffi.enable=true + +zend_extension=opcache + +[opcache] +opcache.memory_consumption=512M +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=function +opcache.validate_timestamps=0 +opcache.jit_buffer_size=128M +opcache.file_update_protection=0 +opcache.max_accelerated_files=1000000 +opcache.interned_strings_buffer=64 + +opcache.jit_prof_threshold=0.000000001 +opcache.jit_max_root_traces= 100000 +opcache.jit_max_side_traces= 100000 +opcache.jit_max_exit_counters=100000 +opcache.jit_hot_loop=1 +opcache.jit_hot_func=1 +opcache.jit_hot_return=1 +opcache.jit_hot_side_exit=1 +opcache.optimization_level=0x7FFEBFFF +opcache.log_verbosity_level=0 +opcache.save_comments=1 + +opcache.jit_blacklist_root_trace=255 +opcache.jit_blacklist_side_trace=255 + +opcache.protect_memory=1 From b7bfe47dc783e10e95784f0bb4166376bb54e8dd Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 17 Feb 2025 22:19:12 +0100 Subject: [PATCH 02/18] Add some comments --- bin/docker/Dockerfile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/docker/Dockerfile b/bin/docker/Dockerfile index 24d2da627b5..d43e7e7c1b2 100644 --- a/bin/docker/Dockerfile +++ b/bin/docker/Dockerfile @@ -1,3 +1,9 @@ +# Not alpine, due to possible performance issues of MUSL malloc. +# +# In theory this should not be relevant because PHP uses its own allocator, +# but some one-time initialization logic inside PHP bypasses it, +# which means system malloc *is* used more often especially in cases like these. + FROM php:8.4-cli # This line invalidates cache when master branch changes @@ -34,4 +40,4 @@ FROM scratch COPY --from=0 / / WORKDIR "/app" -#ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file From a7f3f282979bd3b11444a8810ba76754d0e71276 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 09:01:53 +0100 Subject: [PATCH 03/18] Build from scratch --- bin/docker/Dockerfile | 229 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 220 insertions(+), 9 deletions(-) diff --git a/bin/docker/Dockerfile b/bin/docker/Dockerfile index d43e7e7c1b2..11eb394c83f 100644 --- a/bin/docker/Dockerfile +++ b/bin/docker/Dockerfile @@ -3,8 +3,216 @@ # In theory this should not be relevant because PHP uses its own allocator, # but some one-time initialization logic inside PHP bypasses it, # which means system malloc *is* used more often especially in cases like these. +# +# Copied from autogenerated dockerfile in https://github.com/docker-library/php/tree/master/8.4/bookworm/cli. +# Need to compile PHP from scratch in order to apply deepbind.patch and use jemalloc. + +FROM debian:bookworm-slim + +# prevent Debian's PHP packages from being installed +# https://github.com/docker-library/php/pull/542 +RUN set -eux; \ + { \ + echo 'Package: php*'; \ + echo 'Pin: release *'; \ + echo 'Pin-Priority: -1'; \ + } > /etc/apt/preferences.d/no-debian-php + +# dependencies required for running "phpize" +# (see persistent deps below) +ENV PHPIZE_DEPS \ + autoconf \ + dpkg-dev \ + file \ + g++ \ + gcc \ + libc-dev \ + make \ + pkg-config \ + re2c + +# persistent / runtime deps +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + $PHPIZE_DEPS \ + ca-certificates \ + curl \ + xz-utils \ + ; \ + rm -rf /var/lib/apt/lists/* + +ENV PHP_INI_DIR /usr/local/etc/php +RUN set -eux; \ + mkdir -p "$PHP_INI_DIR/conf.d"; \ +# allow running as an arbitrary user (https://github.com/docker-library/php/issues/743) + [ ! -d /var/www/html ]; \ + mkdir -p /var/www/html; \ + chown www-data:www-data /var/www/html; \ + chmod 1777 /var/www/html + +# Apply stack smash protection to functions using local buffers and alloca() +# Make PHP's main executable position-independent (improves ASLR security mechanism, and has no performance impact on x86_64) +# Enable optimization (-O2) +# Enable linker optimization (this sorts the hash buckets to improve cache locality, and is non-default) +# https://github.com/docker-library/php/issues/272 +# -D_LARGEFILE_SOURCE and -D_FILE_OFFSET_BITS=64 (https://www.php.net/manual/en/intro.filesystem.php) +ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O3 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" +ENV PHP_CPPFLAGS="$PHP_CFLAGS" +ENV PHP_LDFLAGS="-Wl,-O1 -pie" + +ENV GPG_KEYS AFD8691FDAEDF03BDF6E460563F15A9B715376CA 9D7F99A0CB8F05C8A6958D6256A97AF7600A39A6 0616E93D95AF471243E26761770426E17EBBB3DD + +ENV PHP_VERSION 8.4.4 +ENV PHP_URL="https://www.php.net/distributions/php-8.4.4.tar.xz" PHP_ASC_URL="https://www.php.net/distributions/php-8.4.4.tar.xz.asc" +ENV PHP_SHA256="05a6c9a2cc894dd8be719ecab221b311886d5e0c02cb6fac648dd9b3459681ac" -FROM php:8.4-cli +ADD bin/docker/deepbind.patch / + +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends gnupg; \ + rm -rf /var/lib/apt/lists/*; \ + \ + mkdir -p /usr/src; \ + cd /usr/src; \ + \ + curl -fsSL -o php.tar.xz "$PHP_URL"; \ + \ + if [ -n "$PHP_SHA256" ]; then \ + echo "$PHP_SHA256 *php.tar.xz" | sha256sum -c -; \ + fi; \ + \ + if [ -n "$PHP_ASC_URL" ]; then \ + curl -fsSL -o php.tar.xz.asc "$PHP_ASC_URL"; \ + export GNUPGHOME="$(mktemp -d)"; \ + for key in $GPG_KEYS; do \ + gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key"; \ + done; \ + gpg --batch --verify php.tar.xz.asc php.tar.xz; \ + gpgconf --kill all; \ + rm -rf "$GNUPGHOME"; \ + fi; \ + \ + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark > /dev/null; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false + +COPY bin/docker/docker-php-source /usr/local/bin/ + +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + libargon2-dev \ + libcurl4-openssl-dev \ + libonig-dev \ + libreadline-dev \ + libsodium-dev \ + libsqlite3-dev \ + libssl-dev \ + libxml2-dev \ + zlib1g-dev \ + libcapstone-dev \ + ; \ + \ + export \ + CFLAGS="$PHP_CFLAGS" \ + CPPFLAGS="$PHP_CPPFLAGS" \ + LDFLAGS="$PHP_LDFLAGS" \ +# https://github.com/php/php-src/blob/d6299206dd828382753453befd1b915491b741c6/configure.ac#L1496-L1511 + PHP_BUILD_PROVIDER='https://github.com/docker-library/php' \ + PHP_UNAME='Linux - Docker' \ + ; \ + docker-php-source extract; \ + cd /usr/src/php; \ + patch -p1 < /deepbind.patch; \ + gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ + debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ +# https://bugs.php.net/bug.php?id=74125 + if [ ! -d /usr/include/curl ]; then \ + ln -sT "/usr/include/$debMultiarch/curl" /usr/local/include/curl; \ + fi; \ + ./configure \ + --build="$gnuArch" \ + --with-config-file-path="$PHP_INI_DIR" \ + --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \ + \ +# make sure invalid --configure-flags are fatal errors instead of just warnings + --enable-option-checking=fatal \ + \ +# https://github.com/docker-library/php/issues/439 + --with-mhash \ + \ +# https://github.com/docker-library/php/issues/822 + --with-pic \ + \ +# --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) + --enable-mbstring \ +# --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself) + --enable-mysqlnd \ +# https://wiki.php.net/rfc/argon2_password_hash + --with-password-argon2 \ +# https://wiki.php.net/rfc/libsodium + --with-sodium=shared \ +# always build against system sqlite3 (https://github.com/php/php-src/commit/6083a387a81dbbd66d6316a3a12a63f06d5f7109) + --with-pdo-sqlite=/usr \ + --with-sqlite3=/usr \ + \ + --with-curl \ + --with-iconv \ + --with-openssl \ + --with-readline \ + --with-zlib \ + \ +# in PHP 7.4+, the pecl/pear installers are officially deprecated (requiring an explicit "--with-pear") + --with-pear \ + \ + --disable-cgi \ + --disable-phpdbg \ + --with-capstone \ + --with-libdir="lib/$debMultiarch" \ + \ + ; \ + make -j "$(nproc)"; \ + find -type f -name '*.a' -delete; \ + make install; \ + make clean; \ + \ +# https://github.com/docker-library/php/issues/692 (copy default example "php.ini" files somewhere easily discoverable) + cp -v php.ini-* "$PHP_INI_DIR/"; \ + \ + cd /; \ + docker-php-source delete; rm deepbind.patch; \ + \ +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \ + find /usr/local -type f -executable -exec ldd '{}' ';' \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); printf "*%s\n", so }' \ + | sort -u \ + | xargs -r dpkg-query --search \ + | cut -d: -f1 \ + | sort -u \ + | xargs -r apt-mark manual \ + ; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/*; \ + \ +# update pecl channel definitions https://github.com/docker-library/php/issues/443 + pecl update-channels; \ + rm -rf /tmp/pear ~/.pearrc; \ + \ +# smoke test + php --version + +COPY bin/docker/docker-php-ext-* /usr/local/bin/ + +# sodium was built as a shared module (so that it can be replaced later if so desired), so let's enable it too (https://github.com/docker-library/php/issues/598) +RUN docker-php-ext-enable sodium # This line invalidates cache when master branch changes ADD https://github.com/vimeo/psalm/commits/master.atom /dev/null @@ -16,7 +224,7 @@ RUN sed 's/-O2/-O3/g' -i /usr/local/bin/install-php-extensions && \ install-php-extensions pcntl mbstring xml dom igbinary opcache && \ rm /usr/local/bin/install-php-extensions -RUN apt-get update && apt-get -y --no-install-recommends install git unzip && apt-get clean && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get -y --no-install-recommends install git unzip libjemalloc2 && apt-get clean && rm -rf /var/lib/apt/lists/* ADD bin/docker/php.ini /usr/local/lib/php.ini @@ -26,18 +234,21 @@ ARG PSALM_REV=dev-master RUN COMPOSER_ALLOW_SUPERUSER=1 \ COMPOSER_HOME="/composer" \ composer global require vimeo/psalm:${PSALM_REV} --prefer-dist --no-progress --dev && \ - rm /usr/bin/composer /usr/local/bin/phpdbg /usr/local/bin/php-cgi /usr/local/lib/libphp.so - -ENV PATH /composer/vendor/bin:${PATH} + rm /usr/bin/composer # Add entrypoint script COPY ./bin/docker/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -# Squash into single layer -FROM scratch -COPY --from=0 / / +RUN ln -s "$(dpkg -L libjemalloc2 | grep libjemalloc.so | head -1)" /usr/lib/libjemalloc.so + +ENV PATH=/composer/vendor/bin:${PATH} + +ENV USE_ZEND_ALLOC=0 +ENV LD_PRELOAD=/usr/lib/libjemalloc.so + +RUN php -v WORKDIR "/app" -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file +CMD ["/entrypoint.sh"] \ No newline at end of file From 25e1bebeefab6a397e3fafd0fe0c1d2fc90cb2c8 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 09:22:08 +0100 Subject: [PATCH 04/18] Wait for packagist --- .github/workflows/build-phar.yml | 2 + bin/ci/waitPackagist.php | 20 ++++ bin/docker/deepbind.patch | 51 ++++++++++ bin/docker/docker-php-ext-configure | 69 ++++++++++++++ bin/docker/docker-php-ext-enable | 121 +++++++++++++++++++++++ bin/docker/docker-php-ext-install | 143 ++++++++++++++++++++++++++++ bin/docker/docker-php-source | 34 +++++++ 7 files changed, 440 insertions(+) create mode 100755 bin/ci/waitPackagist.php create mode 100644 bin/docker/deepbind.patch create mode 100755 bin/docker/docker-php-ext-configure create mode 100755 bin/docker/docker-php-ext-enable create mode 100755 bin/docker/docker-php-ext-install create mode 100755 bin/docker/docker-php-source diff --git a/.github/workflows/build-phar.yml b/.github/workflows/build-phar.yml index 21df5828d8b..1b0e681fe50 100644 --- a/.github/workflows/build-phar.yml +++ b/.github/workflows/build-phar.yml @@ -83,6 +83,8 @@ jobs: - name: Upload docker image run: | + bin/ci/waitPackagist.php + docker build . -t ghcr.io/vimeo/psalm:${{ github.ref_name }} --build-arg PSALM_REV=${{ github.ref_name }} -f bin/docker/Dockerfile echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin diff --git a/bin/ci/waitPackagist.php b/bin/ci/waitPackagist.php new file mode 100755 index 00000000000..694787f6026 --- /dev/null +++ b/bin/ci/waitPackagist.php @@ -0,0 +1,20 @@ +#!/usr/bin/env php + +Date: Wed, 13 Nov 2024 12:24:29 +0000 +Subject: [PATCH] Add --enable-rtld-deepbind configure flag + +--- + Zend/zend_portability.h | 2 +- + configure.ac | 17 +++++++++++++++++ + 2 files changed, 18 insertions(+), 1 deletion(-) + +diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h +index 9ab46f9b32cfe..1efc15fb6386c 100644 +--- a/Zend/zend_portability.h ++++ b/Zend/zend_portability.h +@@ -164,7 +164,7 @@ + + # if defined(RTLD_GROUP) && defined(RTLD_WORLD) && defined(RTLD_PARENT) + # define DL_LOAD(libname) dlopen(libname, PHP_RTLD_MODE | RTLD_GLOBAL | RTLD_GROUP | RTLD_WORLD | RTLD_PARENT) +-# elif defined(RTLD_DEEPBIND) && !defined(__SANITIZE_ADDRESS__) && !__has_feature(memory_sanitizer) ++# elif defined(RTLD_DEEPBIND) && !defined(__SANITIZE_ADDRESS__) && !__has_feature(memory_sanitizer) && defined(PHP_USE_RTLD_DEEPBIND) + # define DL_LOAD(libname) dlopen(libname, PHP_RTLD_MODE | RTLD_GLOBAL | RTLD_DEEPBIND) + # else + # define DL_LOAD(libname) dlopen(libname, PHP_RTLD_MODE | RTLD_GLOBAL) +diff --git a/configure.ac b/configure.ac +index 01d9ded69b920..137f7dae8c3a9 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -868,6 +868,23 @@ AS_VAR_IF([PHP_RTLD_NOW], [yes], + [Define to 1 if 'dlopen()' uses the 'RTLD_NOW' mode flag instead of + 'RTLD_LAZY'.])]) + ++if test "$PHP_SAPI" = "apache2handler"; then ++ PHP_RTLD_DEEPBIND_DEFAULT=yes ++else ++ PHP_RTLD_DEEPBIND_DEFAULT=no ++fi ++ ++PHP_ARG_ENABLE([rtld-deepbind], ++ [whether to dlopen extensions with RTLD_DEEPBIND], ++ [AS_HELP_STRING([--enable-rtld-deepbind], ++ [Use dlopen with RTLD_DEEPBIND])], ++ [$PHP_RTLD_DEEPBIND_DEFAULT], ++ [$PHP_RTLD_DEEPBIND_DEFAULT]) ++ ++if test "$PHP_RTLD_DEEPBIND" = "yes"; then ++ AC_DEFINE(PHP_USE_RTLD_DEEPBIND, 1, [ Use dlopen with RTLD_DEEPBIND ]) ++fi ++ + PHP_ARG_WITH([layout], + [layout of installed files], + [AS_HELP_STRING([--with-layout=TYPE], diff --git a/bin/docker/docker-php-ext-configure b/bin/docker/docker-php-ext-configure new file mode 100755 index 00000000000..34fc1337d56 --- /dev/null +++ b/bin/docker/docker-php-ext-configure @@ -0,0 +1,69 @@ +#!/bin/sh +set -e + +# prefer user supplied CFLAGS, but default to our PHP_CFLAGS +: ${CFLAGS:=$PHP_CFLAGS} +: ${CPPFLAGS:=$PHP_CPPFLAGS} +: ${LDFLAGS:=$PHP_LDFLAGS} +export CFLAGS CPPFLAGS LDFLAGS + +srcExists= +if [ -d /usr/src/php ]; then + srcExists=1 +fi +docker-php-source extract +if [ -z "$srcExists" ]; then + touch /usr/src/php/.docker-delete-me +fi + +cd /usr/src/php/ext + +usage() { + echo "usage: $0 ext-name [configure flags]" + echo " ie: $0 gd --with-jpeg-dir=/usr/local/something" + echo + echo 'Possible values for ext-name:' + find . \ + -mindepth 2 \ + -maxdepth 2 \ + -type f \ + -name 'config.m4' \ + | xargs -n1 dirname \ + | xargs -n1 basename \ + | sort \ + | xargs + echo + echo 'Some of the above modules are already compiled into PHP; please check' + echo 'the output of "php -i" to see which modules are already loaded.' +} + +ext="$1" +if [ -z "$ext" ] || [ ! -d "$ext" ]; then + usage >&2 + exit 1 +fi +shift + +pm='unknown' +if [ -e /lib/apk/db/installed ]; then + pm='apk' +fi + +if [ "$pm" = 'apk' ]; then + if \ + [ -n "$PHPIZE_DEPS" ] \ + && ! apk info --installed .phpize-deps > /dev/null \ + && ! apk info --installed .phpize-deps-configure > /dev/null \ + ; then + apk add --no-cache --virtual .phpize-deps-configure $PHPIZE_DEPS + fi +fi + +if command -v dpkg-architecture > /dev/null; then + gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" + set -- --build="$gnuArch" "$@" +fi + +cd "$ext" +phpize +./configure --enable-option-checking=fatal "$@" diff --git a/bin/docker/docker-php-ext-enable b/bin/docker/docker-php-ext-enable new file mode 100755 index 00000000000..41d20bbe3fb --- /dev/null +++ b/bin/docker/docker-php-ext-enable @@ -0,0 +1,121 @@ +#!/bin/sh +set -e + +extDir="$(php -d 'display_errors=stderr' -r 'echo ini_get("extension_dir");')" +cd "$extDir" + +usage() { + echo "usage: $0 [options] module-name [module-name ...]" + echo " ie: $0 gd mysqli" + echo " $0 pdo pdo_mysql" + echo " $0 --ini-name 0-apc.ini apcu apc" + echo + echo 'Possible values for module-name:' + find -maxdepth 1 \ + -type f \ + -name '*.so' \ + -exec basename '{}' ';' \ + | sort \ + | xargs + echo + echo 'Some of the above modules are already compiled into PHP; please check' + echo 'the output of "php -i" to see which modules are already loaded.' +} + +opts="$(getopt -o 'h?' --long 'help,ini-name:' -- "$@" || { usage >&2 && false; })" +eval set -- "$opts" + +iniName= +while true; do + flag="$1" + shift + case "$flag" in + --help|-h|'-?') usage && exit 0 ;; + --ini-name) iniName="$1" && shift ;; + --) break ;; + *) + { + echo "error: unknown flag: $flag" + usage + } >&2 + exit 1 + ;; + esac +done + +modules= +for module; do + if [ -z "$module" ]; then + continue + fi + if ! [ -f "$module" ] && ! [ -f "$module.so" ]; then + echo >&2 "error: '$module' does not exist" + echo >&2 + usage >&2 + exit 1 + fi + modules="$modules $module" +done + +if [ -z "$modules" ]; then + usage >&2 + exit 1 +fi + +pm='unknown' +if [ -e /lib/apk/db/installed ]; then + pm='apk' +fi + +apkDel= +if [ "$pm" = 'apk' ]; then + if \ + [ -n "$PHPIZE_DEPS" ] \ + && ! apk info --installed .phpize-deps > /dev/null \ + && ! apk info --installed .phpize-deps-configure > /dev/null \ + ; then + apk add --no-cache --virtual '.docker-php-ext-enable-deps' binutils + apkDel='.docker-php-ext-enable-deps' + fi +fi + +for module in $modules; do + moduleFile="$module" + if [ -f "$module.so" ] && ! [ -f "$module" ]; then + moduleFile="$module.so" + fi + if readelf --wide --syms "$moduleFile" | grep -q ' zend_extension_entry$'; then + # https://wiki.php.net/internals/extensions#loading_zend_extensions + line="zend_extension=$module" + else + line="extension=$module" + fi + + ext="$(basename "$module")" + ext="${ext%.*}" + if php -d 'display_errors=stderr' -r 'exit(extension_loaded("'"$ext"'") ? 0 : 1);'; then + # this isn't perfect, but it's better than nothing + # (for example, 'opcache.so' presents inside PHP as 'Zend OPcache', not 'opcache') + echo >&2 + echo >&2 "warning: $ext ($module) is already loaded!" + echo >&2 + continue + fi + + case "$iniName" in + /*) + # allow an absolute path + ini="$iniName" + ;; + *) + ini="$PHP_INI_DIR/conf.d/${iniName:-"docker-php-ext-$ext.ini"}" + ;; + esac + if ! grep -qFx -e "$line" -e "$line.so" "$ini" 2>/dev/null; then + echo "$line" >> "$ini" + fi +done + +if [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then + apk del --no-network $apkDel +fi diff --git a/bin/docker/docker-php-ext-install b/bin/docker/docker-php-ext-install new file mode 100755 index 00000000000..aa0b96c5a3e --- /dev/null +++ b/bin/docker/docker-php-ext-install @@ -0,0 +1,143 @@ +#!/bin/sh +set -e + +# prefer user supplied CFLAGS, but default to our PHP_CFLAGS +: ${CFLAGS:=$PHP_CFLAGS} +: ${CPPFLAGS:=$PHP_CPPFLAGS} +: ${LDFLAGS:=$PHP_LDFLAGS} +export CFLAGS CPPFLAGS LDFLAGS + +srcExists= +if [ -d /usr/src/php ]; then + srcExists=1 +fi +docker-php-source extract +if [ -z "$srcExists" ]; then + touch /usr/src/php/.docker-delete-me +fi + +cd /usr/src/php/ext + +usage() { + echo "usage: $0 [-jN] [--ini-name file.ini] ext-name [ext-name ...]" + echo " ie: $0 gd mysqli" + echo " $0 pdo pdo_mysql" + echo " $0 -j5 gd mbstring mysqli pdo pdo_mysql shmop" + echo + echo 'if custom ./configure arguments are necessary, see docker-php-ext-configure' + echo + echo 'Possible values for ext-name:' + find . \ + -mindepth 2 \ + -maxdepth 2 \ + -type f \ + -name 'config.m4' \ + | xargs -n1 dirname \ + | xargs -n1 basename \ + | sort \ + | xargs + echo + echo 'Some of the above modules are already compiled into PHP; please check' + echo 'the output of "php -i" to see which modules are already loaded.' +} + +opts="$(getopt -o 'h?j:' --long 'help,ini-name:,jobs:' -- "$@" || { usage >&2 && false; })" +eval set -- "$opts" + +j=1 +iniName= +while true; do + flag="$1" + shift + case "$flag" in + --help|-h|'-?') usage && exit 0 ;; + --ini-name) iniName="$1" && shift ;; + --jobs|-j) j="$1" && shift ;; + --) break ;; + *) + { + echo "error: unknown flag: $flag" + usage + } >&2 + exit 1 + ;; + esac +done + +exts= +for ext; do + if [ -z "$ext" ]; then + continue + fi + if [ ! -d "$ext" ]; then + echo >&2 "error: $PWD/$ext does not exist" + echo >&2 + usage >&2 + exit 1 + fi + exts="$exts $ext" +done + +if [ -z "$exts" ]; then + usage >&2 + exit 1 +fi + +pm='unknown' +if [ -e /lib/apk/db/installed ]; then + pm='apk' +fi + +apkDel= +if [ "$pm" = 'apk' ]; then + if [ -n "$PHPIZE_DEPS" ]; then + if apk info --installed .phpize-deps-configure > /dev/null; then + apkDel='.phpize-deps-configure' + elif ! apk info --installed .phpize-deps > /dev/null; then + apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS + apkDel='.phpize-deps' + fi + fi +fi + +popDir="$PWD" +for ext in $exts; do + cd "$ext" + + [ -e Makefile ] || docker-php-ext-configure "$ext" + + make -j"$j" + + if ! php -n -d 'display_errors=stderr' -r 'exit(ZEND_DEBUG_BUILD ? 0 : 1);' > /dev/null; then + # only "strip" modules if we aren't using a debug build of PHP + # (none of our builds are debug builds, but PHP might be recompiled with "--enable-debug" configure option) + # https://github.com/docker-library/php/issues/1268 + + find modules \ + -maxdepth 1 \ + -name '*.so' \ + -exec sh -euxc ' \ + strip --strip-all "$@" || : + ' -- '{}' + + fi + + make -j"$j" install + + find modules \ + -maxdepth 1 \ + -name '*.so' \ + -exec basename '{}' ';' \ + | xargs -r docker-php-ext-enable ${iniName:+--ini-name "$iniName"} + + make -j"$j" clean + + cd "$popDir" +done + +if [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then + apk del --no-network $apkDel +fi + +if [ -e /usr/src/php/.docker-delete-me ]; then + docker-php-source delete +fi diff --git a/bin/docker/docker-php-source b/bin/docker/docker-php-source new file mode 100755 index 00000000000..9033d243de2 --- /dev/null +++ b/bin/docker/docker-php-source @@ -0,0 +1,34 @@ +#!/bin/sh +set -e + +dir=/usr/src/php + +usage() { + echo "usage: $0 COMMAND" + echo + echo "Manage php source tarball lifecycle." + echo + echo "Commands:" + echo " extract extract php source tarball into directory $dir if not already done." + echo " delete delete extracted php source located into $dir if not already done." + echo +} + +case "$1" in + extract) + mkdir -p "$dir" + if [ ! -f "$dir/.docker-extracted" ]; then + tar -Jxf /usr/src/php.tar.xz -C "$dir" --strip-components=1 + touch "$dir/.docker-extracted" + fi + ;; + + delete) + rm -rf "$dir" + ;; + + *) + usage + exit 1 + ;; +esac From 47fc78093ed243166b7f69ee0bc643cfdf94be2d Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 09:35:32 +0100 Subject: [PATCH 05/18] Split job --- .github/workflows/build-docker.yml | 61 ++++++++++++++++++++++++++++++ .github/workflows/build-phar.yml | 12 ------ 2 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/build-docker.yml diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml new file mode 100644 index 00000000000..890aa1cff8b --- /dev/null +++ b/.github/workflows/build-docker.yml @@ -0,0 +1,61 @@ +name: Build phar + +on: + push: + branches: + - master + - 6.x + release: + types: + - published + +permissions: + contents: read + +jobs: + pre_job: + permissions: + actions: write + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v5.3.1 + with: + concurrent_skipping: always + cancel_others: true + do_not_skip: '["release"]' + # list files that may affect or are included into the built phar + paths: '["bin/**", "assets/**", "build/**", "dictionaries/**", "src/**", "stubs/**", "psalm", "psalm-language-server", "psalm-plugin", "psalm-refactor", "psalm-review", "psalter", "box.json.dist", "composer.json", "config.xsd", "keys.asc.gpg", "scoper.inc.php"]' + + build-phar: + permissions: + packages: write + needs: pre_job + if: ${{ needs.pre_job.outputs.should_skip != 'true' }} + runs-on: ubuntu-latest + steps: + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: composer:v2 + coverage: none + env: + fail-fast: true + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # required for composer to automatically detect root package version + + - name: Upload docker image + run: | + bin/ci/waitPackagist.php + + docker build . -t ghcr.io/vimeo/psalm:${{ github.ref_name }} --build-arg PSALM_REV=${{ github.ref_name }} -f bin/docker/Dockerfile + + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + docker tag ghcr.io/vimeo/psalm:${{ github.ref_name }} ghcr.io/vimeo/psalm:latest + docker push ghcr.io/vimeo/psalm:${{ github.ref_name }} ghcr.io/vimeo/psalm:latest diff --git a/.github/workflows/build-phar.yml b/.github/workflows/build-phar.yml index 1b0e681fe50..379d7aac759 100644 --- a/.github/workflows/build-phar.yml +++ b/.github/workflows/build-phar.yml @@ -31,7 +31,6 @@ jobs: build-phar: permissions: contents: write # for release - packages: write needs: pre_job if: ${{ needs.pre_job.outputs.should_skip != 'true' }} runs-on: ubuntu-latest @@ -81,17 +80,6 @@ jobs: GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - name: Upload docker image - run: | - bin/ci/waitPackagist.php - - docker build . -t ghcr.io/vimeo/psalm:${{ github.ref_name }} --build-arg PSALM_REV=${{ github.ref_name }} -f bin/docker/Dockerfile - - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - docker tag ghcr.io/vimeo/psalm:${{ github.ref_name }} ghcr.io/vimeo/psalm:latest - docker push ghcr.io/vimeo/psalm:${{ github.ref_name }} ghcr.io/vimeo/psalm:latest - - name: Upload release assets if: ${{ github.event_name == 'release' }} uses: svenstaro/upload-release-action@v2 From fde42c39953114ef291b18aa081303fe1beec5b2 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 18:38:46 +0100 Subject: [PATCH 06/18] Fix --- .github/workflows/build-docker.yml | 30 +++++-------------------- bin/ci/build-docker.php | 36 ++++++++++++++++++++++++++++++ bin/ci/waitPackagist.php | 20 ----------------- 3 files changed, 41 insertions(+), 45 deletions(-) create mode 100755 bin/ci/build-docker.php delete mode 100755 bin/ci/waitPackagist.php diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 890aa1cff8b..e4660ab9b3f 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -5,6 +5,7 @@ on: branches: - master - 6.x + - add_docker_image release: types: - published @@ -13,27 +14,9 @@ permissions: contents: read jobs: - pre_job: - permissions: - actions: write - runs-on: ubuntu-latest - outputs: - should_skip: ${{ steps.skip_check.outputs.should_skip }} - steps: - - id: skip_check - uses: fkirc/skip-duplicate-actions@v5.3.1 - with: - concurrent_skipping: always - cancel_others: true - do_not_skip: '["release"]' - # list files that may affect or are included into the built phar - paths: '["bin/**", "assets/**", "build/**", "dictionaries/**", "src/**", "stubs/**", "psalm", "psalm-language-server", "psalm-plugin", "psalm-refactor", "psalm-review", "psalter", "box.json.dist", "composer.json", "config.xsd", "keys.asc.gpg", "scoper.inc.php"]' - build-phar: permissions: packages: write - needs: pre_job - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} runs-on: ubuntu-latest steps: - name: Set up PHP @@ -50,12 +33,9 @@ jobs: fetch-depth: 0 # required for composer to automatically detect root package version - name: Upload docker image + env: + EVENT_NAME: ${{ github.event_name }} + REF: ${{ github.ref }} run: | - bin/ci/waitPackagist.php - - docker build . -t ghcr.io/vimeo/psalm:${{ github.ref_name }} --build-arg PSALM_REV=${{ github.ref_name }} -f bin/docker/Dockerfile - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - docker tag ghcr.io/vimeo/psalm:${{ github.ref_name }} ghcr.io/vimeo/psalm:latest - docker push ghcr.io/vimeo/psalm:${{ github.ref_name }} ghcr.io/vimeo/psalm:latest + bin/ci/build-docker.php diff --git a/bin/ci/build-docker.php b/bin/ci/build-docker.php new file mode 100755 index 00000000000..40ece2e8ec5 --- /dev/null +++ b/bin/ci/build-docker.php @@ -0,0 +1,36 @@ + $cmd\n"; + passthru($cmd); +} + +$composer_branch = $is_tag ? $ref : "dev-$ref"; + +$cur = 0; +while (true) { + $json = json_decode(file_get_contents("https://repo.packagist.org/p/vimeo/psalm.json?v=$cur"), true); + if ($json["packages"]["vimeo/psalm"][$composer_branch]["source"]["reference"] === $commit) { + return; + } + sleep(1); + $cur++; +} + +passthru("docker build . -t ghcr.io/vimeo/psalm:$branch --build-arg PSALM_REV=$compose_branch -f bin/docker/Dockerfile"); +passthru("docker push ghcr.io/vimeo/psalm:$branch"); + +if ($is_tag) { + passthru("docker tag ghcr.io/vimeo/psalm:$branch ghcr.io/vimeo/psalm:latest"); + passthru("docker push ghcr.io/vimeo/psalm:latest"); +} diff --git a/bin/ci/waitPackagist.php b/bin/ci/waitPackagist.php deleted file mode 100755 index 694787f6026..00000000000 --- a/bin/ci/waitPackagist.php +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env php - Date: Thu, 20 Feb 2025 18:40:36 +0100 Subject: [PATCH 07/18] Fix --- .github/workflows/build-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index e4660ab9b3f..1ebbdd39e46 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -38,4 +38,4 @@ jobs: REF: ${{ github.ref }} run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - bin/ci/build-docker.php + php bin/ci/build-docker.php From d92ed9ebb7d98ccd26291a2672e6501b31bec55e Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 18:41:03 +0100 Subject: [PATCH 08/18] Fix --- .github/workflows/build-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 1ebbdd39e46..17a403c6c57 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -1,4 +1,4 @@ -name: Build phar +name: Build docker image on: push: @@ -14,7 +14,7 @@ permissions: contents: read jobs: - build-phar: + build-docker: permissions: packages: write runs-on: ubuntu-latest From 2b87d8c9c68331ccb328f62ef214c789db59df70 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 18:44:39 +0100 Subject: [PATCH 09/18] Fix --- .github/workflows/build-docker.yml | 18 ++++++++++++++++++ bin/ci/build-docker.php | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 17a403c6c57..b99f420ff4d 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -14,9 +14,27 @@ permissions: contents: read jobs: + pre_job: + permissions: + actions: write + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v5.3.1 + with: + concurrent_skipping: always + cancel_others: true + do_not_skip: '["release"]' + # list files that may affect or are included into the built phar + paths: '["bin/**", "assets/**", "build/**", "dictionaries/**", "src/**", "stubs/**", "psalm", "psalm-language-server", "psalm-plugin", "psalm-refactor", "psalm-review", "psalter", "box.json.dist", "composer.json", "config.xsd", "keys.asc.gpg", "scoper.inc.php"]' + build-docker: permissions: packages: write + needs: pre_job + if: ${{ needs.pre_job.outputs.should_skip != 'true' }} runs-on: ubuntu-latest steps: - name: Set up PHP diff --git a/bin/ci/build-docker.php b/bin/ci/build-docker.php index 40ece2e8ec5..55c2adfc7ee 100755 --- a/bin/ci/build-docker.php +++ b/bin/ci/build-docker.php @@ -4,7 +4,7 @@ declare(strict_types=1); $commit = getenv('GITHUB_SHA'); -$ref = getenv('REF'); +$ref = substr(getenv('REF'), strlen('refs/heads/')); $is_tag = getenv('EVENT_NAME') === 'release'; echo "Waiting for commit $commit on $ref...".PHP_EOL; From 4dbc32b90ed6e54a04751306cd432304acdef257 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 18:51:38 +0100 Subject: [PATCH 10/18] Fix --- bin/ci/build-docker.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/ci/build-docker.php b/bin/ci/build-docker.php index 55c2adfc7ee..2ee246418d9 100755 --- a/bin/ci/build-docker.php +++ b/bin/ci/build-docker.php @@ -16,12 +16,18 @@ function r(string $cmd): void } $composer_branch = $is_tag ? $ref : "dev-$ref"; +$dev = $is_tag ? '' : '~dev'; $cur = 0; while (true) { - $json = json_decode(file_get_contents("https://repo.packagist.org/p/vimeo/psalm.json?v=$cur"), true); - if ($json["packages"]["vimeo/psalm"][$composer_branch]["source"]["reference"] === $commit) { - return; + $json = json_decode(file_get_contents("https://repo.packagist.org/p2/vimeo/psalm$dev.json?v=$cur"), true)["packages"]["vimeo/psalm"]; + foreach ($json as $v) { + if ($v['version'] === $composer_branch) { + if ($v['source']['reference'] === $commit) { + break 2; + } + break; + } } sleep(1); $cur++; From 68c27ee5aa36ea9fb7fd4ef946b58fd6773d5836 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 18:58:45 +0100 Subject: [PATCH 11/18] Fix --- .github/workflows/build-docker.yml | 26 ++++++++++++++++++++++++++ bin/ci/build-docker.php | 25 ++++++++++++++++++++----- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index b99f420ff4d..604c2c5bd13 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -50,6 +50,32 @@ jobs: with: fetch-depth: 0 # required for composer to automatically detect root package version + - name: Get Composer Cache Directories + id: composer-cache + run: | + echo "files_cache=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + echo "vcs_cache=$(composer config cache-vcs-dir)" >> $GITHUB_OUTPUT + + - name: Generate composer.lock + run: | + composer update --no-install + + - name: Cache composer cache + uses: actions/cache@v4 + with: + path: | + ${{ steps.composer-cache.outputs.files_cache }} + ${{ steps.composer-cache.outputs.vcs_cache }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Run composer install + run: composer install -o + # DO NOT set this, we need composer to figure out the version itself + # env: + # COMPOSER_ROOT_VERSION: dev-master + - name: Upload docker image env: EVENT_NAME: ${{ github.event_name }} diff --git a/bin/ci/build-docker.php b/bin/ci/build-docker.php index 2ee246418d9..760e2d54c3c 100755 --- a/bin/ci/build-docker.php +++ b/bin/ci/build-docker.php @@ -3,6 +3,15 @@ declare(strict_types=1); +use Amp\Process\Process; + +use function Amp\ByteStream\getStderr; +use function Amp\ByteStream\getStdout; +use function Amp\ByteStream\pipe; +use function Amp\async; + +require 'vendor/autoload.php'; + $commit = getenv('GITHUB_SHA'); $ref = substr(getenv('REF'), strlen('refs/heads/')); $is_tag = getenv('EVENT_NAME') === 'release'; @@ -11,8 +20,11 @@ function r(string $cmd): void { - echo "> $cmd\n"; - passthru($cmd); + getStderr()->write("> $cmd\n"); + $cmd = Process::start($cmd); + async(pipe(...), $cmd->getStdout(), getStdout())->ignore(); + async(pipe(...), $cmd->getStderr(), getStderr())->ignore(); + $cmd->join(); } $composer_branch = $is_tag ? $ref : "dev-$ref"; @@ -33,10 +45,13 @@ function r(string $cmd): void $cur++; } -passthru("docker build . -t ghcr.io/vimeo/psalm:$branch --build-arg PSALM_REV=$compose_branch -f bin/docker/Dockerfile"); -passthru("docker push ghcr.io/vimeo/psalm:$branch"); +$ref = escapeshellarg($ref); +$composer_branch = escapeshellarg($composer_branch); + +passthru("docker build . -t ghcr.io/vimeo/psalm:$ref --build-arg PSALM_REV=$composer_branch -f bin/docker/Dockerfile"); +passthru("docker push ghcr.io/vimeo/psalm:$ref"); if ($is_tag) { - passthru("docker tag ghcr.io/vimeo/psalm:$branch ghcr.io/vimeo/psalm:latest"); + passthru("docker tag ghcr.io/vimeo/psalm:$ref ghcr.io/vimeo/psalm:latest"); passthru("docker push ghcr.io/vimeo/psalm:latest"); } From 3adc74f75397856e36f08b52a98685d74ae754a6 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 19:09:35 +0100 Subject: [PATCH 12/18] Multiarch builds --- .github/workflows/build-docker.yml | 9 +++++++-- bin/ci/build-docker.php | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 604c2c5bd13..b87d08926ea 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -3,8 +3,6 @@ name: Build docker image on: push: branches: - - master - - 6.x - add_docker_image release: types: @@ -76,6 +74,13 @@ jobs: # env: # COMPOSER_ROOT_VERSION: dev-master + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + - name: Upload docker image env: EVENT_NAME: ${{ github.event_name }} diff --git a/bin/ci/build-docker.php b/bin/ci/build-docker.php index 760e2d54c3c..7a090fab6b1 100755 --- a/bin/ci/build-docker.php +++ b/bin/ci/build-docker.php @@ -48,7 +48,7 @@ function r(string $cmd): void $ref = escapeshellarg($ref); $composer_branch = escapeshellarg($composer_branch); -passthru("docker build . -t ghcr.io/vimeo/psalm:$ref --build-arg PSALM_REV=$composer_branch -f bin/docker/Dockerfile"); +passthru("docker buildx build --platform linux/amd64,linux/arm64/v8 . -t ghcr.io/vimeo/psalm:$ref --build-arg PSALM_REV=$composer_branch -f bin/docker/Dockerfile"); passthru("docker push ghcr.io/vimeo/psalm:$ref"); if ($is_tag) { From bafebdddb2d19fc3db79b374ec7284feb9928d19 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 19:38:28 +0100 Subject: [PATCH 13/18] Finalize --- .github/workflows/build-docker.yml | 3 --- docs/running_psalm/installation.md | 12 ++++++++++-- .../running_psalm/issues/MissingOverrideAttribute.md | 8 ++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index b87d08926ea..c6ac5a2c798 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -1,9 +1,6 @@ name: Build docker image on: - push: - branches: - - add_docker_image release: types: - published diff --git a/docs/running_psalm/installation.md b/docs/running_psalm/installation.md index 862a394ac50..fdbcb160308 100644 --- a/docs/running_psalm/installation.md +++ b/docs/running_psalm/installation.md @@ -1,6 +1,6 @@ # Installation -The latest version of Psalm requires PHP >= 7.4 and [Composer](https://getcomposer.org/). +The latest version of Psalm requires PHP >= 8.2 and [Composer](https://getcomposer.org/). ```bash composer require --dev vimeo/psalm @@ -17,11 +17,19 @@ Psalm will scan your project and figure out an appropriate [error level](error_l Then run Psalm: ```bash -./vendor/bin/psalm +./vendor/bin/psalm --no-cache ``` Psalm will probably find a number of issues - find out how to deal with them in [Dealing with code issues](dealing_with_code_issues.md). +## Docker image + +It is recommended to run Psalm in the official docker image: it uses a custom build of PHP built from scratch, running Psalm **+30% faster** on average than normal PHP (**+50% faster** if comparing to PHP without opcache installed). + +```bash +docker run -v $PWD:/app --rm -it gchr.io/vimeo/psalm /app/vendor/bin/psalm --no-cache +``` + ## Installing plugins While Psalm can figure out the types used by various libraries based on diff --git a/docs/running_psalm/issues/MissingOverrideAttribute.md b/docs/running_psalm/issues/MissingOverrideAttribute.md index e59b35bd11e..622b9ef0a5d 100644 --- a/docs/running_psalm/issues/MissingOverrideAttribute.md +++ b/docs/running_psalm/issues/MissingOverrideAttribute.md @@ -21,3 +21,11 @@ class B extends A { ## Why this is bad Having an `Override` attribute on overridden methods makes intentions clear. Read the [PHP RFC](https://wiki.php.net/rfc/marking_overriden_methods) for more details. + +## How to fix + +Declare the `#[\Override]` attribute on all indicated methods, or run `vendor/bin/psalter --issues=MissingOverrideAttribute` to let Psalm do it for you. + +Note that the `#[\Override]` attribute is compatible with **all PHP versions**, even PHP 4. + +On PHP 8.0-8.2, require [symfony/polyfill-php83](https://packagist.org/packages/symfony/polyfill-php83) to polyfill the missing Override attribute. \ No newline at end of file From 00725b416d56f90e1fa482d6899a742b66c3f7be Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 20 Feb 2025 19:40:25 +0100 Subject: [PATCH 14/18] Tmp --- .github/workflows/build-docker.yml | 3 +++ bin/ci/build-docker.php | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index c6ac5a2c798..b87d08926ea 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -1,6 +1,9 @@ name: Build docker image on: + push: + branches: + - add_docker_image release: types: - published diff --git a/bin/ci/build-docker.php b/bin/ci/build-docker.php index 7a090fab6b1..0c8dd8057f3 100755 --- a/bin/ci/build-docker.php +++ b/bin/ci/build-docker.php @@ -45,6 +45,10 @@ function r(string $cmd): void $cur++; } +$is_tag = true; +$composer_branch = '^6'; +$ref = '6.x'; + $ref = escapeshellarg($ref); $composer_branch = escapeshellarg($composer_branch); From f4decaac1a2d3a660373876fdaa5eea31439501c Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 25 Feb 2025 18:38:23 +0100 Subject: [PATCH 15/18] Cleanup --- src/Psalm/Internal/Codebase/ClassLikes.php | 4 ++-- src/Psalm/Internal/PhpVisitor/TraitFinder.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index f71503b08ed..ebe746214dd 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -780,7 +780,7 @@ public function getTraitNode(string $fq_trait_name): PhpParser\Node\Stmt\Trait_ ProjectAnalyzer::getInstance()->getCodebase()->analysis_php_version_id, ); - $trait_finder = new TraitFinder(strtolower($fq_trait_name)); + $trait_finder = new TraitFinder($fq_trait_name); $traverser = new NodeTraverser(); $traverser->addVisitor( @@ -797,7 +797,7 @@ public function getTraitNode(string $fq_trait_name): PhpParser\Node\Stmt\Trait_ return $trait_node; } - throw new UnexpectedValueException("Could not locate trait statement for $fq_trait_name"); + throw new UnexpectedValueException('Could not locate trait statement'); } public function addClassAlias(string $fq_class_name, string $alias_name): void diff --git a/src/Psalm/Internal/PhpVisitor/TraitFinder.php b/src/Psalm/Internal/PhpVisitor/TraitFinder.php index f56e0040b64..4af5a56ca9e 100644 --- a/src/Psalm/Internal/PhpVisitor/TraitFinder.php +++ b/src/Psalm/Internal/PhpVisitor/TraitFinder.php @@ -44,10 +44,10 @@ public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true): $fq_trait_name_parts = explode('\\', $this->fq_trait_name); /** @psalm-suppress PossiblyNullPropertyFetch */ - if ($node->name->name !== null && strcasecmp($node->name->name, end($fq_trait_name_parts)) === 0) { + if ($node->name->name === end($fq_trait_name_parts)) { $this->matching_trait_nodes[] = $node; } - } elseif (strcasecmp($resolved_name, $this->fq_trait_name) === 0) { + } elseif ($resolved_name === $this->fq_trait_name) { $this->matching_trait_nodes[] = $node; } } From 6e7dafbe0fb68b578853938ebcd96c8af3414981 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 25 Feb 2025 18:44:55 +0100 Subject: [PATCH 16/18] Add labels --- bin/docker/Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bin/docker/Dockerfile b/bin/docker/Dockerfile index 11eb394c83f..489416c4628 100644 --- a/bin/docker/Dockerfile +++ b/bin/docker/Dockerfile @@ -9,6 +9,14 @@ FROM debian:bookworm-slim +LABEL "repository"="http://github.com/vimeo/psalm" +LABEL "homepage"="http://psalm.dev" +LABEL "maintainer"="Daniil Gentili " + +LABEL "org.opencontainers.image.source"="http://github.com/vimeo/psalm" +LABEL "org.opencontainers.image.description"="A static analysis tool for finding errors in PHP applications " +LABEL "org.opencontainers.image.licenses"=MIT + # prevent Debian's PHP packages from being installed # https://github.com/docker-library/php/pull/542 RUN set -eux; \ From 6094d9d7ba9e4493ddbbd98c90ed8e9dc1c8b318 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 25 Feb 2025 19:15:37 +0100 Subject: [PATCH 17/18] Rm temp --- bin/ci/build-docker.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bin/ci/build-docker.php b/bin/ci/build-docker.php index 0c8dd8057f3..7a090fab6b1 100755 --- a/bin/ci/build-docker.php +++ b/bin/ci/build-docker.php @@ -45,10 +45,6 @@ function r(string $cmd): void $cur++; } -$is_tag = true; -$composer_branch = '^6'; -$ref = '6.x'; - $ref = escapeshellarg($ref); $composer_branch = escapeshellarg($composer_branch); From 9863f01da682a33e0ef6b590e9841a524ddc6016 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 25 Feb 2025 20:47:43 +0100 Subject: [PATCH 18/18] cs-fix --- bin/ci/build-docker.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/ci/build-docker.php b/bin/ci/build-docker.php index 7a090fab6b1..a76c0bf703b 100755 --- a/bin/ci/build-docker.php +++ b/bin/ci/build-docker.php @@ -24,7 +24,9 @@ function r(string $cmd): void $cmd = Process::start($cmd); async(pipe(...), $cmd->getStdout(), getStdout())->ignore(); async(pipe(...), $cmd->getStderr(), getStderr())->ignore(); - $cmd->join(); + if ($exit = $cmd->join()) { + exit($exit); + } } $composer_branch = $is_tag ? $ref : "dev-$ref"; @@ -48,8 +50,7 @@ function r(string $cmd): void $ref = escapeshellarg($ref); $composer_branch = escapeshellarg($composer_branch); -passthru("docker buildx build --platform linux/amd64,linux/arm64/v8 . -t ghcr.io/vimeo/psalm:$ref --build-arg PSALM_REV=$composer_branch -f bin/docker/Dockerfile"); -passthru("docker push ghcr.io/vimeo/psalm:$ref"); +passthru("docker buildx build --push --platform linux/amd64,linux/arm64/v8 --cache-from ghcr.io/vimeo/psalm:$ref --cache-to type=inline . -t ghcr.io/vimeo/psalm:$ref --build-arg PSALM_REV=$composer_branch -f bin/docker/Dockerfile"); if ($is_tag) { passthru("docker tag ghcr.io/vimeo/psalm:$ref ghcr.io/vimeo/psalm:latest");