diff --git a/.github/skills/staticphp-build-troubleshooting/SKILL.md b/.agents/skills/staticphp-build-troubleshooting/SKILL.md similarity index 100% rename from .github/skills/staticphp-build-troubleshooting/SKILL.md rename to .agents/skills/staticphp-build-troubleshooting/SKILL.md diff --git a/.github/skills/staticphp-build-troubleshooting/agents/openai.yaml b/.agents/skills/staticphp-build-troubleshooting/agents/openai.yaml similarity index 100% rename from .github/skills/staticphp-build-troubleshooting/agents/openai.yaml rename to .agents/skills/staticphp-build-troubleshooting/agents/openai.yaml diff --git a/.github/skills/staticphp-build-troubleshooting/references/log-triage.md b/.agents/skills/staticphp-build-troubleshooting/references/log-triage.md similarity index 100% rename from .github/skills/staticphp-build-troubleshooting/references/log-triage.md rename to .agents/skills/staticphp-build-troubleshooting/references/log-triage.md diff --git a/.github/skills/staticphp-documentation-sync/SKILL.md b/.agents/skills/staticphp-documentation-sync/SKILL.md similarity index 100% rename from .github/skills/staticphp-documentation-sync/SKILL.md rename to .agents/skills/staticphp-documentation-sync/SKILL.md diff --git a/.github/skills/staticphp-package-maintenance/SKILL.md b/.agents/skills/staticphp-package-maintenance/SKILL.md similarity index 100% rename from .github/skills/staticphp-package-maintenance/SKILL.md rename to .agents/skills/staticphp-package-maintenance/SKILL.md diff --git a/.github/skills/staticphp-package-maintenance/agents/openai.yaml b/.agents/skills/staticphp-package-maintenance/agents/openai.yaml similarity index 100% rename from .github/skills/staticphp-package-maintenance/agents/openai.yaml rename to .agents/skills/staticphp-package-maintenance/agents/openai.yaml diff --git a/.github/skills/staticphp-package-maintenance/references/build-class-patterns.md b/.agents/skills/staticphp-package-maintenance/references/build-class-patterns.md similarity index 100% rename from .github/skills/staticphp-package-maintenance/references/build-class-patterns.md rename to .agents/skills/staticphp-package-maintenance/references/build-class-patterns.md diff --git a/.github/skills/staticphp-package-maintenance/references/package-reference.md b/.agents/skills/staticphp-package-maintenance/references/package-reference.md similarity index 100% rename from .github/skills/staticphp-package-maintenance/references/package-reference.md rename to .agents/skills/staticphp-package-maintenance/references/package-reference.md diff --git a/docs/.vitepress/sidebar.en.ts b/docs/.vitepress/sidebar.en.ts index 2a2d81dae..fa832a14d 100644 --- a/docs/.vitepress/sidebar.en.ts +++ b/docs/.vitepress/sidebar.en.ts @@ -51,6 +51,7 @@ export default { { text: 'Build Lifecycle', link: '/en/develop/build-lifecycle' }, { text: 'Compilation Tools', link: '/en/develop/system-build-tools' }, { text: 'Doctor', link: '/en/develop/doctor-module' }, + { text: 'Performance Engineering', link: '/en/develop/performance' }, { text: 'PHP Source Modifications', link: '/en/develop/php-src-changes' }, ], }, diff --git a/docs/.vitepress/sidebar.zh.ts b/docs/.vitepress/sidebar.zh.ts index 07872db9c..40a0a8997 100644 --- a/docs/.vitepress/sidebar.zh.ts +++ b/docs/.vitepress/sidebar.zh.ts @@ -51,6 +51,7 @@ export default { { text: '构建生命周期', link: '/zh/develop/build-lifecycle' }, { text: '编译工具', link: '/zh/develop/system-build-tools' }, { text: 'Doctor 环境检查', link: '/zh/develop/doctor-module' }, + { text: '性能工程', link: '/zh/develop/performance' }, { text: '对 PHP 源码的修改', link: '/zh/develop/php-src-changes' }, ], }, diff --git a/docs/en/contributing/index.md b/docs/en/contributing/index.md index 5438cc30c..af9167f7f 100644 --- a/docs/en/contributing/index.md +++ b/docs/en/contributing/index.md @@ -1,5 +1,132 @@ # Contributing - +StaticPHP welcomes fixes, package definitions, platform support, tests, and documentation. v3 is still defining parts of its extension API, so distinguish between a contribution to the built-in `core` Registry and an external Registry that needs a long-term compatibility contract. + +## Development Setup + +A source checkout requires PHP 8.4 or later and Composer. The CLI also needs the PHP extensions listed in the [source installation guide](/en/guide/installation). Install the development dependencies from the repository root: + +```bash +composer install +bin/spc --version +``` + +Node.js and npm are only required when changing the VitePress documentation: + +```bash +npm install +npm run docs:build +``` + +Run `bin/spc doctor` before attempting a real build. Full static builds are platform-sensitive and can be slow, so they are not a prerequisite for every documentation, test, or configuration-only change. + +## Find the Correct Layer + +Keep a change in the narrowest layer that can express it: + +| Area | Purpose | +|---|---| +| `config/pkg/ext/` | PHP extension Package definitions | +| `config/pkg/lib/` | Link-time dependency library definitions | +| `config/pkg/target/` | Final and virtual build targets | +| `config/pkg/tool/` | Host-side build tool Packages | +| `config/artifact/` | Shared or complex source/binary Artifact definitions | +| `src/Package/` | Package-specific recipes, patches, hooks, and custom Artifact behavior | +| `src/StaticPHP/` | Framework, resolver, runtime, Doctor, Registry, and command implementation | +| `tests/` | PHPUnit tests and fixtures | +| `docs/en/`, `docs/zh/` | Canonical English documentation and its synchronized Chinese version | + +Prefer declarative YAML fields over PHP conditions. Add a recipe class only when the package needs build commands, source rewrites, validation, custom configure arguments, or lifecycle hooks. + +Do not fix a problem by editing generated `buildroot/`, `source/`, `downloads/`, or `pkgroot/` content. Changes there are discarded and do not describe how another user can reproduce the build. + +## Adding or Updating a Package + +Before writing a new definition, search for a package with the same build system and artifact type. Follow its structure, then verify these points: + +1. Choose the correct type: `php-extension`, `library`, `target`, `virtual-target`, or `tool`. +2. Use the exact package name. PHP extension dependencies and configs use the `ext-` prefix. +3. Put hard dependencies in `depends`, optional relationships in `suggests`, and host build tools in `tools`. +4. Express platform differences with `@windows`, `@unix`, `@linux`, or `@macos` fields when the schema supports them. +5. Prefer an inline Artifact for a simple one-package source; use `config/artifact/` when multiple Packages share it or custom behavior makes a standalone definition clearer. +6. Record license identifiers and files in Artifact metadata. Add structured Package `license` entries only for material that must be copied after a source build. +7. Add or update the relevant PHP recipe only when configuration alone is insufficient. + +See [Package Model](/en/develop/package-model) and [Artifact Model](/en/develop/artifact-model) for the current schemas. + +Configuration linting is strict about unknown fields but cannot prove that a URL exists, a patch still applies, or a library links on every platform. When practical, test the oldest and newest affected PHP branches and the platforms touched by the change. + +## PHP Code and Tests + +StaticPHP follows the existing code style and favors existing package/build patterns over new abstractions. For framework code: + +- Keep internal orchestration details internal; PHP `public` visibility alone does not make a symbol part of the supported extension API. +- Add a focused PHPUnit test for resolver, config, Registry, command, or utility behavior. +- Include the failing input or regression case in the test instead of relying only on a manual build. +- Avoid unrelated formatting or mechanical changes in the same pull request. + +Tests live under `tests/StaticPHP/` and use the `Tests\StaticPHP\` namespace. Run the smallest relevant test during development, then the project checks before submitting. + +## Doctor Changes + +Built-in Doctor checks currently live under `src/StaticPHP/Doctor/Item/`. A check should observe the environment without changing it; installation, downloads, and file changes belong in a separate fix callback. + +The current Attributes and callback signatures are internal and have known limitations, including raw fix parameters and no automatic re-check after a fix. Follow an existing core check when maintaining the built-in set, but do not present that pattern as a stable third-party API. Update [Doctor Module](/en/develop/doctor-module) and [Compilation Tools](/en/develop/system-build-tools) when user-visible checks, fixes, prerequisites, or lock behavior change. + +## Documentation + +English under `docs/en/` is canonical. Every English Markdown file must have a corresponding file under `docs/zh/`, and both versions must have the same headings, examples, tables, admonitions, and links to the appropriate language path. + +When behavior changes, update all pages that describe it—not only the closest reference page. Common examples include CLI options, environment variables, Package/Artifact fields, Doctor checks, and migration guidance. + +Validate matching file trees and build the site: + +```bash +diff <(find docs/en -name '*.md' | sed 's|docs/en/||' | sort) \ + <(find docs/zh -name '*.md' | sed 's|docs/zh/||' | sort) +npm run docs:build +``` + +New pages must also be added to both VitePress sidebars. + +## Validation Checklist + +Use checks proportional to the change: + +| Change | Required checks | +|---|---| +| Package, Artifact, target, extension, library, or tool config | `bin/spc dev:lint-config`, `composer cs-fix` | +| PHP framework or recipe code | `composer cs-fix`, `composer test`, `composer analyse` | +| Documentation | English/Chinese tree and structure checks, `npm run docs:build` | +| Platform build fix | The checks above plus the narrowest affected build or CI matrix when available | + +Useful commands: + +```bash +bin/spc dev:lint-config +composer cs-fix +composer test +composer analyse +``` + +If a full build cannot be run locally, state which platform/PHP combinations remain untested and include the relevant logs or CI result. Build and shell logs live under `log/` by default, but generated logs should not normally be committed. + +## Pull Requests + +Keep each pull request focused and explain: + +- The problem and why the selected layer is appropriate. +- The affected Package/Artifact, target OS, architecture, toolchain, and PHP versions. +- User-visible behavior or compatibility impact. +- Tests and builds that were run, including anything not tested. +- Documentation updated for both languages. + +Complete the repository pull-request checklist. Do not commit generated build directories, downloaded archives, caches, local environment files, or unrelated editor/OS metadata. + +For a new dependency or bundled patch, include its upstream source, license, and the condition that requires it. Prefer an upstream fix and keep downstream patches scoped to the affected versions. + +## Security Reports + +Do not publish exploit details, credentials, signing material, or a reproducible vulnerability in a normal issue. Use GitHub's private vulnerability reporting for the repository when available, or contact the maintainers through an existing project channel to arrange a private disclosure. + +The repository does not currently contain a dedicated `SECURITY.md`; adding a formal supported-version and disclosure policy remains a project maintenance task. diff --git a/docs/en/develop/doctor-module.md b/docs/en/develop/doctor-module.md index 3bd284297..083f2483c 100644 --- a/docs/en/develop/doctor-module.md +++ b/docs/en/develop/doctor-module.md @@ -1,4 +1,117 @@ # Doctor Module - +Doctor diagnoses whether the current host, selected target, and compiler toolchain can run StaticPHP's build pipeline. It checks common host commands, platform-specific SDKs, managed tool packages, and target-aware requirements such as Zig or musl support. + +This page documents the current v3 behavior. The built-in check/fix loader is also described for maintainers, but its PHP Attributes, callback signatures, and loader classes are not yet a stable third-party extension API. + +## Running Doctor + +Run Doctor explicitly after installing StaticPHP, changing the target/toolchain, or updating system build tools: + +```bash +# Pre-built binary +./spc doctor + +# Source installation +bin/spc doctor +``` + +The fix policy is selected with `--auto-fix` (`-y`): + +| Invocation | Current behavior | +|---|---| +| `spc doctor` | Ask before each available automatic fix | +| `spc doctor --auto-fix` | Apply available fixes without prompting | +| `spc doctor --auto-fix=never` | Report the first unresolved failure and do not attempt a fix | + +Doctor exits successfully only when every applicable check either passes, is skipped, or reports a fix that the current implementation considers successful. It stops at the first unresolved failure. + +The `craft` command also runs Doctor by default. It uses the automatic-fix policy without prompting; set `craft-options.doctor: false` in `craft.yml` to disable that step. + +## Execution Model + +The current execution sequence is: + +1. `DoctorLoader` collects built-in and Registry-provided classes and scans their public methods for Doctor Attributes. +2. Checks are sorted by descending `level`. +3. `#[OptionalCheck]`, `limit_os`, and `SPC_SKIP_DOCTOR_CHECK_ITEMS` remove checks that do not apply. +4. The check callback is called directly. +5. `null` means skipped; `CheckResult::ok()` means passed; `CheckResult::fail()` describes a failure and may name a fix. +6. Depending on the selected fix policy, Doctor rejects, prompts for, or immediately invokes the named fix through `ApplicationContext`. + +`checkAll()` is fail-fast: it does not collect all failures into a final report. A successful fixer is currently accepted without re-running its original check. + +::: warning +An invalid check return value is currently printed as “Skipped due to invalid return value” and treated as successful. This is an implementation limitation, not behavior extension authors should rely on. +::: + +## Built-in Check Groups + +The exact list is target-aware. The current core Registry provides these groups: + +| Scope | Checks | +|---|---| +| Common | At least one Registry is loaded; the host OS is Linux, macOS, or Windows | +| Linux | Distribution build-tool baseline, CMake 3.22+, `re2c` 1.0.3+, and Linux headers on musl distributions | +| macOS | Homebrew or MacPorts, required build commands, GNU Bison 3+, and the selected Homebrew/MacPorts LLVM variant | +| Windows target | `vswhere`, Visual Studio C++ tools, Git `patch.exe`, managed MSYS2, `7za.exe`, and the initialized MSVC command environment | +| Unix target | StaticPHP-managed and functional `pkg-config` | +| Zig toolchain | Installed StaticPHP `zig` tool package | +| musl target/toolchain | Required musl wrapper and, for the legacy `MuslToolchain`, the cross toolchain under `/usr/local/musl` | + +Package-specific tools declared through a package's `tools` field are resolved by `PackageInstaller`; they are not all duplicated as baseline Doctor checks. See [Compilation Tools](./system-build-tools) for the platform package lists and toolchain details. + +## Automatic Fixes + +Depending on the failed item and platform, a fix may: + +- Invoke `apt`, `apk`, `dnf`, `yum`, `pacman`, Homebrew, or MacPorts. +- Use `sudo` for system-level installation. +- Download and install a StaticPHP Tool or Artifact package. +- Build a replacement tool such as `re2c`. +- Install musl runtime or cross-toolchain files outside the workspace. + +Automatic fixes can therefore use the network and change the host system. The default interactive policy asks first; CI commonly uses either `--auto-fix` in a disposable environment or `--auto-fix=never` in a pre-provisioned image. + +Fixers do not share a transaction or rollback mechanism. A fixer returning `true` is currently considered sufficient, even if the environment still fails the original condition. Re-run `spc doctor --auto-fix=never` after a repair when verification matters. + +## Doctor Lock + +After all checks are considered successful, Doctor writes `.spc-doctor.lock` containing the current StaticPHP version. `craft` writes the same lock after its Doctor step succeeds. + +The preferred locations are: + +| Platform | Preferred path | Fallbacks | +|---|---|---| +| Unix | `$XDG_CACHE_HOME/.spc-doctor.lock`, or `$HOME/.cache/.spc-doctor.lock` | System temporary directory, then the working directory | +| Windows | `%LOCALAPPDATA%\.spc-doctor.lock` | System temporary directory, then the working directory | + +Build/download commands that call `checkDoctorCache()` only use this lock to decide whether to show a warning. The lock does not skip an explicit `spc doctor` run and does not block a build. + +The current fingerprint is only the StaticPHP version. A target, toolchain, Registry set, PATH, or operating-system change does not invalidate it automatically. Delete the lock or run Doctor explicitly after such a change. + +## Skipping Checks and Warnings + +Two environment variables have different purposes: + +```ini +# Skip named Doctor items during an actual Doctor run (exact item names, comma-separated) +SPC_SKIP_DOCTOR_CHECK_ITEMS="if cmake version >= 3.22,if re2c version >= 1.0.3" + +# Suppress the pre-build warning when the Doctor lock is missing or stale +SPC_SKIP_DOCTOR_CHECK=1 +``` + +`SPC_SKIP_DOCTOR_CHECK` does not remove checks from an explicit `spc doctor` invocation. `SPC_SKIP_DOCTOR_CHECK_ITEMS` does, and a skipped required check can allow an unusable environment to appear healthy, so it should be limited to controlled CI or debugging cases. + +## Current Internal Registration Model + +A Registry can currently point `doctor.psr-4` or `doctor.classes` at PHP classes. `DoctorLoader` constructs each class without constructor arguments and recognizes: + +| Attribute | Current role | +|---|---| +| `#[CheckItem]` | Registers a check name, optional OS limit, and numeric level; higher levels run first | +| `#[OptionalCheck]` | Supplies a class- or method-level callable that decides whether the check is present | +| `#[FixItem]` | Registers a string fix name used by a failed `CheckResult` | + +Check callbacks currently receive no DI context and return `null` or `CheckResult`. Fix callbacks are invoked through `ApplicationContext`, with `CheckResult`'s raw `fix_params` array used as callback context. The `manual` property on `CheckItem` is not consumed by the current executor. diff --git a/docs/en/develop/performance.md b/docs/en/develop/performance.md new file mode 100644 index 000000000..9ddd88536 --- /dev/null +++ b/docs/en/develop/performance.md @@ -0,0 +1,136 @@ +# Performance + +This page explains how StaticPHP-built PHP performs, what the build already optimizes for you, what you can tune yourself, and where the project's performance research is heading. It is written for two audiences at once: users who build PHP with StaticPHP and want a fast, fit-for-purpose binary, and contributors who want to understand or extend the optimization work. + +One caveat up front: no build configuration is fastest for every workload. Results depend on PHP version, CPU, toolchain, extensions, SAPI, libc, and runtime configuration. This page describes defaults and tradeoffs, not a universal recipe. + +## What You Get by Default + +A default StaticPHP build is already an optimized build. Without passing any extra flags you get: + +- **`-O3` optimization** for PHP and all dependency libraries, favoring runtime speed over compile time and code size. +- **Dead code elimination**: `-ffunction-sections -fdata-sections` combined with `--gc-sections` (Linux) or `-Wl,-dead_strip` (macOS) removes unused functions and data, keeping binaries smaller. +- **Cheaper ELF symbol handling** on Linux: `-fno-semantic-interposition` and `-fno-plt` let the compiler avoid indirection that only matters for interposable shared libraries. +- **Link hardening** on Linux: `relro`, `-z now`, `noexecstack`, `--as-needed`. +- **Link-time code generation on Windows**: StaticPHP rewrites the final CLI/CGI/micro/embed link rules to include `/LTCG`. +- **A lean PHP configuration**: only the SAPIs and extensions you request are built, and `--enable-re2c-cgoto` is enabled by default. +- **Stripped deployed binaries** with debug info preserved separately under `buildroot/debug/`. `--no-strip` keeps symbols *without* disabling optimization — a lesson learned from [issue #385](https://github.com/crazywhalecc/static-php-cli/issues/385), where v2's `--no-strip` silently selected `-O0` and made one Laravel test about 3× slower. + +The authoritative values live in `config/env.ini` (`SPC_DEFAULT_CFLAGS`, `SPC_DEFAULT_LDFLAGS`, `SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS`, and their per-platform variants). + +Frame pointers and `-g` are deliberately kept during compilation so profiling tools (perf, samply, Instruments) produce usable stacks; the deployed binary is stripped anyway, so this costs nothing at runtime. + +## Linux: Choosing Linkage and libc + +The Linux default is a **fully static musl binary** (`SPC_TARGET=native-native-musl` with the Zig toolchain): a single file that runs on any compatible kernel, with no shared extensions and no FFI. That is the right choice for distribution, but it is a *portability* choice first and a performance choice second. + +Three targets matter for performance work: + +| Target | libc / linkage | Shared extensions, FFI | Choose when | +|---|---|---|---| +| `native-native-musl` | musl, fully static | No | Default: portable single-file distribution | +| `native-native-musl -dynamic` | musl, dynamic | Yes | Experiments isolating linkage effects under musl | +| `native-native-gnu.2.17` | glibc, dynamic (2.17 baseline) | Yes | You need `.so` extensions, FFI, NSS, or your own benchmarks favor glibc | + +"Dynamic" here only means libc and the loader are dynamic — StaticPHP still links most libraries and extensions statically into the executable. Only the fully static musl target cannot load extensions at runtime. + +**Does linkage actually affect speed?** Less than most people assume. Measurements in [issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) showed static and dynamic builds within about 1% of each other on PHP 8.4. Static linking can shave loader and relocation time off very short CLI invocations; long-running FPM or FrankenPHP workers amortize that completely. Likewise, musl-versus-glibc differences only appear where a workload actually reaches libc — the allocator under heavy native allocation, threading under ZTS, DNS resolution, locale/iconv. Pure PHP code runs in the Zend VM and Zend Memory Manager, which mostly bypass the system allocator. + +Practical guidance: + +- **Default to musl static.** It is portable, predictable, and not measurably slower for typical PHP workloads. +- **Choose glibc dynamic** when you need shared extensions or FFI, or when your own benchmarks show a glibc advantage (glibc's tunable per-thread allocator caches can help allocation-heavy threaded workloads). +- **Benchmark with your real application.** Resolver behavior alone (musl queries nameservers in parallel; glibc traditionally sequentially) can dominate a "libc performance" comparison if your app does many DNS lookups. + +If you publish or compare numbers, verify linkage from the binary itself rather than filenames: + +```bash +file buildroot/bin/php +readelf -l buildroot/bin/php | grep 'Requesting program interpreter' +``` + +## Toolchains and PHP Version + +Compiler choice can matter more than any individual flag: + +- **Linux defaults to Zig** (`zig cc`, LLVM-based) because it provides flexible cross-targeting and reproducible musl/glibc selection — not because it always produces the fastest binary. Native GCC remains available as a maintainer-level option via `SPC_TOOLCHAIN`. +- **GCC vs Clang**: [issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) found large differences for some PHP 8.4 x86-64 tests because the older Zend VM can exploit GCC's global register variables. PHP 8.5's newer VM changes this picture — conclusions from one PHP version do not transfer automatically to the next. +- **macOS** uses system Clang by default; `SPC_USE_LLVM=brew` or `=port` selects a newer upstream LLVM from Homebrew or MacPorts as a coherent toolchain. This frees builds from Apple's release cycle and helps some PHP versions and workloads, but it is not a guaranteed uplift. +- **Windows** uses MSVC with `/LTCG` on the final link. FrankenPHP's CGO link goes through Clang/LLD, so some libraries deliberately avoid `/GL` — `/LTCG` on the link line does not mean every object participates in whole-program optimization. + +The compiler landscape per PHP version is one of the most underappreciated performance factors: upgrading PHP (or the compiler) can outperform weeks of flag tuning. + +## Tuning Your Own Build + +All defaults can be overridden reproducibly through `config/env.custom.ini` (or process environment variables). Values replace the complete default string, so you keep the baseline visible and change one factor at a time. + +### CPU instruction set level + +The most impactful user-level knob on Linux is the ISA baseline. Adding `-march=x86-64-v3` lets the compiler use AVX2 and friends: + +```ini +[linux] +SPC_DEFAULT_CFLAGS="-fPIC -O3 -pipe -fno-plt -fno-semantic-interposition -fstack-clash-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections -march=x86-64-v3" +SPC_DEFAULT_CXXFLAGS="${SPC_DEFAULT_CFLAGS}" +``` + +The tradeoff is portability: the binary will not run on CPUs below that level, and `-march=native` ties it to the build machine. [Issue #1088](https://github.com/crazywhalecc/static-php-cli/issues/1088) tracks a friendlier way to declare intrinsic levels across libraries and PHP instead of hand-managing flags. + +### Scope of overrides + +- **Everything**: `SPC_DEFAULT_CFLAGS` / `SPC_DEFAULT_CXXFLAGS` / `SPC_DEFAULT_LDFLAGS` +- **PHP and in-tree extensions only**: `SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS` (and CXX/LD variants) +- **One library**: snake-case variables such as `libaom_CFLAGS`, merged with the defaults by the common build executors + +### Remember: many "build performance" questions are runtime questions + +- **Opcache is a runtime feature.** Building `ext-opcache` only makes it available; `opcache.enable_cli`, JIT settings, and real application behavior determine what it does for you. `--disable-opcache-jit` changes build capability, not measured speed. +- **ZTS vs NTS**: FrankenPHP requires ZTS; everything else defaults to NTS. Historical tests showed small differences, specific to those workloads. +- **FrankenPHP** performance is dominated by worker mode and count, application boot behavior, Caddy modules, and Go runtime settings — no compiler flag fixes an unrepresentative server configuration. +- **UPX** (`--with-upx-pack`) is a size optimization that trades startup time, memory mapping, and security-tool behavior. It does not make PHP execute faster. + +## Ongoing Research + +Performance work in StaticPHP is active and incremental. The main directions: + +**PGO (Profile-Guided Optimization)** — [PR #1138](https://github.com/crazywhalecc/static-php-cli/pull/1138) is bringing v3-native PGO: instrument the build, train it on representative workloads, rebuild with the collected profiles. The hard parts are orchestration, not flags: per-SAPI profile directories, clean rebuilds when switching profiles, shutdown patches so profiles actually flush (Go/CGO processes bypass the libc `atexit` path), and invalidation when sources change. A naive `-fprofile-generate` in the global flags instruments every dependency library and may not even produce usable data, which is why PGO will arrive as a structured feature rather than an environment variable. Related: [PR #1142](https://github.com/crazywhalecc/static-php-cli/pull/1142) already uses FrankenPHP's bundled Go profile (`default.pgo`) for the Go/Caddy portion — that optimizes Go code only, not php-src against your application. The earlier one-size-fits-all proposal ([issue #862](https://github.com/crazywhalecc/static-php-cli/issues/862)) was closed because meaningful training has to be SAPI-aware and user-owned. + +**LTO (Link-Time Optimization)** — not enabled by default on Unix. In one environment measured in [issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838), the runtime gain was about 2% while ThinLTO doubled build time and full LTO multiplied it by seven. LTO also exposes archive incompatibilities, package bugs, and CGO linker limitations across dozens of dependency libraries, so the current direction is limited-scope LTO (PHP core and in-tree extensions, one toolchain family, consistent `-flto=thin`) rather than a global switch. Windows already uses `/LTCG` on final links as noted above. + +**Better flag plumbing** — flags propagate through `env.ini` → toolchain → package executors → PHP/FrankenPHP targets, but packages with hand-written compiler commands can still ignore parts of the global set. Making propagation uniform is ongoing work that benefits every other optimization. + +## How We Benchmark + +Performance claims in this project follow a reproducible methodology, centered on [issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) as the public notebook: + +- Compare builds that differ in **one factor**, holding PHP version, commit, extensions, SAPI, ZTS/NTS, INI, libc, and compiler constant. +- Alternate baseline and candidate on the **same machine**, with repetitions; medians and variation matter more than a single best run. +- Record full context: toolchain versions, `SPC_TARGET`, CPU model and frequency policy, extension set, env overrides, workload revision, concurrency, warmup, plus throughput/tail-latency/RSS/binary-size/build-time as applicable. +- Verify what was actually built (linkage via `file`/`readelf`, flags via build logs and `php -i`) rather than trusting invocation parameters. + +If you report a performance issue or regression, including this context makes it actionable. + +## History and Lessons + +| Record | Lesson | +|---|---| +| [Issue #385](https://github.com/crazywhalecc/static-php-cli/issues/385) | Debug symbols must never silently disable optimization; led to customizable PHP compiler variables. | +| [PR #806](https://github.com/crazywhalecc/static-php-cli/pull/806) | Zig toolchain added for target flexibility; discussion contains the static/dynamic ~1% observation. | +| [Issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) | The main performance notebook: compiler, ZTS/NTS, Opcache, LTO, VM, and architecture comparisons behind the `-O3` defaults. | +| [Issue #985](https://github.com/crazywhalecc/static-php-cli/issues/985) | v3 toolchain decision: predictable defaults first, performance-oriented alternatives available. | +| [Issue #862](https://github.com/crazywhalecc/static-php-cli/issues/862) | A universal PGO training script is unrealistic; training must be SAPI-aware and user-owned. | +| [PR #966](https://github.com/crazywhalecc/static-php-cli/pull/966) | Unified PHP make flags; decoupled stripping from optimization. | +| [PR #1142](https://github.com/crazywhalecc/static-php-cli/pull/1142) | FrankenPHP's bundled Go profile used in the build; distinct from application-trained PGO. | +| [Issue #1088](https://github.com/crazywhalecc/static-php-cli/issues/1088) | Open: declaring CPU intrinsic levels uniformly across libraries and PHP. | +| [PR #1138](https://github.com/crazywhalecc/static-php-cli/pull/1138) | v3-native PGO implementation in progress. | +| [PR #1150](https://github.com/crazywhalecc/static-php-cli/pull/1150) | Optimization flags must be target-format aware (an ELF-only flag broke macOS configure checks). | + +## Contributing + +Most of the performance investigation and implementation has been led by [@henderkes](https://github.com/henderkes), with review, integration, and testing from other contributors — and more help is welcome: + +- **Benchmarks**: reproducible measurements on real applications (especially FrankenPHP/FPM under load, allocation-heavy extensions, DNS-heavy workloads) are always valuable. Follow the methodology above and share results in [issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) or a new issue. +- **Platform coverage**: ARM servers, older glibc baselines, and Windows/MSVC behavior are less explored than Linux x86_64. +- **PGO and LTO**: testing [PR #1138](https://github.com/crazywhalecc/static-php-cli/pull/1138) on your workloads and reporting build or runtime issues directly shapes the feature. + +Because StaticPHP sits at the intersection of PHP, its dependency ecosystem, and multiple toolchains, findings here often surface upstream bugs — in php-src, in libraries, in Zig or LLVM. Reports and fixes that flow back upstream improve things for the wider PHP community, not just for static builds. diff --git a/docs/en/develop/php-src-changes.md b/docs/en/develop/php-src-changes.md index bb150ee87..c45557407 100644 --- a/docs/en/develop/php-src-changes.md +++ b/docs/en/develop/php-src-changes.md @@ -1,4 +1,148 @@ # PHP Source Modifications - +StaticPHP modifies the extracted PHP source tree to support static linking, additional SAPIs, older PHP branches, and platform-specific toolchains. These changes are applied to the generated workspace under `source/php-src`; the downloaded archive in `downloads/` is not modified. + +This page describes the patches shipped by the `core` registry. It is an implementation inventory for maintainers, not a promise that `SourcePatcher`, lifecycle callback signatures, or individual patch names are stable public APIs. + +## When Modifications Are Applied + +Changes enter the source tree at several points: + +1. **After `php-src` extraction**: artifact hooks apply compatibility changes before any package is built. +2. **Before `buildconf` / configure**: the PHP target applies the versioned patch set and rewrites build-system inputs. +3. **Before an individual SAPI or extension build**: conditional hooks patch generated Makefiles or extension sources. +4. **Temporarily around a stage**: a small number of changes are backed up and restored after that SAPI has been built. + +Methods annotated with `#[PatchDescription]` emit a `[PATCH]` message when StaticPHP invokes them through its lifecycle/DI dispatcher. This is useful for tracing a build, but it is not a complete machine-readable manifest: direct method calls do not pass through that logger, and a few source rewrites and generated-file changes are not annotated. + +## Changes Applied After Extraction + +`src/Package/Artifact/php_src.php` runs whenever a fresh `php-src` artifact is extracted. + +| Source or condition | Modification | +|---|---| +| PHP older than 8.0 | Applies the legacy stream-cast compatibility patch used by older Alpine/musl builds | +| PHP 8.0.x | Applies the libxml2 2.12 compatibility patch to DOM, libxml, and SOAP, plus the legacy stream-cast patch | +| PHP 8.1.x | Applies the legacy stream-cast patch | +| GD on all supported PHP branches | Replaces `ext/gd/config.w32` with StaticPHP's version-specific static-build configuration; PHP older than 8.2 also receives the `gdft.c` `_WIN32` compatibility fix | +| Bundled IMAP source without a license file | Adds the bundled Apache license text as `ext/imap/LICENSE` for license collection | + +The GD and IMAP operations edit files that only matter when those extensions are present or built. The extraction hooks themselves run as part of extracting PHP source, before extension resolution reaches the build stage. + +## Versioned PHP Patch Set + +Before `buildconf`, both Unix and Windows builds call `SourcePatcher::patchPhpSrc()`. Despite its historical “micro patches” name, this happens for the PHP build in general; selecting `php-micro` is not required. + +The default `SPC_MICRO_PATCHES` values from `config/env.ini` are: + +| Host platform | Default patch names | +|---|---| +| Linux | `cli_checks`, `disable_huge_page` | +| macOS | `cli_checks`, `macos_iconv` | +| Windows | `static_extensions_win32`, `cli_checks`, `disable_huge_page`, `vcruntime140`, `win32`, `zend_stream`, `cli_static`, `win32_api` | + +Their current purposes are: + +| Patch | Purpose | +|---|---| +| `cli_checks` | Treat the micro SAPI like CLI in PHP code paths that otherwise compare the SAPI name with `cli` | +| `disable_huge_page` | Disable the Linux 2 MiB segment-alignment probe, avoiding unnecessary growth of SFX binaries | +| `macos_iconv` | Make the PHP iconv check link with the macOS iconv library correctly | +| `static_extensions_win32` | Adjust Windows extension configuration, including Fileinfo and OpenSSL, for static builds | +| `cli_static` | Adjust the Windows CLI source for a fully static executable | +| `vcruntime140` | Bypass the startup check for a dynamically loaded `vcruntime140` module | +| `win32` | Switch the PHP Windows build configuration from the dynamic MSVC runtime (`/MD`) to the static runtime (`/MT`) | +| `zend_stream` | Avoid a conflicting `isatty` declaration with the static Windows CRT | +| `win32_api` | Correct Windows API declarations needed by supported PHP branches | + +Patch files live in `src/globals/patch/php-src-patches/`. A name may have an unversioned file such as `zend_stream.patch`, or versioned files such as `cli_checks_84.patch`. For a versioned name, StaticPHP chooses the newest available PHP minor less than or equal to the source minor. For example, an 8.2 source uses an `_81` patch when `_81` and `_84` are available but `_82` is not. PHP 7.4 skips this patch-set loader. + +The directory also contains non-default or conditionally selected patches such as `comctl32`, `phar`, and `static_opcache`. A missing compatible patch or a failed `patch -p1` application stops the build with `PatchException`. + +## Common Build-Time Changes + +The PHP target applies two changes independently of the platform-specific tables below. + +### StaticPHP Version Metadata + +Before the PHP `build` stage, StaticPHP adds an INI entry named `StaticPHP.version` to `main/main.c` when the source does not already contain it. This makes the StaticPHP version that produced the binary available at runtime. + +### Hardcoded INI Values + +Each `--with-hardcoded-ini=key=value` option adds a literal line to `HARDCODED_INI` in every available supported SAPI source: + +- `sapi/cli/php_cli.c` +- `sapi/micro/php_micro.c` +- `sapi/embed/php_embed.c` + +The original files are saved with a `.bak` suffix. When a later invocation supplies another non-empty set, StaticPHP restores the backups before applying that set, so replacing one configured set with another does not accumulate old values. Completely omitting all `--with-hardcoded-ini` options currently does not run the restore path; re-extract `php-src` to remove previously injected values. + +::: warning +Hardcoded values become part of the source and resulting binary. Do not use this option for passwords, tokens, or other secrets. +::: + +## Unix Build-Time Changes + +Linux and macOS builds apply the following target-aware changes: + +| Condition | File or generated input | Modification | +|---|---|---| +| Every Unix PHP build | `configure.ac` | Replaces host `ldd`-based musl detection with the libc selected by `SPC_TARGET`; the original file is backed up | +| Every Unix PHP build | `build/php.m4` and extension `config.m4` files | Replaces `PKG_CHECK_MODULES` with `PKG_CHECK_MODULES_STATIC` so dependency checks include private static link requirements | +| PHP 8.3.x | `build/php.m4` | Applies the AVX-512 configure-cache compatibility patch used before PHP 8.4 | +| PHP older than 8.3 | generated `configure` | Uses the supported `-std=gnu17` probe instead of the newer `-std=gnu23` probe | +| Dynamically linked musl target | `TSRM/TSRM.h` | Gives the main TSRM TLS cache symbol default visibility so shared extensions using the `initial-exec` TLS model can load | +| Linux | generated `Makefile` | Normalizes accidental `//lib` paths to `/lib` | +| Linux with Zig | generated `Makefile` | Uses the host `cc` for `BUILD_CC` instead of `zig-cc`, preventing host tools such as minilua from being built for the target environment | +| A release linker flag is present | `ext/standard/info.c` | Hides the configure command from PHP information output | + +When `php-micro` is resolved, its package hooks also adjust the generated Makefile so the embed prerequisite builds only `libphp.la` and the main PHP install step does not install the micro binary prematurely. + +If `ext-phar` is selected for a Unix micro build, `ext/phar/phar.c` is temporarily changed so a compressed PHAR appended to the current executable can be recognized even when the executable name does not contain `.phar`. The original file is restored immediately after the micro stage. + +## Windows Build-Time Changes + +Windows requires more extensive build-system changes because PHP's upstream Windows flow normally assumes a dynamic CRT, PHP SDK tools, and import-library-based SAPIs. + +| Stage or condition | Modification | +|---|---| +| Before `buildconf.bat` | Removes the shared `dllmain.c` object from the relevant Windows core build rule, then applies the versioned patch set | +| Missing `win32/wsyslog.h` | Generates a compatibility header with the event IDs defined by `win32/build/wsyslog.mc` | +| PHP 8.1.x | Moves the Fiber assembly objects from linker flags to the assembly object list so they participate in static linking | +| Detected Visual Studio | Rewrites `win32/build/confutils.js` so supported Visual Studio installations report the correct PHP toolset name | +| After `buildconf.bat` | Disables the generated `configure.js` PHP SDK version check because v3 uses MSVC plus its managed MSYS2 environment instead of PHP SDK binary tools | +| `--enable-micro-win32` | Defines `PHP_MICRO_WIN32_NO_CONSOLE` in `sapi/micro/php_micro.c`; a backup is restored when the option is no longer active | +| CLI build | Rewrites the generated `php.exe` rule to link PHP core, the CLI SAPI, static extensions, assembly objects, and static libraries directly; also places `buildroot/include` flags before extension flags | +| CGI build | Rewrites the generated `php-cgi.exe` rule for static linking and removes the duplicate `ZEND_TSRMLS_CACHE_DEFINE()` from the CGI source | +| Micro build | Adds the Fiber assembly prerequisites needed by the generated micro target and supplies the static library set | +| Embed build | Rewrites the generated embed target so `phpNembed.lib` (for example, `php8embed.lib`) contains the embed SAPI, PHP core, static extensions, and assembly objects instead of being only an import library | + +The final embed change is also what makes the Windows FrankenPHP build possible: FrankenPHP links the self-contained embed library through CGO with Clang/LLD. Unix FrankenPHP does not apply a separate php-src patch; it consumes the Unix `php-embed` library produced by the normal PHP target. + +## Conditional Extension Patches + +Extension package classes may modify their source under `php-src/ext/` when that extension is resolved. Two cross-cutting examples are: + +- **Opcache**: `ext-opcache` applies a version-specific static Opcache patch for PHP 8.0–8.4.x. It uses dedicated legacy patches before PHP 8.2.23 and PHP 8.3.11, and otherwise uses the versioned `static_opcache` patch. PHP 8.5 and later support the required static path without this patch. +- **PHAR for micro**: the Unix micro hook described above is applied only when `ext-phar` participates in the build and is reversed after the micro binary is produced. + +Other extension classes contain narrower compatibility changes for particular PHP, compiler, dependency-library, or operating-system versions. They are deliberately kept next to that extension's build logic in `src/Package/Extension/`, rather than added to the global PHP patch set. Search for `#[PatchDescription]` to review the current annotated inventory. + +## Patch Application and Recovery + +`SourcePatcher::patchFile()` resolves relative patch names under `src/globals/patch/`, checks whether the patch is already applied with a reverse dry run, and then invokes `patch --binary -p1`. This makes file-based patches safe to encounter again in the same extracted tree. Direct string replacements are written so repeated invocation normally finds no original text to replace. + +There is no transaction that restores the whole PHP tree after a build. Only explicitly temporary changes, such as the PHAR and hardcoded-INI backups, have dedicated restoration logic. To return to pristine upstream source, remove/re-extract `source/php-src`, or use `switch-php-version` without `--keep-source` when changing or refreshing the PHP version. + +The v2 arbitrary patch-point injection mechanism is not a supported v3 interface. Until a public patch extension contract is defined, new core patches should be implemented as maintainer-owned artifact or package hooks. + +## Adding or Updating a Core Patch + +When maintaining a built-in patch: + +1. Prefer an upstream fix; keep a downstream patch only while supported PHP branches still need it. +2. Put reusable patch files in `src/globals/patch/`; put the version-selected micro/static-PHP set in `src/globals/patch/php-src-patches/`. +3. Use an exact PHP/platform/dependency condition so unrelated builds do not mutate their sources unnecessarily. +4. Make the operation repeatable and fail loudly when the expected upstream context is no longer present. +5. Add `#[PatchDescription]` to a method invoked through the lifecycle/DI dispatcher so the change is visible in build logs; log direct calls explicitly. +6. Test the oldest and newest PHP branches affected by the patch, and update both language versions of this page when behavior changes. diff --git a/docs/en/develop/system-build-tools.md b/docs/en/develop/system-build-tools.md index a5ac6edfe..3459e33b1 100644 --- a/docs/en/develop/system-build-tools.md +++ b/docs/en/develop/system-build-tools.md @@ -1,4 +1,174 @@ # Compilation Tools - +StaticPHP builds PHP, libraries, extensions, and helper tools by invoking native build systems. The environment is split into three layers: + +1. **Host tools** such as `autoconf`, `cmake`, `make`, `patch`, and parsers/code generators. +2. **Compiler toolchain** such as Zig, GCC, Clang, or MSVC. The selected toolchain provides the compiler, linker, archiver, and target-specific environment. +3. **Package build executors** that apply StaticPHP's common Autoconf or CMake arguments and run the package build. + +This distinction matters when troubleshooting: having `cmake` installed does not mean that the selected compiler toolchain is initialized, and having a compiler does not mean that all source-generation tools are present. + +## Checking the Environment + +Run `doctor` after installing StaticPHP and after changing the build target or toolchain: + +```bash +# Pre-built binary +./spc doctor + +# Source installation +bin/spc doctor +``` + +To let StaticPHP install supported missing items, use: + +```bash +./spc doctor --auto-fix +``` + +On Linux and macOS, automatic fixes may invoke the system package manager and may require `sudo`. On Windows, StaticPHP can install its own helper packages, but it cannot install Visual Studio or Git for you. See the [`doctor` command reference](/en/guide/cli-reference#doctor) for its command-line options. + +Doctor checks are target-aware. For example, Zig and musl-related checks only run when the selected toolchain and target require them, and a Homebrew or MacPorts LLVM check only runs when that LLVM variant is selected. + +## What the Tools Are Used For + +| Tool group | Examples | Role in a build | +|---|---|---| +| Compiler and binutils | `zig`, `gcc`, `g++`, `clang`, `cl.exe`, `ld`, `link.exe`, `ar`, `lib.exe`, `ranlib` | Compile and link PHP and dependency libraries | +| Autotools | `autoconf`, `automake`, `libtoolize` / `glibtoolize`, `autopoint` | Generate `configure` scripts and supporting files | +| Make tools | `make`, `nmake.exe` | Execute generated Makefiles | +| CMake | `cmake` | Configure and build CMake-based packages | +| Source generators | `bison`, `re2c`, `flex`, `gperf` | Regenerate parsers, lexers, and generated C/C++ sources | +| Dependency metadata | `pkg-config` | Read `.pc` files and return static compile/link flags | +| Source and archive tools | `git`, `patch`, `tar`, `unzip`, `gzip`, `bzip2`, `xz`, `7za.exe` | Fetch, patch, and extract source archives | +| Package-specific tools | `nasm`, Perl, Go | Build packages such as OpenSSL or FrankenPHP when selected | + +Not every package uses every tool. Doctor verifies the common host baseline and target-aware prerequisites; an individual package may also resolve a tool package such as Go, NASM, or Perl when selected. + +## Linux + +`LinuxToolCheck` detects the distribution from `/etc/os-release`, selects a package-family baseline, and checks the command (or file) provided by each package. Its current baselines are: + +| Distribution family | Checked packages or commands | +|---|---| +| Alpine | `make`, `bison`, `re2c`, `flex`, `gperf`, `git`, `autoconf`, `automake`, `gettext-dev`, `tar`, `unzip`, `gzip`, `bzip2`, `cmake`, `gcc`, `g++`, `patch`, `binutils-gold`, `libtoolize`, `which` | +| Debian / Ubuntu and the fallback baseline | `make`, `bison`, `re2c`, `flex`, `gperf`, `git`, `autoconf`, `automake`, `autopoint`, `tar`, `unzip`, `gzip`, `gcc`, `g++`, `bzip2`, `cmake`, `patch`, `xz`, `libtoolize`, `which` | +| RHEL / Fedora | `perl`, `make`, `bison`, `re2c`, `flex`, `gperf`, `git`, `autoconf`, `automake`, `tar`, `unzip`, `gzip`, `gcc`, `g++`, `bzip2`, `cmake`, `patch`, `which`, `xz`, `libtool`, `gettext-devel`, `file` | +| CentOS | The RHEL baseline plus the Perl `IPC::Cmd` and `Time::Piece` modules | +| Arch / Manjaro | `base-devel`, `cmake`, `gperf` | + +Doctor additionally checks: + +- CMake is at least version 3.22. +- `re2c` is at least version 1.0.3. +- `linux-headers` is available on a musl distribution. +- StaticPHP's isolated `pkg-config` is installed and executable. + +The Linux default is `ZigToolchain`. When `--auto-fix` is enabled, Doctor can install the StaticPHP `zig` tool package when it is missing. When a musl target is built on a non-musl host, Doctor also checks the musl runtime/wrapper required by that target. The legacy `MuslToolchain` additionally expects the musl cross toolchain under `/usr/local/musl`. A native Alpine build does not need these cross-host components. + +::: tip +The target string and the host distribution are different concepts. A glibc distribution can produce a musl target through Zig, while a `*-gnu.*` target remains dynamically linked to the requested glibc ABI. +::: + +## macOS + +The default compiler toolchain is the system Clang supplied by Xcode or Xcode Command Line Tools. StaticPHP also requires either Homebrew or MacPorts to provide the remaining build tools. + +Doctor currently checks these commands: + +```text +curl, make, bison, re2c, flex, gperf, pkg-config, git, +autoconf, automake, tar, libtool, unzip, xz, gzip, bzip2, +cmake, glibtoolize +``` + +It also requires GNU Bison 3 or later. If `/usr/bin/bison` is too old, Doctor searches the Homebrew and MacPorts locations and can install a newer package. On Apple Silicon, a Homebrew installation used by StaticPHP must be the native installation under `/opt/homebrew`, not an Intel installation under `/usr/local`. + +`SPC_USE_LLVM` controls the Clang variant: + +| Value | Toolchain | Compiler location | +|---|---|---| +| `system` (default) | `ClangNativeToolchain` | Xcode / Command Line Tools `clang` | +| `brew` | `ClangBrewToolchain` | Homebrew `opt/llvm/bin` | +| `port` | `ClangPortsToolchain` | MacPorts `bin` (normally `/opt/local/bin`) | + +When `brew` or `port` is selected, Doctor verifies that the corresponding LLVM installation contains `clang`. + +## Windows + +Windows builds currently target x86-64 and use two kinds of prerequisites. + +The following components must be installed by the user: + +- Visual Studio with the x86/x64 C++ tools component. +- Git for Windows, with `patch.exe` available on `PATH` (normally from `C:\Program Files\Git\usr\bin`). + +Doctor can install these StaticPHP-managed baseline packages: + +| Package | Purpose | +|---|---| +| `vswhere` | Locate the newest Visual Studio installation that contains `Microsoft.VisualStudio.Component.VC.Tools.x86.x64` | +| `msys2-build-essentials` | Provide MSYS2 plus `make`, Autotools, `libtool`, `pkgconf`, Perl, Bison, and `re2c` | +| `7za-win` | Extract formats that need `7za.exe` | + +Additional tools such as `nasm` and `strawberry-perl` are also represented as Tool packages, but they are installed when a selected package declares them through `tools`; they are not unconditional Windows Doctor checks. + +`MSVCToolchain` calls the detected Visual Studio `vcvarsall.bat x64` and imports its environment. Doctor then verifies `cl.exe`, `link.exe`, `lib.exe`, `dumpbin.exe`, `msbuild.exe`, and `nmake.exe`. The imported environment is cached in `downloads/.vcenv-cache` for up to one hour. + +The MSYS2 root defaults to `pkgroot/.../msys2-build-essentials/msys64`. It can be changed with `SPC_MSYS2_PATH`; the value must point to the `msys64` directory. The old v2 PHP SDK directory and Visual Studio version options are not used in v3. + +### LLVM/Clang for FrankenPHP + +The normal Windows PHP and library build uses MSVC. FrankenPHP is the exception: its CGO link step requires Clang and LLD. + +StaticPHP first accepts an absolute Clang path from `CC`. Otherwise it looks for the Visual Studio LLVM installation at `VC\Tools\Llvm\x64\bin\clang.exe` using `vswhere`. Install the **C++ Clang tools for Windows** component in Visual Studio Installer when building FrankenPHP. This Clang requirement does not replace the MSVC requirement for the PHP and dependency-library stages. + +## Compiler Toolchain Selection + +`GlobalEnvManager` loads `config/env.ini` (and an optional `config/env.custom.ini`) before `ToolchainManager` initializes the selected toolchain. The normal selection is: + +| Host OS | Default | Other implementations currently present | +|---|---|---| +| Linux | `ZigToolchain` | `GccNativeToolchain`, `ClangNativeToolchain`, `MuslToolchain` | +| macOS | `ClangNativeToolchain` | `ClangBrewToolchain`, `ClangPortsToolchain` | +| Windows | `MSVCToolchain` | None | + +On Unix, the toolchain supplies defaults for `CC`, `CXX`, `AR`, `RANLIB`, and `LD`. Values already set by the caller take precedence over defaults from `env.ini`. `SPC_TARGET` determines the target OS/libc/ABI, while `SPC_USE_LLVM` chooses the macOS Clang distribution. `SPC_TOOLCHAIN` can override the implementation class, but it is currently a maintainer-level/internal setting rather than a stable public extension contract. + +After environment initialization, the toolchain validates its commands and target/libc combination. It also records a compiler description in `PHP_BUILD_COMPILER` when one can be determined. + +## Isolated pkg-config + +Unix builds deliberately use a StaticPHP-managed `pkg-config`, rather than whichever executable happens to be first on the system `PATH`. `PkgConfigUtil` searches, in order: + +1. `pkgroot//bin/pkg-config` +2. `buildroot/bin/pkg-config` + +Doctor can install a pre-built copy through the `pkg-config` tool package. If a source build is needed, StaticPHP builds pkg-config with its internal GLib and disables the system include, library, sysroot, and default `.pc` search paths. + +During a build, `PKG_CONFIG_PATH` starts with `buildroot/lib/pkgconfig`, and StaticPHP's dependency queries and generated CMake toolchain file request `--static` flags. This prevents an unrelated system library from silently satisfying a dependency that StaticPHP intended to build itself. + +## Package Build Executors + +Package classes use three common executor implementations: + +| Executor | Platform and build system | Current behavior | +|---|---|---| +| `UnixAutoconfExecutor` | Linux/macOS, Autoconf | Adds static/PIC and `buildroot` prefix arguments, runs `configure`, parallel `make`, and optionally `make install` | +| `UnixCMakeExecutor` | Linux/macOS, CMake | Generates a toolchain file rooted at `buildroot`, pins StaticPHP's compilers and pkg-config, disables shared libraries, then configures, builds, and installs | +| `WindowsCMakeExecutor` | Windows, CMake/MSVC | Uses the x64 Visual Studio generator, the static MSVC runtime, a StaticPHP CMake toolchain file, and `cmake --build --target install` | + +The executors initialize package-specific include, library, and environment paths and use the configured build concurrency. Unix Autoconf failures preserve `config.log`, and the Unix CMake executor copies available configure/error/output logs to `SPC_LOGS_DIR`. The Windows CMake executor currently relies on the general shell and output logs instead of collecting those CMake files separately. + +These classes are useful landmarks when maintaining built-in packages, but their constructors and fluent methods are not yet declared a stable PHP API. Package authors should follow existing `src/Package/Library` or `src/Package/Tool` implementations until the public extension API is defined. + +## Diagnosing Tool Failures + +When Doctor passes but a package still fails: + +1. Run `spc dev:shell` (or `bin/spc dev:shell`) and check `CC`, `CXX`, `AR`, `LD`, `PKG_CONFIG`, and `PATH`. +2. Confirm that `SPC_TARGET` describes the intended libc and ABI. +3. Read `log/spc.output.log`, `log/spc.shell.log`, and any package-specific Autoconf/CMake logs. +4. On Windows, re-run Doctor if Visual Studio, MSYS2, or LLVM components changed; remove the one-hour `downloads/.vcenv-cache` only when a stale Visual Studio environment is suspected. + +Do not work around a missing tool by copying binaries into `buildroot/` or editing generated build directories. Add it as a host prerequisite, a StaticPHP tool package, or package build dependency according to its role. diff --git a/docs/en/guide/env-vars.md b/docs/en/guide/env-vars.md index bb5425936..d4dabddde 100644 --- a/docs/en/guide/env-vars.md +++ b/docs/en/guide/env-vars.md @@ -21,6 +21,7 @@ You can read the comments for each parameter in [config/env.ini](https://github. Generally, you don't need to modify any of the following environment variables as they are already set to optimal values. However, if you have special needs, you can set these environment variables to meet your needs (for example, you need to debug PHP performance under different compilation parameters). +See [Performance](../develop/performance) for the current compiler defaults, propagation rules, and benchmarking cautions before changing optimization flags. If you want to use custom environment variables, you can use the `export` command in the terminal or set the environment variables directly before the command, for example: @@ -53,4 +54,3 @@ SPC_CONCURRENCY=4 [linux] SPC_DEFAULT_CFLAGS="-O3" ``` - diff --git a/docs/en/guide/sapi-reference.md b/docs/en/guide/sapi-reference.md index 0d8dddadc..05c43ed79 100644 --- a/docs/en/guide/sapi-reference.md +++ b/docs/en/guide/sapi-reference.md @@ -273,5 +273,6 @@ If you installed from source, you can also set `SPC_TARGET=native-native-gnu.2.1 This uses the Zig toolchain to produce a partially static binary dynamically linked against glibc 2.17, compatible with most modern GNU/Linux distributions. No Docker and no extra cross-compilation toolchain are required. The resulting binary supports `dl()`, FFI, and loading `.so` extensions at runtime, but cannot run on musl-based systems such as Alpine Linux. -**Windows** — PHP extensions on Windows are distributed as `.dll` files that depend on the DLLs bundled with the official dynamically-built PHP. StaticPHP produces a standalone static executable that does not include those DLLs, so dynamic extension loading is not possible on Windows. All extensions must be compiled in statically at build time. +For the performance and deployment tradeoffs between the default static-musl target, dynamic musl, and dynamic glibc, see [Performance](../develop/performance#linux-choosing-linkage-and-libc). +**Windows** — PHP extensions on Windows are distributed as `.dll` files that depend on the DLLs bundled with the official dynamically-built PHP. StaticPHP produces a standalone static executable that does not include those DLLs, so dynamic extension loading is not possible on Windows. All extensions must be compiled in statically at build time. diff --git a/docs/zh/contributing/index.md b/docs/zh/contributing/index.md index 385897116..9e59f7acd 100644 --- a/docs/zh/contributing/index.md +++ b/docs/zh/contributing/index.md @@ -1,5 +1,132 @@ # 贡献指南 - +StaticPHP 欢迎修复、Package 定义、平台支持、测试和文档贡献。v3 的部分扩展 API 仍在定型,因此需要区分对内置 `core` Registry 的贡献,以及依赖长期兼容契约的外部 Registry。 + +## 开发环境 + +检出源码需要 PHP 8.4 或更高版本及 Composer。CLI 还需要[源码安装指南](/zh/guide/installation)中列出的 PHP 扩展。在仓库根目录安装开发依赖: + +```bash +composer install +bin/spc --version +``` + +只有修改 VitePress 文档时才需要 Node.js 和 npm: + +```bash +npm install +npm run docs:build +``` + +尝试真实构建前,请运行 `bin/spc doctor`。完整静态构建依赖平台且可能耗时较长,因此不是每个纯文档、测试或配置改动的前置条件。 + +## 选择正确的层级 + +应把改动放在能够表达需求的最小层级: + +| 区域 | 用途 | +|---|---| +| `config/pkg/ext/` | PHP 扩展 Package 定义 | +| `config/pkg/lib/` | 链接期依赖库定义 | +| `config/pkg/target/` | 最终和虚拟构建 target | +| `config/pkg/tool/` | 宿主机侧构建工具 Package | +| `config/artifact/` | 共享或复杂的源码/二进制 Artifact 定义 | +| `src/Package/` | Package 专属 recipe、patch、hook 和自定义 Artifact 行为 | +| `src/StaticPHP/` | 框架、resolver、runtime、Doctor、Registry 和命令实现 | +| `tests/` | PHPUnit 测试和 fixture | +| `docs/en/`、`docs/zh/` | 英文规范文档及其同步的中文版本 | + +优先使用声明式 YAML 字段,不要用 PHP 条件代替。只有 Package 需要构建命令、源码改写、校验、自定义 configure 参数或 lifecycle hook 时,才增加 recipe class。 + +不要通过编辑生成的 `buildroot/`、`source/`、`downloads/` 或 `pkgroot/` 内容修复问题。这些改动会被丢弃,也无法说明其他用户如何复现构建。 + +## 添加或更新 Package + +编写新定义前,先搜索使用相同构建系统和 Artifact 类型的 Package,沿用其结构,然后检查: + +1. 选择正确类型:`php-extension`、`library`、`target`、`virtual-target` 或 `tool`。 +2. 使用精确的 Package 名。PHP 扩展依赖和配置使用 `ext-` 前缀。 +3. 硬依赖放在 `depends`,可选关系放在 `suggests`,宿主机构建工具放在 `tools`。 +4. Schema 支持时,通过 `@windows`、`@unix`、`@linux` 或 `@macos` 字段表达平台差异。 +5. 单个 Package 的简单源码优先使用内联 Artifact;多个 Package 共用,或自定义行为更适合独立定义时,使用 `config/artifact/`。 +6. 在 Artifact metadata 中记录许可证标识和文件。只有需要在源码构建后复制许可证内容时,才增加结构化 Package `license` 条目。 +7. 只有配置不足以表达行为时,才增加或修改相应 PHP recipe。 + +当前 schema 参见 [Package 模型](/zh/develop/package-model)和 [Artifact 模型](/zh/develop/artifact-model)。 + +配置校验会严格拒绝未知字段,但无法证明 URL 存在、patch 仍可应用,或库能在所有平台链接。条件允许时,应测试受影响的最旧和最新 PHP 分支,以及本次改动涉及的平台。 + +## PHP 代码和测试 + +StaticPHP 遵循仓库现有代码风格,并优先沿用现有 Package/构建模式,而不是增加新抽象。修改框架代码时: + +- 内部编排细节应保持为内部实现;PHP 的 `public` 可见性本身不代表受支持的扩展 API。 +- 为 resolver、config、Registry、命令或工具行为增加针对性的 PHPUnit 测试。 +- 把失败输入或回归场景写入测试,不要只依赖一次手动构建。 +- 不要在同一 PR 中混入无关格式化或机械改动。 + +测试位于 `tests/StaticPHP/`,使用 `Tests\StaticPHP\` namespace。开发时先运行最小相关测试,提交前再执行项目检查。 + +## Doctor 改动 + +内置 Doctor check 当前位于 `src/StaticPHP/Doctor/Item/`。Check 应只观察环境,不修改它;安装、下载和文件写入应放在单独的 fix callback 中。 + +当前 Attribute 和 callback 签名属于内部实现,并有原始 fix 参数、修复后不自动重查等已知限制。维护内置检查时可以沿用现有 core check,但不能把该模式描述为稳定的第三方 API。用户可见的 check、fix、前置条件或 lock 行为变化时,应更新 [Doctor 环境检查](/zh/develop/doctor-module)和[编译工具](/zh/develop/system-build-tools)。 + +## 文档 + +`docs/en/` 下的英文是规范版本。每个英文 Markdown 文件都必须在 `docs/zh/` 下有对应文件;两个版本必须拥有相同的标题、示例、表格、admonition,并链接到相应语言路径。 + +行为变化时,应更新所有描述该行为的页面,而不只是最接近的参考页。常见情况包括 CLI 选项、环境变量、Package/Artifact 字段、Doctor 检查和迁移说明。 + +验证文件树一致并构建站点: + +```bash +diff <(find docs/en -name '*.md' | sed 's|docs/en/||' | sort) \ + <(find docs/zh -name '*.md' | sed 's|docs/zh/||' | sort) +npm run docs:build +``` + +新增页面时,还必须同步加入两个 VitePress sidebar。 + +## 验证清单 + +根据改动范围运行相应检查: + +| 改动 | 必需检查 | +|---|---| +| Package、Artifact、target、extension、library 或 tool 配置 | `bin/spc dev:lint-config`、`composer cs-fix` | +| PHP 框架或 recipe 代码 | `composer cs-fix`、`composer test`、`composer analyse` | +| 文档 | 中英文文件树和结构检查、`npm run docs:build` | +| 平台构建修复 | 上述检查,加上条件允许时最小的受影响构建或 CI matrix | + +常用命令: + +```bash +bin/spc dev:lint-config +composer cs-fix +composer test +composer analyse +``` + +本地无法运行完整构建时,应说明哪些平台/PHP 组合尚未测试,并附相关日志或 CI 结果。构建和 shell 日志默认位于 `log/`,但通常不应提交生成的日志。 + +## Pull Request + +每个 Pull Request 应保持聚焦,并说明: + +- 问题是什么,以及为什么选择当前修改层级。 +- 受影响的 Package/Artifact、目标 OS、架构、toolchain 和 PHP 版本。 +- 用户可见行为或兼容性影响。 +- 已运行的测试和构建,包括尚未测试的部分。 +- 已同步更新的两种语言文档。 + +完成仓库的 Pull Request checklist。不要提交生成的构建目录、下载归档、缓存、本地环境文件或无关的编辑器/操作系统元数据。 + +新增依赖或随附 patch 时,应包含上游来源、许可证和需要该内容的条件。优先推动上游修复,并将下游 patch 限制在受影响版本。 + +## 安全问题报告 + +不要在普通 issue 中公开漏洞利用细节、凭据、签名材料或可复现的漏洞。仓库支持时请使用 GitHub private vulnerability reporting,或通过现有项目渠道联系维护者,安排私下披露。 + +仓库目前没有专用 `SECURITY.md`;补充正式的支持版本和披露策略仍是项目维护事项。 diff --git a/docs/zh/develop/doctor-module.md b/docs/zh/develop/doctor-module.md index 1290f8753..bbe34f7c2 100644 --- a/docs/zh/develop/doctor-module.md +++ b/docs/zh/develop/doctor-module.md @@ -1,4 +1,115 @@ # Doctor 环境检查 - +Doctor 用于诊断当前宿主机、选定 target 和编译器 toolchain 能否运行 StaticPHP 构建流水线。它会检查通用宿主机命令、平台专属 SDK、受 StaticPHP 管理的 Tool Package,以及 Zig、musl 等与所选目标相关的前置条件。 + +## 运行 Doctor + +安装 StaticPHP、修改 target/toolchain 或更新系统构建工具后,应显式运行 Doctor: + +```bash +# 预构建二进制 +./spc doctor + +# 源码安装 +bin/spc doctor +``` + +通过 `--auto-fix`(`-y`)选择修复策略: + +| 调用方式 | 当前行为 | +|---|---| +| `spc doctor` | 每个可自动修复的项目执行前都询问 | +| `spc doctor --auto-fix` | 不询问,直接执行可用修复 | +| `spc doctor --auto-fix=never` | 报告第一个未解决的失败,不尝试修复 | + +只有每个适用检查都通过、被跳过,或者返回了当前实现认为成功的修复时,Doctor 才会成功退出。它会在第一个未解决的失败处停止。 + +`craft` 命令默认也会运行 Doctor,并使用不询问的自动修复策略;可以在 `craft.yml` 中设置 `craft-options.doctor: false` 禁用该步骤。 + +## 执行模型 + +当前执行顺序如下: + +1. `DoctorLoader` 收集内置 class 和 Registry 提供的 class,扫描其 public method 上的 Doctor Attribute。 +2. 按 `level` 从高到低排序检查。 +3. 通过 `#[OptionalCheck]`、`limit_os` 和 `SPC_SKIP_DOCTOR_CHECK_ITEMS` 排除不适用检查。 +4. 直接调用 check callback。 +5. `null` 表示跳过;`CheckResult::ok()` 表示通过;`CheckResult::fail()` 描述失败,并可指定一个 fix。 +6. 根据修复策略,Doctor 会拒绝、询问或立即通过 `ApplicationContext` 调用指定 fix。 + +`checkAll()` 采用 fail-fast:它不会收集全部失败后再输出汇总。Fixer 成功后,当前实现也不会重新运行原 check。 + +::: warning +无效的 check 返回值当前会显示“Skipped due to invalid return value”,并被当作成功。这是实现限制,扩展作者不应依赖该行为。 +::: + +## 内置检查分组 + +实际检查清单随所选 target 变化。当前 core Registry 提供以下分组: + +| 范围 | 检查内容 | +|---|---| +| 通用 | 至少加载了一个 Registry;宿主 OS 是 Linux、macOS 或 Windows | +| Linux | 发行版构建工具基线、CMake 3.22+、`re2c` 1.0.3+,以及 musl 发行版的 Linux headers | +| macOS | Homebrew 或 MacPorts、必需构建命令、GNU Bison 3+,以及选定的 Homebrew/MacPorts LLVM 变体 | +| Windows target | `vswhere`、Visual Studio C++ 工具、Git `patch.exe`、受管理的 MSYS2、`7za.exe` 和初始化后的 MSVC 命令环境 | +| Unix target | 由 StaticPHP 管理且可以正常执行的 `pkg-config` | +| Zig toolchain | 已安装 StaticPHP 的 `zig` Tool Package | +| musl target/toolchain | 所需的 musl wrapper;旧式 `MuslToolchain` 还需要 `/usr/local/musl` 下的交叉工具链 | + +Package 通过 `tools` 字段声明的专用工具由 `PackageInstaller` 解析,不会全部重复列入 Doctor 基线检查。各平台软件包清单和 toolchain 细节参见[编译工具](./system-build-tools)。 + +## 自动修复 + +根据失败项目和平台,fix 可能会: + +- 调用 `apt`、`apk`、`dnf`、`yum`、`pacman`、Homebrew 或 MacPorts。 +- 使用 `sudo` 安装系统级内容。 +- 下载并安装 StaticPHP Tool 或 Artifact Package。 +- 构建 `re2c` 等替代工具。 +- 在工作区之外安装 musl runtime 或交叉工具链文件。 + +因此,自动修复可能访问网络并修改宿主系统。默认交互策略会先询问;CI 通常在一次性环境中使用 `--auto-fix`,或在预配置镜像中使用 `--auto-fix=never`。 + +Fixer 之间没有共享事务或回滚机制。Fixer 返回 `true` 后,当前实现即认为修复成功,即使环境仍不满足原条件。需要确认时,应在修复后重新运行 `spc doctor --auto-fix=never`。 + +## Doctor Lock + +所有检查被视为成功后,Doctor 会写入 `.spc-doctor.lock`,内容为当前 StaticPHP 版本。`craft` 的 Doctor 步骤成功后也会写入同一 lock。 + +首选位置如下: + +| 平台 | 首选路径 | 回退位置 | +|---|---|---| +| Unix | `$XDG_CACHE_HOME/.spc-doctor.lock`,或 `$HOME/.cache/.spc-doctor.lock` | 系统临时目录,然后是工作目录 | +| Windows | `%LOCALAPPDATA%\.spc-doctor.lock` | 系统临时目录,然后是工作目录 | + +调用 `checkDoctorCache()` 的构建/下载命令只使用该 lock 决定是否显示警告。Lock 不会跳过显式的 `spc doctor`,也不会阻止构建。 + +当前 fingerprint 只有 StaticPHP 版本。target、toolchain、Registry 集合、PATH 或操作系统发生变化时,lock 不会自动失效。此类变化后应删除 lock 或显式运行 Doctor。 + +## 跳过检查和警告 + +两个环境变量用途不同: + +```ini +# 在实际 Doctor 运行中跳过指定项目(使用完整项目名,逗号分隔) +SPC_SKIP_DOCTOR_CHECK_ITEMS="if cmake version >= 3.22,if re2c version >= 1.0.3" + +# Doctor lock 缺失或过期时,不显示构建前警告 +SPC_SKIP_DOCTOR_CHECK=1 +``` + +`SPC_SKIP_DOCTOR_CHECK` 不会从显式 `spc doctor` 中移除检查。`SPC_SKIP_DOCTOR_CHECK_ITEMS` 会;跳过必要检查可能让不可用的环境看起来健康,因此只应在受控 CI 或调试场景中使用。 + +## 当前内部注册模型 + +Registry 当前可以通过 `doctor.psr-4` 或 `doctor.classes` 指向 PHP class。`DoctorLoader` 会以无参数构造这些 class,并识别: + +| Attribute | 当前作用 | +|---|---| +| `#[CheckItem]` | 注册检查名、可选 OS 限制和数字 level;level 越高越先执行 | +| `#[OptionalCheck]` | 提供 class 或 method 级 callable,决定该检查是否存在 | +| `#[FixItem]` | 注册字符串 fix 名,由失败的 `CheckResult` 引用 | + +Check callback 当前不接收 DI context,返回 `null` 或 `CheckResult`。Fix callback 通过 `ApplicationContext` 调用,并把 `CheckResult` 的原始 `fix_params` 数组作为 callback context。`CheckItem` 的 `manual` 属性当前未被执行器使用。 diff --git a/docs/zh/develop/performance.md b/docs/zh/develop/performance.md new file mode 100644 index 000000000..e623289dc --- /dev/null +++ b/docs/zh/develop/performance.md @@ -0,0 +1,136 @@ +# 性能 + +本页说明 StaticPHP 构建的 PHP 性能如何、构建过程已经为你做了哪些优化、你自己可以调整什么,以及项目性能研究的方向。本页同时面向两类读者:用 StaticPHP 构建 PHP、希望得到快速且合适的二进制的使用者,以及想理解或参与性能优化工作的贡献者。 + +先说明一点:不存在对所有负载都最快的构建配置。结果取决于 PHP 版本、CPU、工具链、扩展集合、SAPI、libc 和运行时配置。本页描述的是默认值和取舍,而不是万能配方。 + +## 默认构建已包含的优化 + +默认的 StaticPHP 构建就是优化过的构建。不传任何额外参数,你就能得到: + +- **`-O3` 优化**:PHP 和所有依赖库都偏向运行速度而非编译时间和代码体积。 +- **无用代码消除**:`-ffunction-sections -fdata-sections` 配合 `--gc-sections`(Linux)或 `-Wl,-dead_strip`(macOS),移除未使用的函数和数据,保持二进制精简。 +- **更低成本的 ELF 符号处理**(Linux):`-fno-semantic-interposition` 和 `-fno-plt` 避免只在可插入(interposable)共享库场景下才需要的间接调用。 +- **链接加固**(Linux):`relro`、`-z now`、`noexecstack`、`--as-needed`。 +- **Windows 链接时代码生成**:StaticPHP 改写最终 CLI/CGI/micro/embed 的链接规则并加入 `/LTCG`。 +- **精简的 PHP 配置**:只构建你请求的 SAPI 和扩展,默认启用 `--enable-re2c-cgoto`。 +- **部署二进制默认 strip**,调试信息单独保存在 `buildroot/debug/`。`--no-strip` 保留符号但*不会*关闭优化——这是 [Issue #385](https://github.com/crazywhalecc/static-php-cli/issues/385) 的教训:v2 的 `--no-strip` 会静默选择 `-O0`,让一个 Laravel 测试慢了约 3 倍。 + +这些默认值以 `config/env.ini` 中的定义为准(`SPC_DEFAULT_CFLAGS`、`SPC_DEFAULT_LDFLAGS`、`SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS` 及各平台变体)。 + +编译阶段有意保留 frame pointer 和 `-g`,使性能分析工具(perf、samply、Instruments)能产生可用的调用栈;部署用的二进制最终会被 strip,因此运行时没有额外成本。 + +## Linux:选择链接方式和 libc + +Linux 默认是 **musl 全静态二进制**(`SPC_TARGET=native-native-musl`,Zig 工具链):单个文件即可在任何兼容内核上运行,不支持共享扩展和 FFI。这是分发的最佳选择,但它首先是*可移植性*选择,其次才是性能选择。 + +性能研究关注三个 target: + +| Target | libc / 链接方式 | 共享扩展、FFI | 何时选择 | +|---|---|---|---| +| `native-native-musl` | musl,全静态 | 不支持 | 默认:可移植单文件分发 | +| `native-native-musl -dynamic` | musl,动态链接 | 支持 | 在 musl 下隔离链接方式影响的实验 | +| `native-native-gnu.2.17` | glibc,动态链接(2.17 基线) | 支持 | 需要 `.so` 扩展、FFI、NSS,或实测 glibc 更适合你的负载 | + +这里的“动态”仅指 libc 和 loader 是动态的——StaticPHP 仍会把大多数库和扩展静态链接进可执行文件。只有 musl 全静态 target 无法在运行时加载扩展。 + +**链接方式真的影响速度吗?** 比大多数人想象的要小。[Issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) 的测量显示,PHP 8.4 下静态和动态构建的差异在约 1% 以内。静态链接可以省去极短 CLI 调用中的 loader 和重定位时间;长时间运行的 FPM 或 FrankenPHP worker 则完全将其摊销。同样,musl 与 glibc 的差异只在负载实际触及 libc 的地方出现——大量原生内存分配下的 allocator、ZTS 下的线程、DNS 解析、locale/iconv。纯 PHP 代码运行在 Zend VM 和 Zend Memory Manager 中,基本绕开系统 allocator。 + +实践建议: + +- **默认用 musl 静态**。可移植、可预测,对典型 PHP 负载没有可测量的性能损失。 +- **需要共享扩展或 FFI,或自己的基准测试显示 glibc 更优时,选 glibc 动态**(glibc 可调的 per-thread allocator cache 对分配密集的多线程负载可能有帮助)。 +- **用你自己的真实应用做基准测试**。仅 resolver 行为(musl 并行查询 nameserver,传统 glibc 依次尝试)一项,就足以在 DNS 密集的应用中主导一场“libc 性能”对比。 + +如果你要发布或对比数据,请从二进制本身确认链接方式,而不是根据文件名推断: + +```bash +file buildroot/bin/php +readelf -l buildroot/bin/php | grep 'Requesting program interpreter' +``` + +## 工具链和 PHP 版本 + +编译器选择的影响可能超过任何单个参数: + +- **Linux 默认使用 Zig**(`zig cc`,基于 LLVM),因为它提供灵活的交叉编译 target 支持和可复现的 musl/glibc 选择——而不是因为它总能生成最快的二进制。native GCC 仍可通过维护者级 `SPC_TOOLCHAIN` 使用。 +- **GCC 与 Clang**:[Issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) 在部分 PHP 8.4 x86-64 测试中发现较大差异,因为较旧的 Zend VM 能利用 GCC 的 global register variable。PHP 8.5 的新 VM 改变了这一局面——一个 PHP 版本上的结论不能自动迁移到下一个版本。 +- **macOS** 默认使用系统 Clang;`SPC_USE_LLVM=brew` 或 `=port` 选择 Homebrew/MacPorts 的较新 upstream LLVM 完整工具链。这让构建不受 Apple 发布节奏限制,对部分 PHP 版本和负载有帮助,但并非必然带来提升。 +- **Windows** 使用 MSVC,最终链接带 `/LTCG`。FrankenPHP 的 CGO 链接走 Clang/LLD,因此部分库会刻意禁用 `/GL`——链接命令行中出现 `/LTCG` 不代表每个目标文件(object)都参与了全程序优化(whole-program optimization)。 + +每个 PHP 版本的编译器格局是最容易被低估的性能因素之一:升级 PHP(或编译器)可能胜过数周的参数调优。 + +## 调整你自己的构建 + +所有默认值都可以通过 `config/env.custom.ini`(或进程环境变量)可复现地覆盖。配置值会完整替换默认字符串,因此你可以保持基线可见、一次只改变一个因素。 + +### CPU 指令集级别 + +Linux 上影响最大的用户级可调项是 ISA 基线。加入 `-march=x86-64-v3` 可让编译器使用 AVX2 等指令: + +```ini +[linux] +SPC_DEFAULT_CFLAGS="-fPIC -O3 -pipe -fno-plt -fno-semantic-interposition -fstack-clash-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections -march=x86-64-v3" +SPC_DEFAULT_CXXFLAGS="${SPC_DEFAULT_CFLAGS}" +``` + +代价是可移植性:产物无法在低于该级别的 CPU 上运行,`-march=native` 则把产物绑定到构建机。[Issue #1088](https://github.com/crazywhalecc/static-php-cli/issues/1088) 正在追踪一种更友好的方式,在依赖库和 PHP 之间统一声明 intrinsic 级别,而不是手工管理参数。 + +### 覆盖范围 + +- **全部**:`SPC_DEFAULT_CFLAGS` / `SPC_DEFAULT_CXXFLAGS` / `SPC_DEFAULT_LDFLAGS` +- **仅 PHP 和源码树内扩展**:`SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS`(及 CXX/LD 对应项) +- **单个库**:snake-case 变量,如 `libaom_CFLAGS`,由通用构建 executor 与默认参数合并 + +### 注意:很多“构建性能”问题其实是运行时问题 + +- **Opcache 是运行时特性**。构建 `ext-opcache` 只是让它可用;`opcache.enable_cli`、JIT 配置和真实应用行为才决定它的效果。`--disable-opcache-jit` 改变的是构建能力,不是实测速度。 +- **ZTS 与 NTS**:FrankenPHP 要求 ZTS,其余 SAPI 默认 NTS。历史测试显示的差异较小,且局限于当时的负载。 +- **FrankenPHP** 的性能主要由 worker 模式和数量、应用启动行为、Caddy 模块和 Go runtime 设置决定——没有编译参数能弥补不具代表性的服务器配置。 +- **UPX**(`--with-upx-pack`)是体积优化,代价是启动时间、内存映射和安全工具行为。它不会让 PHP 执行得更快。 + +## 正在进行的性能研究 + +StaticPHP 的性能工作是活跃且渐进式的,主要方向: + +**PGO(Profile-Guided Optimization)**——[PR #1138](https://github.com/crazywhalecc/static-php-cli/pull/1138) 正在带来 v3 原生的 PGO:插桩构建、用代表性负载训练、用收集到的 profile 重新构建。难点在编排而非参数:每个 SAPI 独立的 profile 目录、切换 profile 时的干净重建、保证 profile 真正 flush 的 shutdown 补丁(Go/CGO 进程会绕过 libc `atexit` 路径),以及源码变化后的失效处理。直接在全局参数中添加 `-fprofile-generate` 会连所有依赖库一起插桩,甚至可能无法产出可用数据——所以 PGO 会以结构化功能的形式到来,而不是一个环境变量。相关:[PR #1142](https://github.com/crazywhalecc/static-php-cli/pull/1142) 已经在构建中使用 FrankenPHP 自带的 Go profile(`default.pgo`)优化 Go/Caddy 部分——它只优化 Go 代码,不会针对你的应用训练 php-src。早期的一刀切提案([Issue #862](https://github.com/crazywhalecc/static-php-cli/issues/862))被关闭,因为有意义的训练必须感知 SAPI 且由用户负责。 + +**LTO(Link-Time Optimization)**——Unix 上默认不启用。在 [Issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) 的一个测试环境中,运行时收益约 2%,而 ThinLTO 构建时间翻倍、Full LTO 增至七倍。LTO 还会在数十个依赖库中暴露 archive 不兼容、Package bug 和 CGO 链接限制,因此当前方向是限定范围的 LTO(PHP 核心和源码树内扩展、单一工具链家族、一致的 `-flto=thin`),而不是全局开关。Windows 如上文所述已在最终链接使用 `/LTCG`。 + +**更好的参数传递**——参数沿 `env.ini` → 工具链 → Package executor → PHP/FrankenPHP target 传递,但使用手写编译命令的包仍可能忽略部分全局参数。让传递保持一致是持续进行的工作,它会让其他所有优化受益。 + +## 我们如何做基准测试 + +本项目的性能结论遵循可复现的方法论,并以 [Issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) 作为公开记录: + +- 只比较**单一因素**不同的构建,保持 PHP 版本、commit、扩展、SAPI、ZTS/NTS、INI、libc 和编译器一致。 +- 在**同一台机器**上交替运行基线和候选构建并重复多次;中位数和波动范围比一次最佳结果更有意义。 +- 记录完整上下文:工具链版本、`SPC_TARGET`、CPU 型号和频率策略、扩展集合、环境变量覆盖、负载 revision、并发、预热,以及适用的吞吐量/尾延迟/RSS/二进制体积/构建时间。 +- 验证实际构建出来的东西(用 `file`/`readelf` 确认链接方式,用构建日志和 `php -i` 确认参数),而不是轻信调用参数。 + +如果你要报告性能问题或退化,附上这些上下文会让报告更具可操作性。 + +## 历史记录与经验 + +| 记录 | 经验 | +|---|---| +| [Issue #385](https://github.com/crazywhalecc/static-php-cli/issues/385) | 调试符号绝不能静默关闭优化;促成了可定制 PHP 编译变量。 | +| [PR #806](https://github.com/crazywhalecc/static-php-cli/pull/806) | 为 target 灵活性加入 Zig 工具链;讨论中包含静态/动态约 1% 差异的观察。 | +| [Issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) | 主要的性能记录:`-O3` 默认值背后的编译器、ZTS/NTS、Opcache、LTO、VM 和架构对比。 | +| [Issue #985](https://github.com/crazywhalecc/static-php-cli/issues/985) | v3 工具链决策:优先可预测默认值,保留性能导向的替代方案。 | +| [Issue #862](https://github.com/crazywhalecc/static-php-cli/issues/862) | 通用 PGO 训练脚本不现实;训练必须感知 SAPI 且由用户负责。 | +| [PR #966](https://github.com/crazywhalecc/static-php-cli/pull/966) | 统一 PHP make 参数;将 strip 与优化解耦。 | +| [PR #1142](https://github.com/crazywhalecc/static-php-cli/pull/1142) | 构建中使用 FrankenPHP 自带 Go profile;不同于应用训练的 PGO。 | +| [Issue #1088](https://github.com/crazywhalecc/static-php-cli/issues/1088) | 开放:在依赖库和 PHP 之间统一声明 CPU intrinsic 级别。 | +| [PR #1138](https://github.com/crazywhalecc/static-php-cli/pull/1138) | 进行中的 v3 原生 PGO 实现。 | +| [PR #1150](https://github.com/crazywhalecc/static-php-cli/pull/1150) | 优化参数必须感知目标文件格式(一个 ELF 专用参数曾破坏 macOS configure 检查)。 | + +## 参与贡献 + +大部分性能调查和实现由 [@henderkes](https://github.com/henderkes) 主导,其他贡献者参与 review、集成和测试——也欢迎更多帮助: + +- **基准测试**:真实应用上的可复现测量(尤其是负载下的 FrankenPHP/FPM、分配密集的扩展、DNS 密集负载)永远有价值。按照上面的方法论,把结果分享到 [Issue #838](https://github.com/crazywhalecc/static-php-cli/issues/838) 或新开 Issue。 +- **平台覆盖**:ARM 服务器、较旧的 glibc 基线和 Windows/MSVC 行为的探索程度不如 Linux x86_64。 +- **PGO 和 LTO**:在你的负载上测试 [PR #1138](https://github.com/crazywhalecc/static-php-cli/pull/1138) 并报告构建或运行时问题,会直接塑造这个功能。 + +由于 StaticPHP 处在 PHP、依赖生态和多种工具链的交叉点,这里的发现经常会暴露上游 bug——在 php-src、依赖库、Zig 或 LLVM 中。回流到上游的报告和修复改善的是整个 PHP 社区,而不只是静态构建。 diff --git a/docs/zh/develop/php-src-changes.md b/docs/zh/develop/php-src-changes.md index e15cf1eca..31a84095d 100644 --- a/docs/zh/develop/php-src-changes.md +++ b/docs/zh/develop/php-src-changes.md @@ -1,3 +1,148 @@ # 对 PHP 源码的修改 - +StaticPHP 会修改解压后的 PHP 源码树,以支持静态链接、额外的 SAPI、较旧的 PHP 分支以及特定平台工具链。这些改动应用于 `source/php-src` 下生成的工作区;`downloads/` 中下载的归档文件不会被修改。 + +本页描述 `core` registry 自带的补丁。它是供维护者使用的实现清单,并不承诺 `SourcePatcher`、生命周期回调签名或单个补丁名称属于稳定的公开 API。 + +## 修改发生的时机 + +修改会在以下几个时机进入源码树: + +1. **解压 `php-src` 后**:Artifact hook 会在构建任何 Package 前应用兼容性修改。 +2. **`buildconf` / configure 前**:PHP target 会应用版本化补丁集并改写构建系统输入。 +3. **构建某个 SAPI 或扩展前**:条件 hook 会修改生成的 Makefile 或扩展源码。 +4. **围绕某个构建阶段临时应用**:少量改动会先备份,并在该 SAPI 构建结束后恢复。 + +带有 `#[PatchDescription]` 的方法由 StaticPHP 通过 lifecycle/DI dispatcher 调用时,会输出 `[PATCH]` 消息,便于追踪构建过程。但它不是完整的机器可读清单:直接方法调用不会经过该日志逻辑,仍有少量源码改写和生成文件修改也没有该标注。 + +## 解压后应用的修改 + +每次解压全新的 `php-src` Artifact 时,都会运行 `src/Package/Artifact/php_src.php`。 + +| 源码版本或条件 | 修改 | +|---|---| +| PHP 低于 8.0 | 应用旧版 Alpine/musl 构建使用的 stream cast 兼容补丁 | +| PHP 8.0.x | 对 DOM、libxml 和 SOAP 应用 libxml2 2.12 兼容补丁,并应用旧版 stream cast 补丁 | +| PHP 8.1.x | 应用旧版 stream cast 补丁 | +| 所有受支持 PHP 分支中的 GD | 使用 StaticPHP 针对不同版本的静态构建配置替换 `ext/gd/config.w32`;低于 PHP 8.2 时还会对 `gdft.c` 应用 `_WIN32` 兼容修复 | +| 内置 IMAP 源码缺少许可证文件 | 添加内置 Apache 许可证文本作为 `ext/imap/LICENSE`,供许可证收集使用 | + +GD 和 IMAP 操作修改的是只有在相应扩展存在或参与构建时才有作用的文件。解压 hook 本身会在解压 PHP 源码时运行,此时扩展解析尚未进入构建 stage。 + +## 版本化 PHP 补丁集 + +在 `buildconf` 前,Unix 和 Windows 构建都会调用 `SourcePatcher::patchPhpSrc()`。尽管它沿用了“micro patches”这一历史名称,但实际会用于整个 PHP 构建,并不要求选择 `php-micro`。 + +`config/env.ini` 中默认的 `SPC_MICRO_PATCHES` 值如下: + +| 宿主平台 | 默认补丁名称 | +|---|---| +| Linux | `cli_checks`、`disable_huge_page` | +| macOS | `cli_checks`、`macos_iconv` | +| Windows | `static_extensions_win32`、`cli_checks`、`disable_huge_page`、`vcruntime140`、`win32`、`zend_stream`、`cli_static`、`win32_api` | + +这些补丁当前的用途如下: + +| 补丁 | 用途 | +|---|---| +| `cli_checks` | 在原本直接将 SAPI 名称与 `cli` 比较的 PHP 代码路径中,把 micro SAPI 视作 CLI | +| `disable_huge_page` | 禁用 Linux 的 2 MiB segment alignment 探测,避免 SFX 二进制无谓增大 | +| `macos_iconv` | 让 PHP 的 iconv 检查正确链接 macOS iconv 库 | +| `static_extensions_win32` | 调整包括 Fileinfo 和 OpenSSL 在内的 Windows 扩展配置,以支持静态构建 | +| `cli_static` | 调整 Windows CLI 源码,以生成完全静态的可执行文件 | +| `vcruntime140` | 绕过启动时对动态加载 `vcruntime140` 模块的检查 | +| `win32` | 把 PHP Windows 构建配置从动态 MSVC runtime(`/MD`)切换到静态 runtime(`/MT`) | +| `zend_stream` | 避免 `isatty` 声明与 Windows 静态 CRT 冲突 | +| `win32_api` | 修正受支持 PHP 分支所需的 Windows API 声明 | + +补丁文件位于 `src/globals/patch/php-src-patches/`。同一名称可以只有不带版本的文件,如 `zend_stream.patch`;也可以包含 `cli_checks_84.patch` 这样的版本文件。对于带版本的名称,StaticPHP 会选择不高于目标源码 minor 版本的最新补丁。例如,可用 `_81` 和 `_84` 但没有 `_82` 时,PHP 8.2 源码会使用 `_81`。PHP 7.4 会跳过该补丁集加载器。 + +该目录还包含 `comctl32`、`phar` 和 `static_opcache` 等非默认或按条件选择的补丁。找不到兼容补丁或执行 `patch -p1` 失败时,构建会以 `PatchException` 终止。 + +## 通用构建期修改 + +除下方各平台表格中的改动外,PHP target 还会应用两个通用修改。 + +### StaticPHP 版本元数据 + +在 PHP `build` stage 前,如果源码尚未包含相关内容,StaticPHP 会在 `main/main.c` 中添加名为 `StaticPHP.version` 的 INI 条目。这样可以在运行时获取生成该二进制的 StaticPHP 版本。 + +### 硬编码 INI 值 + +每个 `--with-hardcoded-ini=key=value` 选项都会向所有实际存在且受支持的 SAPI 源码中的 `HARDCODED_INI` 添加一行字面值: + +- `sapi/cli/php_cli.c` +- `sapi/micro/php_micro.c` +- `sapi/embed/php_embed.c` + +原文件会保存为带 `.bak` 后缀的备份。后续调用再次提供非空值集合时,StaticPHP 会先恢复备份,再应用新的集合,因此从一组配置值改成另一组时不会累积旧值。当前完全不再传入任何 `--with-hardcoded-ini` 选项时不会触发恢复逻辑;要移除先前注入的值,需要重新解压 `php-src`。 + +::: warning +硬编码值会成为源码和最终二进制的一部分。不要使用该选项保存密码、token 或其他秘密信息。 +::: + +## Unix 构建期修改 + +Linux 和 macOS 构建会应用以下与目标相关的修改: + +| 条件 | 文件或生成的输入 | 修改 | +|---|---|---| +| 每次 Unix PHP 构建 | `configure.ac` | 使用 `SPC_TARGET` 选择的 libc 取代基于宿主机 `ldd` 的 musl 检测;原文件会被备份 | +| 每次 Unix PHP 构建 | `build/php.m4` 和扩展的 `config.m4` 文件 | 把 `PKG_CHECK_MODULES` 替换为 `PKG_CHECK_MODULES_STATIC`,使依赖检查包含静态链接所需的 private 依赖 | +| PHP 8.3.x | `build/php.m4` | 应用 PHP 8.4 以前使用的 AVX-512 configure cache 兼容补丁 | +| PHP 低于 8.3 | 生成的 `configure` | 使用受支持的 `-std=gnu17` 探测,而不是较新的 `-std=gnu23` 探测 | +| 动态链接的 musl 目标 | `TSRM/TSRM.h` | 为主 TSRM TLS cache 符号添加 default visibility,使采用 `initial-exec` TLS model 的共享扩展可以加载 | +| Linux | 生成的 `Makefile` | 把意外出现的 `//lib` 路径规范化为 `/lib` | +| 使用 Zig 的 Linux | 生成的 `Makefile` | 让 `BUILD_CC` 使用宿主机 `cc` 而不是 `zig-cc`,避免 minilua 等宿主工具被构建为目标环境程序 | +| 存在 release 链接参数 | `ext/standard/info.c` | 从 PHP 信息输出中隐藏 configure 命令 | + +解析到 `php-micro` 时,其 Package hook 还会调整生成的 Makefile:embed 前置构建只生成 `libphp.la`,并防止 PHP 主安装步骤过早安装 micro 二进制。 + +如果 Unix micro 构建选择了 `ext-phar`,`ext/phar/phar.c` 会被临时修改,使追加到当前可执行文件的压缩 PHAR 即使文件名不含 `.phar` 也能被识别。micro stage 结束后会立即恢复原文件。 + +## Windows 构建期修改 + +Windows 需要更大范围的构建系统修改,因为 PHP 上游的 Windows 流程通常假定使用动态 CRT、PHP SDK 工具以及基于 import library 的 SAPI。 + +| Stage 或条件 | 修改 | +|---|---| +| `buildconf.bat` 前 | 从相应的 Windows core 构建规则中移除共享 `dllmain.c` 对象,然后应用版本化补丁集 | +| 缺少 `win32/wsyslog.h` | 使用 `win32/build/wsyslog.mc` 定义的 event ID 生成兼容头文件 | +| PHP 8.1.x | 把 Fiber 汇编对象从链接器参数移动到汇编对象列表,使其参与静态链接 | +| 检测到 Visual Studio | 改写 `win32/build/confutils.js`,使受支持的 Visual Studio 安装报告正确的 PHP toolset 名称 | +| `buildconf.bat` 后 | 禁用生成的 `configure.js` 中的 PHP SDK 版本检查,因为 v3 使用 MSVC 加自管 MSYS2 环境,而不是 PHP SDK binary tools | +| `--enable-micro-win32` | 在 `sapi/micro/php_micro.c` 中定义 `PHP_MICRO_WIN32_NO_CONSOLE`;选项不再启用时会恢复备份 | +| CLI 构建 | 改写生成的 `php.exe` 规则,直接链接 PHP core、CLI SAPI、静态扩展、汇编对象和静态库;同时把 `buildroot/include` 参数放到扩展参数之前 | +| CGI 构建 | 改写生成的 `php-cgi.exe` 规则以支持静态链接,并从 CGI 源码中移除重复的 `ZEND_TSRMLS_CACHE_DEFINE()` | +| Micro 构建 | 添加生成的 micro target 所需的 Fiber 汇编前置依赖,并提供静态库集合 | +| Embed 构建 | 改写生成的 embed target,使 `phpNembed.lib`(例如 `php8embed.lib`)包含 embed SAPI、PHP core、静态扩展和汇编对象,而不再只是 import library | + +最后一项 embed 修改也是 Windows FrankenPHP 得以构建的基础:FrankenPHP 通过 CGO 使用 Clang/LLD 链接这个自包含的 embed 库。Unix FrankenPHP 不会应用单独的 php-src 补丁;它直接使用常规 PHP target 生成的 Unix `php-embed` 库。 + +## 条件式扩展补丁 + +扩展被解析到构建集合后,其 Package 类可以修改 `php-src/ext/` 下的源码。两个跨版本的典型例子是: + +- **Opcache**:`ext-opcache` 会为 PHP 8.0–8.4.x 应用相应版本的静态 Opcache 补丁。PHP 8.2.23 以前和 PHP 8.3.11 以前使用专用 legacy 补丁,其余版本使用版本化的 `static_opcache` 补丁。PHP 8.5 及以后无需该补丁即可使用所需的静态路径。 +- **Micro 的 PHAR**:只有 `ext-phar` 参与构建时才会应用前文所述的 Unix micro hook,并在生成 micro 二进制后恢复原文件。 + +其他扩展类还包含针对特定 PHP、编译器、依赖库或操作系统版本的更小范围兼容修改。这些修改会刻意保留在 `src/Package/Extension/` 中相应扩展的构建逻辑旁,而不加入全局 PHP 补丁集。可以搜索 `#[PatchDescription]` 查看当前带标注的清单。 + +## 补丁应用与恢复 + +`SourcePatcher::patchFile()` 会把相对补丁名称解析到 `src/globals/patch/`,通过 reverse dry run 检查补丁是否已经应用,然后调用 `patch --binary -p1`。因此,在同一解压源码树中重复应用基于文件的补丁是安全的。直接字符串替换也按可重复调用的方式编写,再次执行时通常已经找不到需要替换的原始文本。 + +构建后不存在恢复整个 PHP 源码树的事务。只有 PHAR 和硬编码 INI 备份等明确的临时改动带有专用恢复逻辑。要回到干净的上游源码,请删除并重新解压 `source/php-src`;切换或刷新 PHP 版本时,也可以使用不带 `--keep-source` 的 `switch-php-version`。 + +v2 的任意 patch point 注入机制不是受支持的 v3 接口。在公开补丁扩展契约确定前,新增 core 补丁应实现为由维护者维护的 Artifact 或 Package hook。 + +## 添加或更新 Core 补丁 + +维护内置补丁时: + +1. 优先推动上游修复;只有仍受支持的 PHP 分支确实需要时才保留下游补丁。 +2. 可复用补丁文件放在 `src/globals/patch/`;需要按版本选择的 micro/static-PHP 补丁集放在 `src/globals/patch/php-src-patches/`。 +3. 使用精确的 PHP/平台/依赖条件,避免无关构建无谓修改源码。 +4. 确保操作可重复执行,并在预期的上游上下文已经变化时明确失败。 +5. 对通过 lifecycle/DI dispatcher 调用的方法添加 `#[PatchDescription]`,使该修改在构建日志中可见;直接调用的方法应显式记录日志。 +6. 测试该补丁影响的最旧和最新 PHP 分支;行为变化时同步更新本页的两个语言版本。 diff --git a/docs/zh/develop/system-build-tools.md b/docs/zh/develop/system-build-tools.md index 7b77f1b90..e51ab9b7f 100644 --- a/docs/zh/develop/system-build-tools.md +++ b/docs/zh/develop/system-build-tools.md @@ -1,4 +1,174 @@ # 编译工具 - +StaticPHP 通过调用原生构建系统来构建 PHP、依赖库、扩展和辅助工具。整个环境可以分为三层: + +1. **宿主机工具**,如 `autoconf`、`cmake`、`make`、`patch` 以及解析器/代码生成器。 +2. **编译器工具链**,如 Zig、GCC、Clang 或 MSVC。选定的工具链负责提供编译器、链接器、归档器和目标平台环境。 +3. **Package 构建 Executor**,负责应用 StaticPHP 通用的 Autoconf 或 CMake 参数并执行 Package 构建。 + +排查问题时需要区分这三层:安装了 `cmake` 不代表选定的编译器工具链已经初始化;存在编译器也不代表源码生成所需的全部工具已经齐备。 + +## 检查环境 + +安装 StaticPHP 后,以及修改构建目标或工具链后,都应运行 `doctor`: + +```bash +# 预构建二进制 +./spc doctor + +# 源码安装 +bin/spc doctor +``` + +如果允许 StaticPHP 自动安装可修复的缺失项,使用: + +```bash +./spc doctor --auto-fix +``` + +在 Linux 和 macOS 上,自动修复可能调用系统包管理器,并可能需要 `sudo`。在 Windows 上,StaticPHP 可以安装自己管理的辅助 Package,但不能代替你安装 Visual Studio 或 Git。命令行选项参见 [`doctor` 命令参考](/zh/guide/cli-reference#doctor)。 + +Doctor 检查与当前所选目标相关。例如,只有选定的工具链和目标需要 Zig 或 musl 时,相关检查才会运行;只有选择对应 LLVM 变体时,才会检查 Homebrew 或 MacPorts LLVM。 + +## 各类工具的用途 + +| 工具类别 | 示例 | 在构建中的作用 | +|---|---|---| +| 编译器与 binutils | `zig`、`gcc`、`g++`、`clang`、`cl.exe`、`ld`、`link.exe`、`ar`、`lib.exe`、`ranlib` | 编译和链接 PHP 及依赖库 | +| Autotools | `autoconf`、`automake`、`libtoolize` / `glibtoolize`、`autopoint` | 生成 `configure` 脚本及其辅助文件 | +| Make 工具 | `make`、`nmake.exe` | 执行生成的 Makefile | +| CMake | `cmake` | 配置和构建基于 CMake 的 Package | +| 源码生成器 | `bison`、`re2c`、`flex`、`gperf` | 重新生成解析器、词法分析器和 C/C++ 源码 | +| 依赖元数据 | `pkg-config` | 读取 `.pc` 文件并返回静态编译/链接参数 | +| 源码与归档工具 | `git`、`patch`、`tar`、`unzip`、`gzip`、`bzip2`、`xz`、`7za.exe` | 获取、修补和解压源码归档 | +| Package 专用工具 | `nasm`、Perl、Go | 在选中相应 Package 时构建 OpenSSL 或 FrankenPHP 等项目 | + +并非每个 Package 都会使用全部工具。Doctor 会检查通用宿主机基础环境和与目标相关的前置条件;选中具体 Package 时,还可能解析 Go、NASM 或 Perl 等额外工具 Package。 + +## Linux + +`LinuxToolCheck` 从 `/etc/os-release` 识别发行版,选择对应发行版家族的基础清单,并检查每个包所提供的命令(或文件)。当前清单如下: + +| 发行版家族 | 检查的包或命令 | +|---|---| +| Alpine | `make`、`bison`、`re2c`、`flex`、`gperf`、`git`、`autoconf`、`automake`、`gettext-dev`、`tar`、`unzip`、`gzip`、`bzip2`、`cmake`、`gcc`、`g++`、`patch`、`binutils-gold`、`libtoolize`、`which` | +| Debian / Ubuntu 及默认回退清单 | `make`、`bison`、`re2c`、`flex`、`gperf`、`git`、`autoconf`、`automake`、`autopoint`、`tar`、`unzip`、`gzip`、`gcc`、`g++`、`bzip2`、`cmake`、`patch`、`xz`、`libtoolize`、`which` | +| RHEL / Fedora | `perl`、`make`、`bison`、`re2c`、`flex`、`gperf`、`git`、`autoconf`、`automake`、`tar`、`unzip`、`gzip`、`gcc`、`g++`、`bzip2`、`cmake`、`patch`、`which`、`xz`、`libtool`、`gettext-devel`、`file` | +| CentOS | RHEL 清单外加 Perl 的 `IPC::Cmd` 和 `Time::Piece` 模块 | +| Arch / Manjaro | `base-devel`、`cmake`、`gperf` | + +Doctor 还会检查: + +- CMake 版本不低于 3.22。 +- `re2c` 版本不低于 1.0.3。 +- 在 musl 发行版上存在 `linux-headers`。 +- StaticPHP 隔离使用的 `pkg-config` 已安装且可以执行。 + +Linux 默认使用 `ZigToolchain`。启用 `--auto-fix` 后,缺少 Zig 时 Doctor 可以安装 StaticPHP 的 `zig` 工具 Package。在非 musl 宿主机上构建 musl 目标时,Doctor 还会检查该目标所需的 musl 运行时/wrapper。旧式的 `MuslToolchain` 还要求 `/usr/local/musl` 下存在 musl 交叉工具链。原生 Alpine 构建不需要这些跨宿主机组件。 + +::: tip +目标字符串和宿主发行版是两个不同概念。glibc 发行版可以通过 Zig 生成 musl 目标,而 `*-gnu.*` 目标仍会动态链接到指定的 glibc ABI。 +::: + +## macOS + +默认编译器工具链是 Xcode 或 Xcode Command Line Tools 提供的系统 Clang。StaticPHP 还要求安装 Homebrew 或 MacPorts 之一,以提供其余构建工具。 + +Doctor 当前检查以下命令: + +```text +curl, make, bison, re2c, flex, gperf, pkg-config, git, +autoconf, automake, tar, libtool, unzip, xz, gzip, bzip2, +cmake, glibtoolize +``` + +它还要求 GNU Bison 3 或更高版本。如果 `/usr/bin/bison` 版本过旧,Doctor 会继续搜索 Homebrew 和 MacPorts 路径,并可安装较新的包。在 Apple Silicon 上,StaticPHP 使用的 Homebrew 必须是位于 `/opt/homebrew` 的原生安装,而不是 `/usr/local` 下的 Intel 安装。 + +`SPC_USE_LLVM` 控制使用的 Clang 变体: + +| 值 | 工具链 | 编译器位置 | +|---|---|---| +| `system`(默认) | `ClangNativeToolchain` | Xcode / Command Line Tools 的 `clang` | +| `brew` | `ClangBrewToolchain` | Homebrew 的 `opt/llvm/bin` | +| `port` | `ClangPortsToolchain` | MacPorts 的 `bin`(通常为 `/opt/local/bin`) | + +选择 `brew` 或 `port` 时,Doctor 会验证对应 LLVM 安装中是否包含 `clang`。 + +## Windows + +Windows 当前以 x86-64 为构建目标,并使用两类前置依赖。 + +以下组件需要用户自行安装: + +- Visual Studio,并勾选 x86/x64 C++ 工具组件。 +- Git for Windows,且 `patch.exe` 可以从 `PATH` 找到(通常来自 `C:\Program Files\Git\usr\bin`)。 + +Doctor 可以安装以下由 StaticPHP 管理的基础 Package: + +| Package | 用途 | +|---|---| +| `vswhere` | 查找包含 `Microsoft.VisualStudio.Component.VC.Tools.x86.x64` 的最新 Visual Studio 安装 | +| `msys2-build-essentials` | 提供 MSYS2,以及 `make`、Autotools、`libtool`、`pkgconf`、Perl、Bison 和 `re2c` | +| `7za-win` | 解压需要 `7za.exe` 的格式 | + +`nasm`、`strawberry-perl` 等附加工具也已经表示为 Tool Package,但它们会在选中的 Package 通过 `tools` 声明时安装,并不是无条件运行的 Windows Doctor 检查。 + +`MSVCToolchain` 会调用检测到的 Visual Studio 的 `vcvarsall.bat x64` 并导入其环境。随后 Doctor 会验证 `cl.exe`、`link.exe`、`lib.exe`、`dumpbin.exe`、`msbuild.exe` 和 `nmake.exe`。导入的环境会在 `downloads/.vcenv-cache` 中缓存最多一小时。 + +MSYS2 根目录默认为 `pkgroot/.../msys2-build-essentials/msys64`。可以通过 `SPC_MSYS2_PATH` 修改;其值必须指向 `msys64` 目录。v3 不再使用 v2 的 PHP SDK 目录和 Visual Studio 版本选项。 + +### FrankenPHP 所需的 LLVM/Clang + +常规 Windows PHP 和依赖库构建使用 MSVC。FrankenPHP 是例外:它的 CGO 链接步骤需要 Clang 和 LLD。 + +StaticPHP 会优先使用 `CC` 指定的 Clang 绝对路径;否则通过 `vswhere` 查找 Visual Studio LLVM 安装中的 `VC\Tools\Llvm\x64\bin\clang.exe`。构建 FrankenPHP 时,请在 Visual Studio Installer 中安装 **C++ Clang tools for Windows** 组件。这个 Clang 依赖不会取代 PHP 和依赖库构建阶段所需的 MSVC。 + +## 编译器工具链选择 + +`GlobalEnvManager` 会先读取 `config/env.ini`(以及可选的 `config/env.custom.ini`),再由 `ToolchainManager` 初始化选定的工具链。常规选择如下: + +| 宿主系统 | 默认实现 | 当前存在的其他实现 | +|---|---|---| +| Linux | `ZigToolchain` | `GccNativeToolchain`、`ClangNativeToolchain`、`MuslToolchain` | +| macOS | `ClangNativeToolchain` | `ClangBrewToolchain`、`ClangPortsToolchain` | +| Windows | `MSVCToolchain` | 无 | + +在 Unix 上,工具链会提供 `CC`、`CXX`、`AR`、`RANLIB` 和 `LD` 的默认值。调用方已经设置的值优先于 `env.ini` 默认值。`SPC_TARGET` 决定目标操作系统/libc/ABI,`SPC_USE_LLVM` 决定 macOS 的 Clang 分发方式。`SPC_TOOLCHAIN` 可以覆盖实现类,但它目前是维护者级别的内部设置,并非稳定的公开扩展契约。 + +环境初始化后,工具链会验证其命令以及目标/libc 组合。能够识别编译器时,还会把编译器描述记录到 `PHP_BUILD_COMPILER`。 + +## 隔离的 pkg-config + +Unix 构建会刻意使用由 StaticPHP 管理的 `pkg-config`,而不是碰巧位于系统 `PATH` 首位的可执行文件。`PkgConfigUtil` 按以下顺序搜索: + +1. `pkgroot//bin/pkg-config` +2. `buildroot/bin/pkg-config` + +Doctor 可以通过 `pkg-config` 工具 Package 安装预构建版本。如果需要从源码构建,StaticPHP 会使用其内置 GLib 构建 pkg-config,并禁用系统 include、library、sysroot 和默认 `.pc` 搜索路径。 + +构建期间,`PKG_CONFIG_PATH` 以 `buildroot/lib/pkgconfig` 开头;StaticPHP 的依赖查询和生成的 CMake toolchain 文件都会请求 `--static` 参数。这样可以避免无关的系统库悄悄满足原本应由 StaticPHP 自行构建的依赖。 + +## Package 构建 Executor + +Package 类使用三种通用 Executor 实现: + +| Executor | 平台与构建系统 | 当前行为 | +|---|---|---| +| `UnixAutoconfExecutor` | Linux/macOS,Autoconf | 添加静态/PIC 和 `buildroot` 前缀参数,执行 `configure`、并行 `make`,并可选执行 `make install` | +| `UnixCMakeExecutor` | Linux/macOS,CMake | 生成以 `buildroot` 为根的 toolchain 文件,固定使用 StaticPHP 的编译器和 pkg-config,禁用共享库,然后执行配置、构建和安装 | +| `WindowsCMakeExecutor` | Windows,CMake/MSVC | 使用 x64 Visual Studio generator、静态 MSVC runtime 和 StaticPHP CMake toolchain 文件,并执行 `cmake --build --target install` | + +Executor 会初始化 Package 专属的 include、library 和环境路径,并使用配置的构建并发数。Unix Autoconf 失败时会保留 `config.log`,Unix CMake Executor 会把可用的配置/错误/输出日志复制到 `SPC_LOGS_DIR`。Windows CMake Executor 当前依赖通用 shell 和 output 日志,不会另行收集这些 CMake 文件。 + +维护内置 Package 时,这些类是很有用的实现入口,但其构造函数和链式方法尚未被声明为稳定 PHP API。在公开扩展 API 定义完成前,Package 作者应遵循现有的 `src/Package/Library` 或 `src/Package/Tool` 实现模式。 + +## 排查工具故障 + +Doctor 已通过但某个 Package 仍构建失败时: + +1. 运行 `spc dev:shell`(或 `bin/spc dev:shell`),检查 `CC`、`CXX`、`AR`、`LD`、`PKG_CONFIG` 和 `PATH`。 +2. 确认 `SPC_TARGET` 描述的是预期的 libc 和 ABI。 +3. 查看 `log/spc.output.log`、`log/spc.shell.log` 以及 Package 专属的 Autoconf/CMake 日志。 +4. 在 Windows 上,如果 Visual Studio、MSYS2 或 LLVM 组件发生变化,请重新运行 Doctor;只有怀疑 Visual Studio 环境缓存过期时,才删除有效期一小时的 `downloads/.vcenv-cache`。 + +不要通过把二进制复制进 `buildroot/` 或修改生成的构建目录来绕过缺失工具。应根据工具的职责,把它加入宿主机前置依赖、StaticPHP 工具 Package 或 Package 构建依赖。 diff --git a/docs/zh/guide/env-vars.md b/docs/zh/guide/env-vars.md index 8709280d8..edb09cdf7 100644 --- a/docs/zh/guide/env-vars.md +++ b/docs/zh/guide/env-vars.md @@ -18,6 +18,7 @@ StaticPHP 将环境变量集中到了 `config/env.ini` 文件中,你可以通 一般情况下,你不需要修改任何以下环境变量,因为它们已经被设置为最佳值。 但是,如果你有特殊需求,你可以通过设置这些环境变量来满足你的需求(比如你需要调试不同编译参数下的 PHP 性能表现)。 +修改优化参数前,请先阅读[性能](../develop/performance),了解当前编译默认值、参数传递规则和基准测试注意事项。 如需使用自定义环境变量,你可以在终端中使用 `export` 命令或者在命令前直接设置环境变量,例如: @@ -48,4 +49,3 @@ SPC_CONCURRENCY=4 [linux] SPC_DEFAULT_CFLAGS="-O3" ``` - diff --git a/docs/zh/guide/sapi-reference.md b/docs/zh/guide/sapi-reference.md index beb291456..4b9e9365a 100644 --- a/docs/zh/guide/sapi-reference.md +++ b/docs/zh/guide/sapi-reference.md @@ -273,5 +273,6 @@ SPC_TARGET=native-native-gnu.2.17 spc build:php "bcmath,openssl" --build-cli 这将使用 Zig 工具链构建出一个准静态二进制,动态链接 glibc 2.17,可运行于大多数现代 GNU/Linux 发行版,无需 Docker,也无需额外的交叉编译工具链。该产物支持 `dl()`、FFI 和运行时加载 `.so` 扩展,但无法运行于 Alpine Linux 等基于 musl 的系统。 -**Windows** — Windows 上的 PHP 扩展均以 `.dll` 形式分发,且依赖官方动态构建的 PHP 中附带的 DLL 文件。StaticPHP 构建的静态 PHP 可执行文件不包含这些 DLL,因此 Windows 不支持动态扩展加载,所有扩展必须在构建时静态编译进去。 +默认 musl 静态 target、musl 动态和 glibc 动态构建之间的性能与部署取舍,请参阅[性能](../develop/performance#linux-选择链接方式和-libc)。 +**Windows** — Windows 上的 PHP 扩展均以 `.dll` 形式分发,且依赖官方动态构建的 PHP 中附带的 DLL 文件。StaticPHP 构建的静态 PHP 可执行文件不包含这些 DLL,因此 Windows 不支持动态扩展加载,所有扩展必须在构建时静态编译进去。