├── LICENSE ├── README.md ├── bin └── build-php-extension ├── scripts ├── build-extension ├── clean-build-directory ├── configure-extension ├── helpers.sh ├── run-shell └── test-extension └── share ├── alpine.dockerfile ├── buildPhp.sh ├── gdbinit └── ubuntu.dockerfile /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Suora GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Develop, Build and Test PHP Extensions 2 | 3 | This is a tool to develop, build and test PHP extensions in Docker containers. 4 | 5 | ## Installation 6 | 7 | Clone this repository and add the `bin/` directory of this repository to your `$PATH`. 8 | 9 | ## Usage 10 | 11 | Call the `build-php-extension` from the root directory of your extension source. The directory must contain 12 | your `config.m4` file. 13 | 14 | The `build-php-extension` command has multiple subcommands. 15 | 16 | To configure and build your extension, run: 17 | 18 | ```shell 19 | build-php-extension configure 20 | ``` 21 | 22 | To trigger a build, you can run: 23 | 24 | ```shell 25 | build-php-extension build 26 | ``` 27 | 28 | And to run the tests, you need to execute: 29 | 30 | ```shell 31 | build-php-extension test 32 | ``` 33 | 34 | You can specify the tests which should be executed as parameters to the test command. If you omit the list of tests, all 35 | tests are run. 36 | 37 | ```shell 38 | build-php-extension test tests/my_test_*.phpt 39 | ``` 40 | 41 | You can specify the minor PHP version which should be used, whether to enable thread-safety (`--zts`) and the build 42 | mode (`--release` or `--debug`) as arguments to all commands: 43 | 44 | ```shell 45 | build-php-extension --php-version 7.4 --release --zts build 46 | ``` 47 | 48 | The default is to disable thread safety and to build in debug mode. 49 | 50 | Instead of a released version (`--php-version`) you can also specify a git branch or tag like `master`, `PHP-8.2` 51 | or `php-8.1.11`: 52 | 53 | ```shell 54 | build-php-extension --php-git-branch PHP-8.2 build 55 | ``` 56 | 57 | To open an interactive shell inside a Docker container, you can execute: 58 | 59 | ```shell 60 | build-php-extension shell 61 | ``` 62 | 63 | The `dist-clean` subcommand can be used to clean all generated files from the build directory: 64 | 65 | ```shell 66 | build-php-extension dist-clean 67 | ``` 68 | 69 | Status information is stored by the tool in the file `.build-php-extension.state.ini` inside the source code of your 70 | extension. You should add this file to your `.gitignore`. 71 | 72 | ## Rebuild Docker Image 73 | 74 | When you first execute a command in one specific configuration, the Docker image for that configuration will 75 | automatically be built. When you call commands with the same configuration later on, that Docker image will be reused. 76 | You can manually rebuild the Docker image with the following command: 77 | 78 | ```shell 79 | build-php-extension --php-version 8.0 --debug build-image 80 | ``` 81 | 82 | ## Customization 83 | 84 | You can customize the behavior of the build using various hooks. These are scripts that are sourced at the respective 85 | steps of the build process. These are the available extension points: 86 | 87 | - `build-hooks/pre-configure.sh` 88 | - `build-hooks/post-configure.sh` 89 | - `build-hooks/pre-build.sh` 90 | - `build-hooks/post-build.sh` 91 | - `build-hooks/pre-test.sh` 92 | - `build-hooks/post-test.sh` 93 | - `build-hooks/pre-clean-build-directory.sh` 94 | - `build-hooks/post-clean-build-directory.sh` 95 | - `build-hooks/pre-shell.sh` 96 | 97 | If the file `build-hooks/configure` exists and is executable, it is executed instead of calling `./configure` directly. 98 | This can be used to pass additional flags to `./configure`. All command-line-arguments that script receives, should be 99 | forwarded to `./configure`: 100 | 101 | ```shell 102 | #!/bin/sh 103 | 104 | set -eu 105 | ./configure --my-special-configure-flag "${@}" 106 | ``` 107 | -------------------------------------------------------------------------------- /bin/build-php-extension: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import configparser 5 | import json 6 | import re 7 | import shlex 8 | import subprocess 9 | import os 10 | import sys 11 | import urllib.request 12 | from enum import Enum 13 | 14 | STATE_FILENAME = ".build-php-extension.state.ini" 15 | CONFIGURED_FOR_IMAGE_OPTION = "configured_for_image" 16 | 17 | BASE_DIRECTORY = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 18 | 19 | 20 | class PhpVersionInfo: 21 | def __init__(self, minor_version, patch_version, version_id, filename): 22 | self.minor_version = minor_version 23 | self.patch_version = patch_version 24 | self.version_id = version_id 25 | self.filename = filename 26 | 27 | 28 | def php_version_info_from_id(version_id, filename): 29 | patch = version_id % 100 30 | minor = int(version_id / 100) % 100 31 | major = int(version_id / 10000) 32 | 33 | return PhpVersionInfo( 34 | "%s.%s" % (major, minor), 35 | "%s.%s.%s" % (major, minor, patch), 36 | version_id, 37 | filename 38 | ) 39 | 40 | 41 | def php_version_info_from_versions(minor_version, patch_version, filename): 42 | match = re.fullmatch(r"(\d+)\.(\d+)\.(\d+)", patch_version) 43 | if not match: 44 | raise RuntimeError("Error parsing PHP version number %s" % patch_version) 45 | 46 | major = int(match.group(1)) 47 | minor = int(match.group(2)) 48 | patch = int(match.group(3)) 49 | version_id = major * 10000 + minor * 100 + patch 50 | 51 | return PhpVersionInfo( 52 | minor_version, 53 | patch_version, 54 | version_id, 55 | filename 56 | ) 57 | 58 | 59 | def get_php_version_info(args): 60 | if args.php_git_branch: 61 | with urllib.request.urlopen( 62 | "https://raw.githubusercontent.com/%s/%s/main/php_version.h" % ( 63 | args.php_github_repository, args.php_git_branch)) as response: 64 | for line in response: 65 | match = re.search(r"^#define PHP_VERSION_ID (\d+)$", line.decode("utf-8")) 66 | if match: 67 | version_id = int(match.group(1)) 68 | return php_version_info_from_id(version_id, None) 69 | raise RuntimeError("Error parsing PHP version in branch %s in repository %s" % ( 70 | args.php_git_branch, args.php_github_repository)) 71 | 72 | minor_version = args.php_version 73 | response = urllib.request.urlopen("https://www.php.net/releases/index.php?json&version=%s" % minor_version) 74 | data = json.load(response) 75 | if "version" not in data or "source" not in data: 76 | raise RuntimeError("Unknown PHP minor version %s" % minor_version) 77 | 78 | patch_version = data["version"] 79 | for source in data["source"]: 80 | if "filename" not in source: 81 | continue 82 | filename = source["filename"] 83 | if filename.endswith(".tar.xz"): 84 | return php_version_info_from_versions(minor_version, patch_version, filename) 85 | 86 | raise RuntimeError("PHP version %s has no .tar.xz source available" % patch_version) 87 | 88 | 89 | class LinuxDistribution(Enum): 90 | ALPINE = "alpine" 91 | UBUNTU = "ubuntu" 92 | 93 | 94 | def get_linux_distribution(args): 95 | if args.ubuntu: 96 | return LinuxDistribution.UBUNTU 97 | return LinuxDistribution.ALPINE 98 | 99 | 100 | def escape_image_name_component(str): 101 | return str.replace("/", "_") 102 | 103 | 104 | def get_version_image_identifier(args): 105 | if not args.php_git_branch: 106 | return args.php_version 107 | 108 | branch_name = escape_image_name_component(args.php_git_branch) 109 | if args.php_github_repository == "php/php-src": 110 | return "git-%s" % branch_name 111 | 112 | return "git-%s-%s" % (escape_image_name_component(args.php_github_repository), branch_name) 113 | 114 | 115 | def get_image_name(args): 116 | linux_distribution = get_linux_distribution(args) 117 | return "".join([ 118 | "build-php-extension:", 119 | get_version_image_identifier(args), 120 | "-%s" % linux_distribution.value, 121 | "-%s" % escape_image_name_component(args.platform.replace) if args.platform else "", 122 | "-clang" if args.clang else "", 123 | "-zts" if args.zts else "", 124 | "-asan" if args.address_sanitizer else "", 125 | "-ubsan" if args.undefined_sanitizer else "", 126 | "-release" if args.release else "-debug", 127 | ]) 128 | 129 | 130 | def ensure_image_exists(args): 131 | image_name = get_image_name(args) 132 | 133 | if not subprocess.check_output(["docker", "images", "--quiet", image_name]): 134 | build_image(args) 135 | 136 | 137 | def run_in_docker_container(args, command, environment=None): 138 | if not environment: 139 | environment = {} 140 | 141 | image_name = get_image_name(args) 142 | 143 | ensure_image_exists(args) 144 | 145 | flags = [ 146 | "--rm", 147 | "-v %s:%s" % (shlex.quote(os.getcwd()), shlex.quote("/src")), 148 | "-w %s" % shlex.quote("/src") 149 | ] 150 | if sys.stdin.isatty(): 151 | flags.append("--interactive --tty") 152 | 153 | if args.platform: 154 | flags.append("--platform=%s" % shlex.quote(args.platform)) 155 | 156 | if args.address_sanitizer: 157 | environment["ZEND_DONT_UNLOAD_MODULES"] = "1" 158 | 159 | for key, value in environment.items(): 160 | flags.append("-e %s=%s" % (shlex.quote(key), shlex.quote(value))) 161 | 162 | if args.docker_network: 163 | flags.append("--network %s" % shlex.quote(args.docker_network)) 164 | 165 | print("Executing command %s in docker container %s" % (command, image_name)) 166 | subprocess.check_call( 167 | "docker run %s %s %s" % (" ".join(flags), shlex.quote(image_name), command), 168 | shell=True) 169 | 170 | 171 | def build_image(args): 172 | version_info = get_php_version_info(args) 173 | image_name = get_image_name(args) 174 | linux_distribution = get_linux_distribution(args) 175 | 176 | flags = [ 177 | "--file %s" % shlex.quote("%s/share/%s.dockerfile" % (BASE_DIRECTORY, linux_distribution.value)), 178 | "--tag %s" % shlex.quote(image_name), 179 | ] 180 | 181 | additional_config_args = [] 182 | extension_cflags = [] 183 | php_cflags = [] 184 | php_ldflags = [] 185 | php_test_args = [] 186 | install_additional_packages = [] 187 | 188 | if args.platform: 189 | flags.append("--platform=%s" % shlex.quote(args.platform)) 190 | 191 | if args.php_git_branch: 192 | flags.append("--build-arg PHP_GIT_BRANCH=%s" % shlex.quote(args.php_git_branch)) 193 | flags.append("--build-arg PHP_GITHUB_REPOSITORY=%s" % shlex.quote(args.php_github_repository)) 194 | else: 195 | flags.append("--build-arg PHP_TARBALL_NAME=%s" % shlex.quote(version_info.filename)) 196 | 197 | if version_info.version_id < 70205: 198 | # Composer 2.3.0 dropped support for PHP <7.2.5 199 | flags.append("--build-arg COMPOSER_TAG_NAME=2.2") 200 | 201 | if version_info.version_id < 80200: 202 | # PHP versions before 8.2 don't support OpenSSL version 3, which was added in Alpine 3.17 and Ubuntu 22.04 203 | flags.append("--build-arg ALPINE_TAG_NAME=3.16") 204 | flags.append("--build-arg UBUNTU_TAG_NAME=20.04") 205 | 206 | if args.zts: 207 | if version_info.patch_version.startswith("7."): 208 | additional_config_args.append("--enable-maintainer-zts") 209 | else: 210 | additional_config_args.append("--enable-zts") 211 | 212 | if not args.release: 213 | additional_config_args.append("--enable-debug") 214 | 215 | if args.clang: 216 | install_additional_packages.append("clang") 217 | flags.append("--build-arg CC=clang") 218 | 219 | if args.address_sanitizer: 220 | cflags = ["-fsanitize=address"] 221 | extension_cflags.extend(cflags) 222 | 223 | if version_info.version_id >= 80000: 224 | additional_config_args.append("--enable-address-sanitizer") 225 | else: 226 | php_cflags.extend(cflags) 227 | php_cflags.append("-DZEND_TRACK_ARENA_ALLOC") 228 | 229 | if version_info.version_id >= 70400: 230 | php_test_args.append("--asan") 231 | 232 | if version_info.version_id < 70400: 233 | # https://github.com/php/php-src/commit/6165c23475d5020cda3794cb684693a7fab9918d 234 | php_ldflags.append("-ldl") 235 | 236 | if args.undefined_sanitizer: 237 | cflags = ["-fsanitize=undefined", "-fno-sanitize-recover=undefined", "-fno-sanitize=object-size"] 238 | extension_cflags.extend(cflags) 239 | if version_info.version_id >= 80100: 240 | additional_config_args.append("--enable-undefined-sanitizer") 241 | else: 242 | php_cflags.extend(cflags) 243 | 244 | build_args = { 245 | "ADDITIONAL_PHP_CONFIG_ARGS": additional_config_args, 246 | "EXTENSION_CFLAGS": extension_cflags, 247 | "PHP_CFLAGS": php_cflags, 248 | "PHP_LDFLAGS": php_ldflags, 249 | "ADDITIONAL_PHP_TEST_ARGS": php_test_args, 250 | "INSTALL_ADDITIONAL_PACKAGES": install_additional_packages 251 | } 252 | for key, values in build_args.items(): 253 | if values: 254 | flags.append("--build-arg %s=%s" % (key, shlex.quote(" ".join(values)))) 255 | 256 | print("Building image for PHP %s (%s%s)…" % ( 257 | version_info.minor_version, 258 | "git branch %s, " % args.php_git_branch if args.php_git_branch else "", 259 | version_info.patch_version 260 | )) 261 | command = "docker build %s %s" % (" ".join(flags), shlex.quote(BASE_DIRECTORY)) 262 | print("Executing build command: %s" % command) 263 | subprocess.check_call(command, shell=True) 264 | print("Successfully built image %s" % image_name) 265 | 266 | 267 | def read_state_file(): 268 | config = configparser.ConfigParser() 269 | config.read(STATE_FILENAME) 270 | return config 271 | 272 | 273 | def write_state_file(config): 274 | config.write(open(STATE_FILENAME, "w")) 275 | 276 | 277 | def set_configured_for_version(image_name): 278 | config = read_state_file() 279 | if image_name: 280 | config.set(config.default_section, CONFIGURED_FOR_IMAGE_OPTION, image_name) 281 | else: 282 | config.remove_option(config.default_section, CONFIGURED_FOR_IMAGE_OPTION) 283 | write_state_file(config) 284 | 285 | 286 | def configure(args): 287 | set_configured_for_version(None) 288 | run_in_docker_container(args, "configure-extension") 289 | set_configured_for_version(get_image_name(args)) 290 | 291 | 292 | def ensure_configured_for_current_version(args): 293 | image_name = get_image_name(args) 294 | config = read_state_file() 295 | if config.get(config.default_section, CONFIGURED_FOR_IMAGE_OPTION, fallback=None) != image_name: 296 | configure(args) 297 | 298 | 299 | def build(args): 300 | ensure_configured_for_current_version(args) 301 | command = "build-extension" 302 | if args.clean: 303 | command += " --clean" 304 | run_in_docker_container(args, command) 305 | 306 | 307 | def test(args): 308 | ensure_configured_for_current_version(args) 309 | environment = {} 310 | if args.tests: 311 | environment["TESTS"] = " ".join(map(shlex.quote, args.tests)) 312 | run_in_docker_container(args, "test-extension", environment) 313 | 314 | 315 | def dist_clean(args): 316 | set_configured_for_version(None) 317 | run_in_docker_container(args, "clean-build-directory") 318 | 319 | 320 | def shell(args): 321 | if not args.no_configure: 322 | ensure_configured_for_current_version(args) 323 | run_in_docker_container(args, "run-shell") 324 | 325 | 326 | def sanitize_arguments(args): 327 | php_version = args.php_version 328 | if not re.fullmatch("[0-9]+\\.[0-9]+", php_version): 329 | raise RuntimeError("Illegal version number %s, the version should have the form ." % php_version) 330 | 331 | linux_distribution = get_linux_distribution(args) 332 | use_sanitizer = args.address_sanitizer or args.undefined_sanitizer 333 | if linux_distribution == LinuxDistribution.ALPINE and use_sanitizer: 334 | raise RuntimeError("Sanitizers are currently not supported on Alpine since they don't support MUSL") 335 | 336 | 337 | argument_parser = argparse.ArgumentParser(description="Build and test PHP extensions") 338 | argument_parser.add_argument("--zts", action="store_true", help="Enable PHP thread safety") 339 | argument_parser.add_argument("--docker-network", help="The name of the docker network to use for the container") 340 | argument_parser.add_argument("--platform", help="Set the platform to use (e.g. linux/amd64 or linux/arm64)") 341 | 342 | php_version = argument_parser.add_mutually_exclusive_group() 343 | php_version.add_argument("--php-version", default="8.4", help="The PHP minor version") 344 | php_version.add_argument("--php-git-branch", 345 | help="A git tag or branch name from the main PHP repository (e.g. PHP-8.2 or php-8.1.11)") 346 | argument_parser.add_argument("--php-github-repository", default="php/php-src", 347 | help="The GitHub user and repository names (e.g. php/php-src)") 348 | 349 | build_type_group = argument_parser.add_mutually_exclusive_group() 350 | build_type_group.add_argument("--debug", action="store_true", help="Enable PHP debug mode (default)") 351 | build_type_group.add_argument("--release", action="store_true", help="Enable PHP release mode") 352 | 353 | distribution_group = argument_parser.add_mutually_exclusive_group() 354 | distribution_group.add_argument("--alpine", action="store_true", help="Use the Alpine Linux distribution (default)") 355 | distribution_group.add_argument("--ubuntu", action="store_true", help="Use the Ubuntu Linux distribution") 356 | 357 | compiler_group = argument_parser.add_mutually_exclusive_group() 358 | compiler_group.add_argument("--gcc", action="store_true", help="Use GCC as a compiler (default)") 359 | compiler_group.add_argument("--clang", action="store_true", help="Use Clang as a compiler") 360 | 361 | argument_parser.add_argument("--address-sanitizer", action="store_true", help="Enable address sanitizer") 362 | argument_parser.add_argument("--undefined-sanitizer", action="store_true", help="Enable undefined sanitizer") 363 | 364 | subparsers = argument_parser.add_subparsers(help="Which command to run", dest="command", required=True) 365 | 366 | build_image_parser = subparsers.add_parser("build-image", help="Build the docker image") 367 | configure_parser = subparsers.add_parser("configure", help="Configure the extension in a container") 368 | 369 | build_parser = subparsers.add_parser("build", help="Build the extension in a container") 370 | build_parser.add_argument("--clean", action="store_true", help="Run make clean before the build") 371 | 372 | test_parser = subparsers.add_parser("test", help="Test the extension in a container") 373 | test_parser.add_argument("tests", help="The tests to execute", nargs="*") 374 | 375 | dist_clean_parser = subparsers.add_parser("dist-clean", help="Clean the build directory") 376 | 377 | shell_parser = subparsers.add_parser("shell", help="Open an interactive shell in a container") 378 | shell_parser.add_argument("--no-configure", action="store_true", help="Don't automatically configure the extension") 379 | 380 | arguments = argument_parser.parse_args() 381 | 382 | try: 383 | sanitize_arguments(arguments) 384 | locals()[arguments.command.replace("-", "_")](arguments) 385 | except BaseException as error: 386 | print("Error: %s" % error) 387 | exit(1) 388 | -------------------------------------------------------------------------------- /scripts/build-extension: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | . helpers.sh 6 | 7 | source_hook pre build 8 | 9 | if [ "${#}" -ge 1 ] && [ "${1}" = '--clean' ]; then 10 | make clean 11 | fi 12 | 13 | make -j$(( $(getconf _NPROCESSORS_ONLN) + 1 )) 14 | 15 | source_hook post build 16 | -------------------------------------------------------------------------------- /scripts/clean-build-directory: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | . helpers.sh 6 | 7 | source_hook pre clean-build-directory 8 | 9 | test -f Makefile && make distclean 10 | rm -rf autom4te.cache/ build/ include/ modules/ config.h config.h.in config.nice configure configure.ac run-tests.php 11 | 12 | source_hook post clean-build-directory 13 | -------------------------------------------------------------------------------- /scripts/configure-extension: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | . helpers.sh 6 | 7 | clean-build-directory 8 | 9 | source_hook pre configure 10 | 11 | phpize 12 | 13 | CONFIGURE_SCRIPT="./configure" 14 | if [ -x "build-hooks/configure" ]; then 15 | CONFIGURE_SCRIPT="build-hooks/configure" 16 | fi 17 | 18 | "${CONFIGURE_SCRIPT}" CFLAGS="${CFLAGS:-} ${EXTENSION_CFLAGS:-}" CXXFLAGS="${CXXFLAGS:-} ${EXTENSION_CFLAGS:-}" 19 | 20 | source_hook post configure 21 | -------------------------------------------------------------------------------- /scripts/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | source_hook() { 4 | if [ "${#}" -ne 2 ]; then 5 | echo "Usage: ${0} " 6 | exit 1 7 | fi 8 | 9 | SCRIPT_NAME="build-hooks/${1}-${2}.sh" 10 | if [ -f "${SCRIPT_NAME}" ]; then 11 | . "${SCRIPT_NAME}" 12 | fi 13 | } 14 | -------------------------------------------------------------------------------- /scripts/run-shell: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | . helpers.sh 6 | 7 | source_hook pre shell 8 | 9 | exec bash 10 | -------------------------------------------------------------------------------- /scripts/test-extension: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | . helpers.sh 6 | 7 | build-extension 8 | 9 | source_hook pre test 10 | 11 | make test 12 | 13 | source_hook post test 14 | -------------------------------------------------------------------------------- /share/alpine.dockerfile: -------------------------------------------------------------------------------- 1 | ARG COMPOSER_TAG_NAME=latest 2 | ARG ALPINE_TAG_NAME=latest 3 | 4 | FROM composer:${COMPOSER_TAG_NAME} AS composer 5 | FROM ghcr.io/php/pie:bin AS pie 6 | 7 | FROM alpine:${ALPINE_TAG_NAME} AS alpine 8 | 9 | RUN apk add --no-cache \ 10 | autoconf \ 11 | bash \ 12 | bison \ 13 | build-base \ 14 | bzip2-dev \ 15 | cmake \ 16 | curl-dev \ 17 | gdb \ 18 | git \ 19 | gperf \ 20 | libsodium-dev \ 21 | libxml2-dev \ 22 | libzip-dev \ 23 | linux-headers \ 24 | oniguruma-dev \ 25 | pcre2-dev \ 26 | pkgconf \ 27 | re2c \ 28 | readline-dev \ 29 | sqlite-dev \ 30 | valgrind 31 | 32 | ENV PS1="\\w \\$ " 33 | 34 | COPY --from=composer /usr/bin/composer /usr/local/bin/composer 35 | COPY --from=pie /pie /usr/local/bin/pie 36 | 37 | COPY scripts/ /usr/local/bin/ 38 | COPY share/gdbinit /root/.config/gdb/gdbinit 39 | COPY share/buildPhp.sh /opt/ 40 | 41 | ARG INSTALL_ADDITIONAL_PACKAGES 42 | 43 | RUN apk add --no-cache ${INSTALL_ADDITIONAL_PACKAGES} 44 | 45 | ARG PHP_TARBALL_NAME 46 | ARG PHP_GIT_BRANCH 47 | ARG PHP_GITHUB_REPOSITORY 48 | ARG ADDITIONAL_PHP_CONFIG_ARGS 49 | ARG PHP_CFLAGS 50 | ARG PHP_LDFLAGS 51 | ARG CC 52 | 53 | ENV CC="${CC:-cc}" 54 | 55 | RUN /opt/buildPhp.sh 56 | 57 | ARG EXTENSION_CFLAGS 58 | ARG ADDITIONAL_PHP_TEST_ARGS 59 | 60 | ENV EXTENSION_CFLAGS="${EXTENSION_CFLAGS}" 61 | ENV NO_INTERACTION=1 62 | ENV TEST_PHP_ARGS="--show-diff${ADDITIONAL_PHP_TEST_ARGS:+ }${ADDITIONAL_PHP_TEST_ARGS}" 63 | 64 | CMD ["/bin/bash"] 65 | -------------------------------------------------------------------------------- /share/buildPhp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | mkdir -p /opt/php-src 6 | 7 | if [ -n "${PHP_GIT_BRANCH-}" ]; then 8 | git clone --depth 1 --branch "${PHP_GIT_BRANCH}" "https://github.com/${PHP_GITHUB_REPOSITORY}.git" /opt/php-src 9 | else 10 | wget "https://www.php.net/distributions/${PHP_TARBALL_NAME}" -O - | tar xJC /opt/php-src/ --strip-components 1 11 | fi 12 | 13 | cd /opt/php-src 14 | 15 | if ! [ -x "configure" ]; then 16 | ./buildconf --force 17 | fi 18 | 19 | ./configure \ 20 | CFLAGS="${CFLAGS-} ${PHP_CFLAGS-}" \ 21 | CXXFLAGS="${CXXFLAGS-} ${PHP_CFLAGS-}" \ 22 | LDFLAGS="${LDFLAGS-} ${PHP_LDFLAGS-}" \ 23 | PHP_BUILD_PROVIDER='https://github.com/SuoraGmbH/build-php-extension' \ 24 | --enable-bcmath \ 25 | --enable-fpm \ 26 | --enable-mbstring \ 27 | --enable-pdo \ 28 | --enable-sockets \ 29 | --enable-soap \ 30 | --with-bz2 \ 31 | --with-curl \ 32 | --with-external-pcre \ 33 | --with-mysqli \ 34 | --with-openssl \ 35 | --with-pdo-mysql \ 36 | --with-pdo-sqlite=/usr \ 37 | --with-readline=/usr \ 38 | --with-sodium \ 39 | --with-sqlite3=/usr \ 40 | --with-zip \ 41 | --with-zlib \ 42 | --without-pear \ 43 | ${ADDITIONAL_PHP_CONFIG_ARGS-} 44 | 45 | make -j$(( $(getconf _NPROCESSORS_ONLN) + 1 )) 46 | make install 47 | -------------------------------------------------------------------------------- /share/gdbinit: -------------------------------------------------------------------------------- 1 | add-auto-load-safe-path /usr/local/bin/php 2 | -------------------------------------------------------------------------------- /share/ubuntu.dockerfile: -------------------------------------------------------------------------------- 1 | ARG COMPOSER_TAG_NAME=latest 2 | ARG UBUNTU_TAG_NAME=latest 3 | 4 | FROM composer:${COMPOSER_TAG_NAME} AS composer 5 | FROM ghcr.io/php/pie:bin AS pie 6 | 7 | FROM ubuntu:${UBUNTU_TAG_NAME} AS ubuntu 8 | 9 | RUN apt-get update && \ 10 | DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y \ 11 | autoconf \ 12 | bison \ 13 | build-essential \ 14 | cmake \ 15 | gdb \ 16 | git \ 17 | gperf \ 18 | less \ 19 | libbz2-dev \ 20 | libcurl4-openssl-dev \ 21 | libonig-dev \ 22 | libpcre2-dev \ 23 | libreadline-dev \ 24 | libsodium-dev \ 25 | libsqlite3-dev \ 26 | libssl-dev \ 27 | libxml2-dev \ 28 | libzip-dev \ 29 | pkg-config \ 30 | re2c \ 31 | unzip \ 32 | valgrind \ 33 | vim-tiny \ 34 | wget \ 35 | zlib1g-dev 36 | 37 | COPY --from=composer /usr/bin/composer /usr/local/bin/composer 38 | COPY --from=pie /pie /usr/local/bin/pie 39 | 40 | COPY scripts/ /usr/local/bin/ 41 | COPY share/gdbinit /root/.config/gdb/gdbinit 42 | COPY share/buildPhp.sh /opt/ 43 | 44 | ARG INSTALL_ADDITIONAL_PACKAGES 45 | 46 | RUN apt-get update && \ 47 | DEBIAN_FRONTEND=noninteractive apt-get install -y ${INSTALL_ADDITIONAL_PACKAGES} 48 | 49 | ARG PHP_TARBALL_NAME 50 | ARG PHP_GIT_BRANCH 51 | ARG PHP_GITHUB_REPOSITORY 52 | ARG ADDITIONAL_PHP_CONFIG_ARGS 53 | ARG PHP_CFLAGS 54 | ARG PHP_LDFLAGS 55 | ARG CC 56 | 57 | ENV CC="${CC:-cc}" 58 | 59 | RUN /opt/buildPhp.sh 60 | 61 | ARG EXTENSION_CFLAGS 62 | ARG ADDITIONAL_PHP_TEST_ARGS 63 | 64 | ENV EXTENSION_CFLAGS="${EXTENSION_CFLAGS}" 65 | ENV NO_INTERACTION=1 66 | ENV TEST_PHP_ARGS="--show-diff${ADDITIONAL_PHP_TEST_ARGS:+ }${ADDITIONAL_PHP_TEST_ARGS}" 67 | 68 | CMD ["/bin/bash"] 69 | --------------------------------------------------------------------------------