Compare commits

..

67 Commits

Author SHA1 Message Date
Jerry Ma
392733b7c3 feat(v3): Tool package refactor (#1200) 2026-07-06 21:59:44 +08:00
Jerry Ma
3870269330 feat(docs): add agent guide, build class patterns, log triage, and AI instructions (#1202) 2026-07-06 21:59:26 +08:00
crazywhalecc
03f1313eec chore 2026-07-06 21:20:38 +08:00
crazywhalecc
b9def51f86 Simplify redundant github instruction 2026-07-06 21:20:20 +08:00
crazywhalecc
8533702cf6 oops 2026-07-06 21:19:55 +08:00
crazywhalecc
fe3cd15f81 feat(docs): add agent guide, build class patterns, log triage, and AI instructions 2026-07-06 20:39:44 +08:00
crazywhalecc
dc33b55fd0 Use binary 2026-07-06 20:12:10 +08:00
crazywhalecc
c9401a0b7a Use redirect instead 2026-07-06 19:35:48 +08:00
crazywhalecc
fa040bc346 Fix mingw patch paths 2026-07-06 19:10:10 +08:00
crazywhalecc
ab37b34fb5 fix: improve Windows patching logic and update file paths for xlsxwriter 2026-07-06 16:46:29 +08:00
crazywhalecc
0b02f62b7e feat: add pkg-config tool package config 2026-07-06 15:36:36 +08:00
crazywhalecc
924fac137c docs: fix hosted repo URL in READMEs
- Update static-php-cli-hosted → static-php/hosted in both README.md and README-zh.md
2026-07-06 15:11:07 +08:00
crazywhalecc
d9c41a73da feat: add --installed flag to check-update, improve CLI and InteractiveTerm
- Add --installed option to check-update command to compare against on-disk version (tool packages)
- Support use_installed parameter in ArtifactDownloader::checkUpdate/checkUpdates
- Add -y shorthand for --auto-fix in doctor command
- Hook InteractiveTerm::init() in ConsoleApplication::doRun() to properly inject Symfony input/output
2026-07-06 15:10:52 +08:00
crazywhalecc
dd1e70a394 feat: declare tool dependencies in package configs via tools field
- Add 'tools' field to ConfigValidator for build-time tool declarations
- Move jom and strawberry-perl from depends@windows to tools@windows in openssl.yml
- Move go-xcaddy/go-win from depends to tools in frankenphp.yml
- Register config/pkg/tool/ path in spc.registry.yml
- Use addInstallPackage for upx tool in php.php (ToolPackage instead of library dep)
- Include 'tool' type in DumpCapabilitiesCommand and InstallPackageCommand package lists
2026-07-06 15:10:22 +08:00
crazywhalecc
1bdbdfc08c refactor: move getLibExtra* to Package base, add ToolPackage support to executors and builders
- Move getLibExtraCFlags/getLibExtraCxxFlags/getLibExtraLdFlags/getLibExtraLibs from LibraryPackage to Package base class so ToolPackage can also use them
- Full ToolPackage implementation: getBuildRootPath, getIncludeDir, getLibDir, getBinDir, getToolField with platform suffix support
- Update PackageBuilder to support building ToolPackage (not just LibraryPackage)
- Update PackageInstaller: support ToolPackage in download/extract/install pipeline, auto-install build-time tools, record installed versions
- Allow LibraryPackage|ToolPackage union type in all Executor classes and UnixShell::initializeEnv
2026-07-06 15:10:08 +08:00
crazywhalecc
90846ab149 refactor: restructure tool target configs into tool package system
- Move tool configs from config/pkg/target/ to config/pkg/tool/
- Add ToolVersionRegistry for tracking installed tool versions
- Delete old Target/pkgconfig.php, move to tool config
- Rename: 7za-win, go-win, go-xcaddy, jom, nasm, protoc, strawberry-perl, upx, vswhere, zig, pkg-config
2026-07-06 15:08:43 +08:00
Jerry Ma
8dd7882869 CRLF to LF (#1199) 2026-07-06 14:14:57 +08:00
crazywhalecc
7f6be12bf1 CRLF to LF 2026-07-06 14:02:38 +08:00
Jerry Ma
45b97581d8 feat(zip): add additional dependencies for Windows zip build (#1198) 2026-07-04 15:42:31 +08:00
crazywhalecc
12a5217f17 feat(zip): add additional dependencies for Windows zip build 2026-07-04 14:10:59 +08:00
Jerry Ma
5913cb07fd [v3] implement tool package support with validation and config (#1196) 2026-06-27 20:17:53 +08:00
crazywhalecc
dd69155539 feat(tool): remove deprecated tool package directory from registry configuration 2026-06-27 15:47:46 +08:00
crazywhalecc
a81dd6d5c9 feat(tool): implement tool package support with validation and configuration 2026-06-27 15:39:30 +08:00
Jerry Ma
b4ed673261 feat(windows): replace php-sdk-binary-tools with MSYS2 + 7za-win (#1193) 2026-06-23 14:36:28 +08:00
crazywhalecc
db2d9a909f feat(gmssl): add patch for pbkdf2_hmac_sm3_genkey rename and update build process 2026-06-22 16:44:44 +08:00
crazywhalecc
38e01a9b88 feat(tests): enhance label information for triggering extension build tests 2026-06-22 13:33:25 +08:00
crazywhalecc
2b210f9403 feat(logging): synchronize logger level and debug mode with output verbosity 2026-06-22 13:33:02 +08:00
crazywhalecc
be329c1d56 feat(build): add patch to Makefile for include order to resolve zip.h conflict 2026-06-22 11:14:14 +08:00
Jerry Ma
227b8f0b0a fix(test): fix redundant terminal output during phpunit (#1194) 2026-06-22 08:08:47 +08:00
crazywhalecc
c88041b7e0 feat(build): add UPX as an optional package for compression 2026-06-21 20:08:27 +08:00
crazywhalecc
3bb84f3b94 feat(build): add zlib as a dependency for Windows 2026-06-21 19:35:07 +08:00
crazywhalecc
c31bf73685 feat(build): add bison and re2c to required packages; bypass phpsdk_version check for MSVC + msys2 2026-06-21 19:28:15 +08:00
crazywhalecc
c4d7ca819b fix(openssl): update perl path for Windows compatibility 2026-06-21 19:27:47 +08:00
crazywhalecc
fd8ab71d80 feat(build): use WinCNG as crypto backend for libssh2 windows 2026-06-21 19:27:32 +08:00
crazywhalecc
2a7966aa4b Fix curl zstd build bug 2026-06-21 19:26:55 +08:00
crazywhalecc
ef83ff074e fix(source): use ftp.gnu.org as primary, ftpmirror.gnu.org as fallback
All GNU packages now use ftp.gnu.org (stable canonical server) as the
primary filelist source, with ftpmirror.gnu.org configured as
source-mirror fallback. ftpmirror uses DNS round-robin across many
mirrors of varying reliability; this setup eliminates random CI failures
caused by unreachable mirror nodes while preserving a fallback path.
2026-06-21 17:33:48 +08:00
crazywhalecc
06864fc3f6 feat(windows): replace php-sdk-binary-tools with MSYS2 + 7za-win
- Add msys2-build-essentials target: downloads the MSYS2 nightly sfx,
  extracts it, disables PGP keyring (CI-safe), runs two-pass pacman
  update, and installs autotools build essentials (make, autoconf,
  automake, libtool, pkgconf, perl).
- Add 7za-win target: downloads 7za.exe to PKG_ROOT_PATH\bin.
- Remove php-sdk-binary-tools target and all PHP_SDK_PATH references;
  replace with SPC_MSYS2_PATH throughout Artifact, ArtifactExtractor,
  FileSystem, DefaultShell and MSVCToolchain.
- Replace {php_sdk_path} path placeholder with {spc_msys2_path}.
- WindowsToolCheck: replace checkSDK/installSDK with checkMsys2,
  installMsys2 and check7zaWin/install7zaWin fix items.
- nasm.yml: extract nasm.exe/ndisasm.exe to {pkg_root_path}/bin.
- env.ini: rename PHP_SDK_PATH to SPC_MSYS2_PATH.
- Docs: update Windows migration guide and package-model placeholder docs.
2026-06-21 16:56:40 +08:00
Marc
408d8f755c Remove laravel/prompts from box.json (#1190) 2026-06-16 19:05:39 +07:00
Marc
1f291a9036 Add Visual Studio 2026 support (#1191) 2026-06-16 19:05:09 +07:00
crazywhalecc
127fb1989f Add vs 18 support 2026-06-16 19:21:18 +08:00
Jerry Ma
af771cf2b5 feat: enhance type hints and improve verbosity handling in commands (#1186) 2026-06-16 18:50:18 +08:00
crazywhalecc
5df65d926c Remove laravel/prompts from box.json 2026-06-16 18:49:22 +08:00
Jerry Ma
bdc7bbe1f1 v3: Fix gmssl build (#1188) 2026-06-16 18:45:43 +08:00
Jerry Ma
89dca1f0fb refactor: remove unused symfony/process and unnecessary laravel/prompts (#1187) 2026-06-16 18:15:03 +08:00
crazywhalecc
064a1f05ae Fix phpstan 2026-06-16 15:17:51 +08:00
crazywhalecc
88cb6749b8 Remove laravel/prompts and symfony/process 2026-06-16 15:11:48 +08:00
Jerry Ma
0010e35882 ext: add ext-fastchart and ext-fastjson registry entries (#1165) 2026-06-16 13:57:33 +08:00
crazywhalecc
728c8dd598 Make gmssl standalone 2026-06-16 13:19:03 +08:00
Marc
bf6216e59f Merge branch 'v3' into v3c/ext-fastchart-fastjson 2026-06-14 17:14:37 +07:00
Marc
411ad7cc0f Add macports support (#1179) 2026-06-06 21:06:13 +07:00
Kevin Boyd
0761267eb3 Combine the macos tool checks into a single checkBrewOrPorts method 2026-06-04 21:40:01 -07:00
Jerry Ma
072a3b5505 feat: add permissions for id-token and attestations in release build (#1181) 2026-06-04 10:35:38 +08:00
Marc
0e5738b710 toolchain: add SPC_DEFAULT_RANLIB and pin cmake AR/RANLIB (#1163) 2026-06-04 09:25:42 +07:00
crazywhalecc
3ff9426e50 feat: add permissions for id-token and attestations in release build 2026-06-04 09:51:06 +08:00
Kevin Boyd
6057394641 Simplify macports checks 2026-05-31 22:04:44 -07:00
Kevin Boyd
2f4fb9d28f Add a check for macports alongside Brew in toolchainmanager 2026-05-28 22:40:33 -07:00
Kevin Boyd
96ab2de4b1 Add preliminary MacPorts support 2026-05-28 22:38:56 -07:00
crazywhalecc
c641c3b8db Make mongodb standalone 2026-05-25 10:25:47 +08:00
Jerry Ma
48d6e9ebc2 LinuxMuslCheck: pass tool env via setEnv instead of command prefixes (#1170) 2026-05-24 22:59:47 +08:00
Jerry Ma
6cab47db67 deduplicate_flags: keep paired flag+value tokens together (#1168) 2026-05-24 22:57:23 +08:00
Jerry Ma
ec3fd0f4b0 patch: strip trailing U+200E from spc_fix_avx512_cache_before_80400.p… (#1167) 2026-05-24 22:57:01 +08:00
henderkes
e72f9aa623 LinuxMuslCheck: pass tool env via setEnv instead of command prefixes
Build the CC/CXX/AR/LD/RANLIB map once and hand it to shell()->setEnv()
so the configure/make invocations don't have to repeat the same prefix
on every line. For the sudo make-install path the env still needs to
be on the command line (sudo strips the parent env), so the same map
is rendered into $envFlags and prepended there. Also adds RANLIB,
which the upstream Makefile honours.
2026-05-24 21:40:26 +07:00
henderkes
c666cd6cd0 deduplicate_flags: keep paired flag+value tokens together
deduplicate_flags() split flags on whitespace then ran a per-token
unique. For paired flags like `-Xclang -mllvm` or `-framework Cocoa`,
where the value is a separate token, the value token could collide
with an unrelated flag or value and get dropped, corrupting the
command line.

Group known paired flags (-Xclang, -Xpreprocessor, -Xlinker,
-Xassembler, -framework, -arch, -target, -include, -imacros, -isystem,
-isysroot, -iquote, -idirafter, -MT, -MF, -MQ) with their following
token into a single atom before the unique pass.
2026-05-24 21:38:22 +07:00
henderkes
b8dd508148 patch: strip trailing U+200E from spc_fix_avx512_cache_before_80400.patch
The filename had a Left-To-Right Mark (U+200E) appended invisibly, so
the file is unreachable by code that constructs the path from a plain
ASCII string literal. Rename to the visible name.
2026-05-24 21:38:00 +07:00
henderkes
37d8b87c3b oops 2026-05-24 21:11:49 +07:00
henderkes
5172580294 toolchain: add SPC_DEFAULT_RANLIB and pin cmake AR/RANLIB
ClangBrew, ClangNative and GccNative now export SPC_DEFAULT_RANLIB
alongside SPC_DEFAULT_AR. UnixCMakeExecutor honours both when
generating the Linux toolchain file, so cmake uses the toolchain's
ar/ranlib (e.g. zig-ar/zig-ranlib for archives that the system
ranlib does not understand) instead of /usr/bin/ranlib.
2026-05-24 20:55:35 +07:00
henderkes
99e05aa22b ext: add ext-fastchart and ext-fastjson registry entries
Register two iliaal/fastchart and iliaal/fastjson PHP extensions
sourced from ghtar, extracted into php-src/ext/{fastchart,fastjson}.
Both target Linux and Darwin. fastchart depends on freetype and
suggests libpng, libjpeg, libwebp.
2026-05-24 20:55:11 +07:00
148 changed files with 4007 additions and 1673 deletions

24
.gitattributes vendored Normal file
View File

@@ -0,0 +1,24 @@
# Force LF line endings for all text files.
# Prevents git from auto-converting LF -> CRLF on Windows checkouts.
* text=auto eol=lf
# Explicit binary files — git won't touch line endings for these
*.phar binary
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.zip binary
*.gz binary
*.tgz binary
*.txz binary
*.xz binary
*.bz2 binary
*.7z binary
*.exe binary
*.dll binary
*.sfx binary
*.so binary
*.a binary
*.o binary

23
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,23 @@
# StaticPHP v3 Copilot Instructions
> The canonical agent instructions live in [AGENTS.md](./AGENTS.md). This file provides the essential summary for GitHub Copilot; read `AGENTS.md` for the complete project map, working rules, and validation steps.
StaticPHP is a PHP CLI application for building static PHP binaries, PHP SAPIs, extensions, libraries, and helper tools. Prefer the v3 architecture under `src/StaticPHP`, `src/Package`, `config/pkg`, and `config/artifact`.
## Skills
Use the repository skills when the task matches them:
- `.github/skills/staticphp-package-maintenance`: add, modify, review, or validate package, artifact, extension, library, target, or tool definitions.
- `.github/skills/staticphp-build-troubleshooting`: diagnose build, download, doctor, shell, terminal, CI, smoke-test, or log failures.
## Quick Reference
- **Entrypoint**: `php bin/spc`
- **Config lint**: `php bin/spc dev:lint-config`
- **CS fix**: `composer cs-fix`
- **Tests**: `composer test`
- **Static analysis**: `composer analyse`
- **Extension packages**: `config/pkg/ext/ext-*.yml`, prefix with `ext-` in dependencies
- **Platform suffixes**: `@unix`, `@linux`, `@macos`, `@windows` — prefer these over runtime conditionals
- **Do NOT edit generated dirs**: `buildroot/`, `source/`, `downloads/`, `pkgroot/`

View File

@@ -0,0 +1,56 @@
---
name: staticphp-build-troubleshooting
description: Diagnose StaticPHP v3 failures. Use when investigating build, compile, linker, download, doctor, environment, CI, smoke-test, terminal output, spc.output.log, spc.shell.log, config.log, CMake logs, or user-provided error snippets from StaticPHP commands.
---
# StaticPHP Build Troubleshooting
## Overview
Use this skill to triage StaticPHP failures from the outside in: command and environment first, SPC module/stage metadata next, shell/config logs last. The goal is to isolate the failing package, stage, command, and root cause before editing code.
## First Pass
1. Capture the exact command, OS, architecture, PHP version, extensions/libs/targets, and whether the run used `-v`, `-vv`, or `-vvv`.
2. Read the final terminal error first. StaticPHP prints module error info, failed package, failed stage, failed command, log paths, and extra log files when available.
3. If log files exist, inspect them in this order:
- `log/spc.output.log`: user-facing SPC messages and exception summary.
- `log/spc.shell.log`: executed commands, working directories, env, stdout/stderr.
- Extra logs named in the exception output: `php-src.config.log`, `lib.<pkg>.console.log`, `lib.<pkg>.cmake-error.log`, `lib.<pkg>.cmake-configure.log`, `lib.<pkg>.cmake-output.log`.
4. Read `references/log-triage.md` for pattern matching, likely causes, and next checks.
## Diagnosis Rules
- Do not start by changing shared core code. Most failures are package metadata, environment, upstream source, dependency order, or platform flags.
- Prefer evidence from the last failing command over earlier warnings.
- When logs are long, search backward for `Command exited`, `error:`, `undefined reference`, `not found`, `No package`, `CMake Error`, `configure: error`, `fatal error`, `Failed module`, and `Failed stage`.
- Use `config.log` and CMake logs for configure detection failures; use `spc.shell.log` for the actual command and env.
- Be careful with rebuild suggestions. `spc reset --with-download --yes` is destructive to caches; ask before clearing caches unless the user explicitly asked.
## Repro Commands
Use focused commands when reproducing:
```bash
php bin/spc doctor -vvv
php bin/spc download --for-extensions="curl,openssl" --with-php=8.5 --parallel=4 --retry=3 -vvv
php bin/spc build:libs "openssl" -vvv
php bin/spc build:php "bcmath,openssl,curl" --build-cli -vvv
php bin/spc dev:lint-config
```
Choose the smallest command that still reaches the failing package.
## Fix Direction
After finding the failing package/stage:
- Package YAML issue: use `$staticphp-package-maintenance`, then edit `config/pkg/*` or `config/artifact/*`.
- Build command or patch issue: inspect the package class under `src/Package/*`.
- Environment issue: check `doctor`, toolchain classes, and `config/pkg/tool/*`.
- Upstream download issue: check artifact type, regex, GitHub rate limiting, mirror behavior, and `GITHUB_TOKEN`.
- Core exception/logging issue: inspect `src/StaticPHP/Exception`, `src/StaticPHP/Runtime/Shell`, or executor classes only after package-level causes are ruled out.
## Resources
- `references/log-triage.md`: log files, failure categories, search patterns, and likely fixes.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "StaticPHP Build Troubleshooting"
short_description: "Diagnose StaticPHP build and log failures."
default_prompt: "Use $staticphp-build-troubleshooting to investigate a StaticPHP build, download, doctor, or log failure."

View File

@@ -0,0 +1,209 @@
# StaticPHP Log Triage
## Contents
- Log files
- Terminal summary
- Search strategy
- Failure categories
- Rebuild hygiene
- Reporting checklist
## Log Files
Default log directory is `log/`, controlled by `SPC_LOGS_DIR`. The important files are:
- `spc.output.log`: StaticPHP console/logger output and exception summaries.
- `spc.shell.log`: command execution log with command, caller, inline env, working directory, stdout, stderr, and exit code.
- `php-src.config.log`: copied when PHP source configure fails.
- `lib.<pkg>.console.log`: copied Autoconf `config.log` for a library when available.
- `lib.<pkg>.cmake-configure.log`: copied CMake 3.26+ configure log.
- `lib.<pkg>.cmake-error.log`: copied CMake error log.
- `lib.<pkg>.cmake-output.log`: copied CMake output log.
Logs are created when `SPC_ENABLE_LOG_FILE` is true. Old `*.log` files are cleaned unless `SPC_PRESERVE_LOG` is set to a truthy value. The code in `src/bootstrap.php` reads `SPC_PRESERVE_LOG` (no trailing S).
## Terminal Summary
StaticPHP exceptions often print:
- Exception category: build, download, environment, execution, file system, patch, validation, wrong usage, registry, internal.
- Failed module: package name, type, and sometimes class/file/line.
- Failed stage: call chain such as `build` or `build -> configure`.
- Failed command: the command that returned non-zero.
- Command working directory and inline env.
- Paths to `spc.output.log`, `spc.shell.log`, and extra logs.
Use this summary to choose the first files to read. If a package and stage are present, start with that package's YAML and class after reading the relevant log tail.
## Search Strategy
For long logs, search from the end first.
Useful patterns:
```text
Failed module:
Failed stage:
Failed command:
Command exited with non-zero code
configure: error
CMake Error
undefined reference
cannot find -l
fatal error:
No package
Package .* not found
Could NOT find
is not a valid
permission denied
curl: (
HTTP/2 403
rate limit
```
In `spc.shell.log`, each command block begins with `>>>>>>>>>>>>>>>>>>>>>>>>>>`, then prints the command, caller, env, and working dir. The last failed block is usually the highest-value evidence.
## Failure Categories
### Download Failure
Signs:
- `Download failed`
- `curl: (56)`, `curl: (22)`, HTTP 403/404/429
- GitHub API calls in verbose logs
- Asset regex did not match a release asset
Likely checks:
- If GitHub rate limited, ask user to set `GITHUB_TOKEN`.
- Verify artifact source type and regex in `config/pkg/*` or `config/artifact/*`.
- Use `--dl-retry`, `--dl-parallel`, `--ignore-cache`, `--no-alt`, or custom source options only as repro aids.
- Prefer fixing stale URLs/regexes over adding workarounds.
### Doctor or Environment Failure
Signs:
- `Environment check failed`
- missing `make`, `cmake`, `autoconf`, `pkg-config`, compiler, Perl, Visual Studio, MSYS2, Zig, etc.
- `Some check items can not be fixed`
Likely checks:
- Run `php bin/spc doctor -vvv`.
- Inspect `src/StaticPHP/Doctor/Item/*` for the check and fix behavior.
- Inspect `config/pkg/tool/*` for installable helper tools.
- On Windows, some dependencies cannot be auto-installed.
### Configure Failure
Signs:
- `configure: error`
- Autoconf failure before compilation
- extra `config.log` listed
Likely checks:
- Read copied `php-src.config.log` or `lib.<pkg>.console.log`.
- Look for the first compiler/linker probe that failed.
- Check `headers`, `static-libs`, `pkg-configs`, dependency order, and `initializeEnv($pkg)` behavior.
- For extensions, check `php-extension.arg-type` and `#[CustomPhpConfigureArg]`.
### CMake Failure
Signs:
- `CMake Error`
- `Could NOT find`
- missing target, package config, or static library
Likely checks:
- Read `lib.<pkg>.cmake-error.log` and `lib.<pkg>.cmake-configure.log`.
- Inspect `UnixCMakeExecutor`/`WindowsCMakeExecutor` usage in the package class.
- Check CMake options, toolchain root path, `pkg-configs`, and transitive dependency libraries.
### Compile Failure
Signs:
- compiler `fatal error: header.h: No such file or directory`
- syntax/type errors from upstream source
- incompatible PHP API or platform macros
Likely checks:
- Identify the source file and include path from `spc.shell.log`.
- Verify dependency headers are installed in `buildroot/include`.
- Check PHP version compatibility and existing patches in `src/globals/patch`.
- Prefer targeted patches or compile flags in the package class over broad toolchain changes.
### Link Failure
Signs:
- `undefined reference`
- `cannot find -lfoo`
- duplicate symbols
- Windows unresolved external symbol or missing `.lib`
Likely checks:
- Verify `static-libs`, dependency order, and `getStaticLibFiles()` usage.
- Inspect `SPC_EXTRA_LIBS`, `Libs.private` in `.pc` files, and package-specific pkg-config patching.
- On Windows, confirm `.lib` names and required system libraries.
- For OpenSSL/curl-like packages, search existing linker flag hooks before adding a new one.
### Smoke Test or Validation Failure
Signs:
- `Validation failed`
- `php --ri` failure
- extension not loaded after build
- micro/embed runtime smoke test fails
Likely checks:
- Check extension `display-name`; empty string skips `php --ri`.
- Confirm static vs shared build settings.
- Confirm the extension is included in the final SAPI target and not only built as a dependency.
- Inspect `src/globals/ext-tests/*` and common tests for expected runtime behavior.
### Registry or Config Failure
Signs:
- `Registry error`
- unknown package, invalid field, invalid platform suffix, type mismatch
- package referenced by attribute but missing config
Likely checks:
- Run `php bin/spc dev:lint-config`.
- Inspect `ConfigValidator` tests for accepted fields and error expectations.
- Confirm attribute type matches YAML `type`.
- Confirm hooks reference existing package names and stages for the current platform.
## Rebuild Hygiene
Use the smallest cleanup that can invalidate the suspected stale state:
- Redownload one artifact: `--ignore-cache="artifact-name"`.
- Skip downloads to test local source/build logic: `--no-download`.
- Prefer source when debugging build logic: `--dl-prefer-source`.
- Clear build products only when necessary. `spc reset --with-download --yes` removes caches and should not be suggested casually.
## Reporting Checklist
When preparing an issue or PR explanation, include:
- Exact `php bin/spc ...` command.
- OS, architecture, PHP version option, and relevant env vars.
- Failed package and stage.
- Last failed command and exit code.
- Tail of `spc.output.log`, relevant command block from `spc.shell.log`, and any extra config/CMake log.
- Whether the failure reproduces after a focused clean or cache refresh.

View File

@@ -0,0 +1,55 @@
---
name: staticphp-package-maintenance
description: Maintain StaticPHP v3 packages and artifacts. Use when adding, modifying, reviewing, or validating config under config/pkg or config/artifact; package classes under src/Package; extension/library/target/tool dependencies; PHP configure args; build hooks; package metadata; or related tests and docs.
---
# StaticPHP Package Maintenance
## Overview
Use this skill to make focused package changes without re-reading the entire repository. StaticPHP v3 separates declarative package/artifact YAML from package-specific PHP build logic; prefer config-only edits unless current patterns require a class.
## Quick Workflow
1. Identify the package kind and exact name.
- Extension: `config/pkg/ext/ext-name.yml`, class `src/Package/Extension/name.php`.
- Library: `config/pkg/lib/name.yml`, class `src/Package/Library/name.php`.
- Target or virtual target: `config/pkg/target/name.yml`, class `src/Package/Target/name.php`.
- Tool: `config/pkg/tool/name.yml`, class `src/Package/Tool/name.php`.
- Shared/custom artifact: `config/artifact/name.yml`, class `src/Package/Artifact/name.php`.
2. Search for the closest existing package before designing anything new.
- Similar build system: `rg "#\\[BuildFor|UnixCMakeExecutor|UnixAutoconfExecutor|WindowsCMakeExecutor" src/Package`.
- Similar config fields: `rg "static-libs|pkg-configs|arg-type|depends@" config/pkg`.
- Similar download type: `rg "type: ghrel|type: pecl|type: pie|type: git|binary: hosted" config`.
3. Read `references/package-reference.md` when changing YAML fields, artifact definitions, package naming, dependencies, platform suffixes, or validation expectations.
4. Read `references/build-class-patterns.md` when PHP build logic, attributes, lifecycle hooks, custom configure args, source patching, or executor usage is needed.
5. Validate narrowly, then broadly if risk warrants it.
- Config lint: `php bin/spc dev:lint-config`
- Focused tests: `vendor/bin/phpunit tests/StaticPHP/Config tests/StaticPHP/Registry tests/StaticPHP/Util/DependencyResolverTest.php --no-coverage`
- Full project checks: `composer test`, `composer analyse`
## Editing Rules
- Treat `config/pkg/*` and `config/artifact/*` as the source of package truth; package classes augment behavior.
- Use platform suffix fields for declarative OS differences: `@unix`, `@linux`, `@macos`, `@windows`.
- Add a PHP class only for custom build stages, validation, hook behavior, patches, or custom configure arguments.
- Keep extension dependencies prefixed with `ext-`; libraries, tools, and targets use their package names.
- Prefer existing helpers such as `UnixAutoconfExecutor`, `UnixCMakeExecutor`, `WindowsCMakeExecutor`, `shell()`, `cmd()`, and `FileSystem`.
- Do not modify build outputs (`buildroot/`, `source/`, `downloads/`, `pkgroot/`) to fix package definitions.
- If upstream metadata changes, update license metadata and smoke-test/display names when relevant.
## Common Task Paths
- Add a new PECL extension: define `config/pkg/ext/ext-name.yml` with `type: php-extension`, `artifact.source.type: pecl`, dependencies, and `php-extension.arg-type`; add a class only if configure args, patches, or hooks are non-standard.
- Add a library: define `config/pkg/lib/name.yml` with artifact, dependencies, and install verification fields (`headers`, `static-libs`, `pkg-configs`, `static-bins`); add build class methods by OS.
- Fix an existing package build: start from the failing package config/class, then inspect dependencies and hooks targeting that package before changing shared core code.
- Update a version source: prefer artifact fields that support update checking (`ghrel`, `ghtar`, `ghtagtar`, `git` with regex, `filelist`, `pecl`, `pie`) over hard-coded URLs when upstream supports it.
## Resources
- `references/package-reference.md`: YAML package/artifact model, naming, fields, validation, and commands.
- `references/build-class-patterns.md`: PHP package attributes, stages, hooks, executors, and class patterns.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "StaticPHP Package Maintenance"
short_description: "Add, update, and validate StaticPHP v3 package definitions."
default_prompt: "Use $staticphp-package-maintenance to add, modify, or review StaticPHP package and artifact definitions."

View File

@@ -0,0 +1,143 @@
# StaticPHP Build Class Patterns
## Contents
- When a class is needed
- Package attributes
- Stage and hook behavior
- Executor choices
- Common patterns
- Failure-safe edits
## When a Class Is Needed
Do not add a PHP package class for simple metadata. Use YAML alone when StaticPHP can infer the configure args, dependencies, and verification.
Add or update a class when the package needs:
- OS-specific build commands.
- Custom validation before building.
- Custom PHP configure arguments.
- Patches or file edits before build.
- Hooks into another package's stage.
- Custom source/binary download or extraction logic.
- Package info shown by dev tooling.
Package classes live in `src/Package/*` and are discovered through PSR-4 plus attributes. Config must exist first; `PackageLoader` throws if an attribute references an undefined package.
## Package Attributes
Class-level package attributes:
- `#[Extension('curl')]` maps to package `ext-curl` if the prefix is omitted.
- `#[Library('openssl')]`
- `#[Target('php')]`
- `#[Tool('zig')]`
Method-level attributes:
- `#[BuildFor('Linux'|'Darwin'|'Windows')]`: registers the `build` stage for that OS.
- `#[Stage('name')]`: registers a named stage; defaults to the method name when omitted.
- `#[BeforeStage('package', 'stage', 'only-when-package-resolved')]`: hook before a target package stage.
- `#[AfterStage('package', 'stage', 'only-when-package-resolved')]`: hook after a target package stage.
- `#[PatchBeforeBuild]`: runs once before package build unless `.spc-patched` exists; return `true` to write that marker.
- `#[CustomPhpConfigureArg('Linux')]`: supplies custom extension configure args.
- `#[Validate]`: validates environment/source assumptions.
- `#[Info]`: returns package information for tooling.
- `#[InitPackage]`: runs while loading a package class.
- `#[ResolveBuild]`: target package callback for resolving build dependencies.
- `#[ConditionalOn(SomeClass::class)]`: conditionally enables before/after hooks only when DI has the class.
## Stage and Hook Behavior
`PackageBuilder::buildPackage()` does:
1. Ensure build directories exist.
2. Skip if already installed unless forced.
3. Ensure source exists for non-virtual packages.
4. Emit `PatchBeforeBuild` callbacks.
5. Run the `build` stage.
6. Install license data.
7. Record tool package versions where relevant.
`Package::runStage()` wraps a stage with:
1. `BeforeStage` callbacks.
2. The stage method itself.
3. `AfterStage` callbacks.
SPC exceptions bind package and stage metadata. Preserve that behavior by throwing existing `SPCException` subclasses instead of raw exceptions when adding new failure paths.
## Executor Choices
Use existing executors where possible:
- `UnixAutoconfExecutor`: for Unix packages using `./configure && make && make install`. It injects default static flags and copies `config.log` into SPC logs on failure.
- `UnixCMakeExecutor`: for Unix CMake packages. It writes toolchain settings and copies CMake configure/error/output logs on failure.
- `WindowsCMakeExecutor`: for Windows CMake packages.
- `shell()->cd(...)->initializeEnv($pkg)`: for custom Unix command chains.
- `cmd()->cd(...)`: for Windows command chains.
Prefer `FileSystem` helpers for file edits and copies so behavior stays consistent.
## Common Patterns
Minimal library class:
```php
#[Library('example')]
class example
{
#[BuildFor('Linux')]
public function build(LibraryPackage $pkg): void
{
(new UnixAutoconfExecutor($pkg))
->configure()
->make();
}
}
```
Extension hook into PHP build:
```php
#[Extension('curl')]
class curl
{
#[BeforeStage('php', [php::class, 'makeForWindows'], 'ext-curl')]
public function patchBeforePhpBuild(): void
{
// Adjust env, generated build files, or linker flags.
}
}
```
Validation:
```php
#[Validate]
public function validate(): void
{
if (SystemTarget::getTargetOS() === 'Windows' && WindowsUtil::findCommand('perl.exe') === null) {
throw new EnvironmentException('You need to install perl first!');
}
}
```
Custom configure args should be used when YAML `arg-type` is `custom` or when args need package/install context:
```php
#[CustomPhpConfigureArg('Linux')]
public function configureArg(PhpExtensionPackage $ext): string
{
return '--with-example=' . $ext->getBuildRootPath();
}
```
## Failure-Safe Edits
- Make hooks conditional with the third `BeforeStage`/`AfterStage` argument when a dependency must be resolved before the hook should run.
- Keep platform-specific code under matching `#[BuildFor]` methods rather than branching heavily inside one method.
- If a patch changes upstream source, place patch files in `src/globals/patch/` and describe them with `#[PatchDescription]` where appropriate.
- If a class only mutates environment variables for another package, search for existing hooks first to avoid duplicate linker flags.
- After adding attributes, run registry/config tests because invalid stages and unknown packages are caught during loading.

View File

@@ -0,0 +1,157 @@
# StaticPHP Package Reference
## Contents
- Package locations
- Package types
- Artifact model
- Common YAML fields
- PHP extension fields
- Platform suffixes
- Validation and tests
## Package Locations
StaticPHP v3 keeps package metadata in YAML and behavior in PHP classes.
| Kind | YAML | PHP class namespace | Typical attribute |
|---|---|---|---|
| PHP extension | `config/pkg/ext/ext-name.yml` | `Package\Extension` | `#[Extension('name')]` |
| Library | `config/pkg/lib/name.yml` | `Package\Library` | `#[Library('name')]` |
| Target | `config/pkg/target/name.yml` | `Package\Target` | `#[Target('name')]` |
| Virtual target | `config/pkg/target/name.yml` | `Package\Target` | `#[Target('name')]` |
| Tool | `config/pkg/tool/name.yml` | `Package\Tool` | `#[Tool('name')]` |
| Custom artifact | `config/artifact/name.yml` | `Package\Artifact` | artifact attributes |
Prefer one package per YAML file unless an existing file groups related definitions. Match nearby naming and ordering.
## Package Types
`php-extension` packages describe PHP extensions. Config package names must use the `ext-` prefix. Dependencies on extensions also use `ext-`.
`library` packages describe buildable dependency libraries. They usually define source artifacts plus verification fields such as headers, static libraries, pkg-config files, or binaries.
`target` packages represent final build outputs and inherit the library-style fields. StaticPHP automatically exposes build commands for targets.
`virtual-target` packages are abstract dependency/build scheduling nodes. They may omit `artifact`.
`tool` packages are helper programs or toolchains installed into `pkgroot/` or used by builds.
## Artifact Model
Artifacts define downloadable source archives or prebuilt binaries. Use inline artifacts for simple one-package sources and `config/artifact/*.yml` for shared, complex, or custom artifacts.
Top-level artifact sections:
- `source`: source archive, git checkout, PECL/PIE package, local directory, or custom source.
- `binary`: prebuilt binary by platform, or aliases such as `hosted`/`custom`.
- `metadata`: `license`, `license-files`, and `source-root`.
Common source types:
- `url`: fixed URL; supports `filename`, `version`, and `extract`.
- `git`: repository source; use `rev` for a fixed branch/tag/commit or `regex` with named `version` capture for update checks.
- `ghrel`: GitHub release asset by regex filename match.
- `ghtar`: generated GitHub release tarball.
- `ghtagtar`: generated GitHub tag tarball.
- `filelist`: scrape a download index and extract version/filename with regex.
- `pecl`: PECL extension by name.
- `pie`: Packagist PIE extension by `vendor/package`.
- `php-release`: official PHP source selected by build PHP version.
- `local`: local source directory for development/offline use.
- `custom`: PHP artifact class handles source or binary logic.
Use `metadata.source-root` when the actual build directory is inside an extracted subdirectory.
Use `metadata.license-files` for source licenses. `@/file.txt` points to bundled license files in `src/globals/licenses/`.
## Common YAML Fields
Shared fields:
- `type`: required package type.
- `description`: optional human-readable package description.
- `license`: package-level license annotation where applicable.
- `lang`: implementation language such as `c` or `c++`.
- `frameworks`: macOS framework tags.
- `artifact`: string reference or inline artifact object. Required for `library` and `target`; optional for built-in extensions and virtual targets.
- `depends`: hard dependencies.
- `suggests`: optional dependencies.
Library, target, and tool verification/installation fields:
- `headers`: expected files/directories under `buildroot/include`.
- `static-libs`: expected static libraries under `buildroot/lib`.
- `pkg-configs`: expected `.pc` files under `buildroot/lib/pkgconfig`.
- `static-bins`: expected executables under `buildroot/bin`.
- `path`: directories appended to PATH after install.
- `env`: environment variables set after install.
- `append-env`: values appended to existing environment variables.
Path placeholders in `path`, `env`, and `append-env`:
- `{build_root_path}`: `buildroot/`
- `{pkg_root_path}`: `pkgroot/`
- `{working_dir}`: repository or working directory
- `{download_path}`: `downloads/`
- `{source_path}`: `source/`
- `{spc_msys2_path}`: MSYS2 root on Windows
## PHP Extension Fields
Extension-specific fields live under `php-extension`.
- `arg-type`: configure argument behavior. Built-ins include `enable`, `enable-path`, `with`, `with-path`, `custom`, and `none`. Full strings are allowed.
- `zend-extension`: true for Zend extensions such as opcache/xdebug.
- `build-shared`: whether shared builds are allowed.
- `build-static`: whether static builds are allowed.
- `build-with-php`: true when built inside the PHP source tree.
- `display-name`: value used for `php --ri` smoke tests and license display; empty string skips the `--ri` check.
- `os`: allowed OS families: `Linux`, `Darwin`, `Windows`.
Configure string placeholders:
- `@build_root_path@`: absolute buildroot path.
- `@shared_suffix@`: `=shared` in shared builds, empty in static builds.
- `@shared_path_suffix@`: `=shared,{buildroot}` in shared builds, `={buildroot}` in static builds.
## Platform Suffixes
Many fields support platform suffixes:
- `@unix`: Linux and macOS.
- `@linux`: Linux only.
- `@macos`: macOS only.
- `@windows`: Windows only.
For Linux, specific fields override generic fields in this order: `field@linux`, then `field@unix`, then `field`.
Use suffixes for declarative differences in dependencies, headers, libraries, pkg-configs, extension args, and suggestions.
## Validation and Tests
Run config validation for most package edits:
```bash
php bin/spc dev:lint-config
```
Run focused tests after config or registry changes:
```bash
vendor/bin/phpunit tests/StaticPHP/Config tests/StaticPHP/Registry tests/StaticPHP/Util/DependencyResolverTest.php --no-coverage
```
Run broader checks for framework or class changes:
```bash
composer test
composer analyse
```
Build commands are expensive. Use them for user-provided repros, package-specific fixes, or when validation cannot prove the behavior:
```bash
php bin/spc build:libs "openssl,curl" -vvv
php bin/spc build:php "bcmath,openssl,curl" --build-cli -vvv
```

View File

@@ -38,6 +38,9 @@ jobs:
- name: "windows-x64"
os: "ubuntu-latest"
filename: "spc-windows-x64.exe"
permissions:
id-token: write
attestations: write
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
@@ -105,6 +108,12 @@ jobs:
fi
fi
- name: "Generate build provenance attestation"
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@v4
with:
subject-path: "${{ github.workspace }}/${{ matrix.operating-system.name == 'windows-x64' && 'spc.exe' || 'spc' }}"
- name: "Copy file"
run: |
if [ "${{ matrix.operating-system.name }}" != "windows-x64" ]; then

View File

@@ -1,68 +1,68 @@
name: Docs build test and auto deploy
on:
pull_request:
branches: [ "v3" ]
types: [ opened, synchronize, reopened ]
paths:
- 'config/**.yml'
- 'docs/**'
- 'package.json'
- 'yarn.lock'
- '.github/workflows/vitepress-deploy.yml'
push:
branches: [ "v3" ]
paths:
- 'config/**.yml'
- 'docs/**'
- 'package.json'
- 'yarn.lock'
- '.github/workflows/vitepress-deploy.yml'
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
if: github.repository == 'crazywhalecc/static-php-cli'
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v3
- run: npm install
- name: "Install PHP for official runners"
uses: shivammathur/setup-php@v2
with:
coverage: none
tools: composer:v2
php-version: 8.4
ini-values: memory_limit=-1
extensions: curl, openssl, mbstring
- name: "Get Composer Cache Directory"
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: "Cache Composer dependencies"
uses: "actions/cache@v4"
with:
path: "${{ steps.composer-cache.outputs.dir }}"
key: "php-8.2-locked-composer-${{ hashFiles('**/composer.lock') }}"
restore-keys: |
php-8.2-locked-composer
- name: "Install Locked Dependencies"
run: "composer install --no-interaction --no-progress"
- name: Build
run: npm run docs:build
# Deploy to GitHub Pages only when the workflow is triggered by a push to the v3 branch
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
if: github.event_name == 'push' && github.ref == 'refs/heads/v3'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/.vitepress/dist
name: Docs build test and auto deploy
on:
pull_request:
branches: [ "v3" ]
types: [ opened, synchronize, reopened ]
paths:
- 'config/**.yml'
- 'docs/**'
- 'package.json'
- 'yarn.lock'
- '.github/workflows/vitepress-deploy.yml'
push:
branches: [ "v3" ]
paths:
- 'config/**.yml'
- 'docs/**'
- 'package.json'
- 'yarn.lock'
- '.github/workflows/vitepress-deploy.yml'
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
if: github.repository == 'crazywhalecc/static-php-cli'
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v3
- run: npm install
- name: "Install PHP for official runners"
uses: shivammathur/setup-php@v2
with:
coverage: none
tools: composer:v2
php-version: 8.4
ini-values: memory_limit=-1
extensions: curl, openssl, mbstring
- name: "Get Composer Cache Directory"
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: "Cache Composer dependencies"
uses: "actions/cache@v4"
with:
path: "${{ steps.composer-cache.outputs.dir }}"
key: "php-8.2-locked-composer-${{ hashFiles('**/composer.lock') }}"
restore-keys: |
php-8.2-locked-composer
- name: "Install Locked Dependencies"
run: "composer install --no-interaction --no-progress"
- name: Build
run: npm run docs:build
# Deploy to GitHub Pages only when the workflow is triggered by a push to the v3 branch
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
if: github.event_name == 'push' && github.ref == 'refs/heads/v3'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/.vitepress/dist

59
AGENTS.md Normal file
View File

@@ -0,0 +1,59 @@
# StaticPHP v3 Agent Guide
StaticPHP is a PHP CLI application for building static PHP binaries, PHP SAPIs, extensions, libraries, helper tools, and project bundles. Prefer the v3 architecture under `src/StaticPHP`, `src/Package`, `config/pkg`, and `config/artifact`.
This file is the shared entry point for coding agents such as Codex, OpenCode, Claude-compatible agents, and GitHub Copilot agents. GitHub Copilot also has `.github/copilot-instructions.md`.
## Skills
Detailed task workflows live in `.github/skills`. Use them when the task matches:
- `.github/skills/staticphp-package-maintenance`: add, modify, review, or validate package, artifact, extension, library, target, or tool definitions.
- `.github/skills/staticphp-build-troubleshooting`: diagnose build, download, doctor, shell, terminal, CI, smoke-test, or log failures.
If your agent does not automatically discover skills from `.github/skills`, read the matching `SKILL.md` manually before working on that task.
## Project Map
- `bin/spc`: primary CLI entrypoint.
- `composer.json`: PHP 8.4+, Symfony Console, DI, YAML, logger; useful scripts are `composer test`, `composer analyse`, `composer lint-config`, and `composer cs-fix`.
- `spc.registry.yml`: built-in `core` registry.
- `config/pkg/ext`: PHP extension package YAML, usually named `ext-*.yml`.
- `config/pkg/lib`: library package YAML.
- `config/pkg/target`: final build target and virtual target YAML.
- `config/pkg/tool`: helper tool package YAML.
- `config/artifact`: standalone artifact YAML for shared or complex sources/binaries.
- `log/`: build and shell execution logs (`spc.output.log`, `spc.shell.log`, and per-package CMake/config logs); controlled by `SPC_LOGS_DIR` and `SPC_ENABLE_LOG_FILE`.
- `src/StaticPHP`: framework core: registry loading, config validation, dependency resolution, package install/build pipeline, doctor, toolchains, runtime shell/executors, exceptions.
- `src/Package`: package-specific build logic registered by PHP attributes.
- `src/globals`: constants, helper functions, patches, smoke tests, bundled license text.
- `tests`: PHPUnit tests for config, registry, artifacts, dependency resolver, DI, commands, and utilities.
- `docs/en/develop` and `docs/zh/develop`: developer documentation. Some pages may be TODO; verify against source when behavior matters.
## Working Rules
- Keep changes scoped to the requested package, artifact, command, docs, or tests.
- Prefer existing package patterns over new abstractions. Search for a similar package first.
- Use structured YAML config; do not encode dependency logic in PHP when config fields are enough.
- Match package names exactly: extension packages use `ext-` in config and dependencies; `#[Extension('curl')]` expands to `ext-curl`.
- Use platform suffixes such as `@unix`, `@linux`, `@macos`, and `@windows` instead of runtime conditionals when the difference is declarative.
- Add or update PHP package classes only when build commands, validation, custom configure args, hooks, or source patching are required.
- Do not edit generated build directories (`buildroot/`, `source/`, `downloads/`, `pkgroot/`) as a fix.
## Validation
For config or package changes, run CS checks first:
```bash
bin/spc dev:lint-config
composer cs-fix
```
For broader PHP code changes, also run:
```bash
composer test
composer analyse
```
Use build commands only when needed for the task or when the user provided a repro, because full static builds can be slow and platform-sensitive.

View File

@@ -1,156 +1,156 @@
# StaticPHP
[![Chinese readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87%20%F0%9F%87%A8%F0%9F%87%B3-moccasin?style=flat-square)](README-zh.md)
[![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md)
[![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
[![Discord](https://img.shields.io/discord/nrSRbpMJ?label=Discord&logo=discord&style=flat-square)](https://discord.gg/xf6Rd4pEAk)
**StaticPHP** 是一个强大的工具,用于构建可移植的可执行文件,包括 PHP、扩展等。
> [!IMPORTANT]
> 我们正准备发布 **v3** 版本,届时项目名称将从 **static-php-cli** 更改为 **StaticPHP**。
> 此分支对应 v3 版本。v2 版本请查看 [v2 分支](https://github.com/crazywhalecc/static-php-cli/tree/main)。
> 请更新您的参考资料,并关注正式版发布。
## 特性
- :elephant: 支持多个 PHP 版本 - PHP 8.1, 8.2, 8.3, 8.4, 8.5
- :handbag: 构建零依赖的单文件 PHP 可执行程序
- :hamburger: 构建 **[phpmicro](https://github.com/static-php/phpmicro)** 自解压可执行文件(将 PHP 二进制和源码合并为单个文件)
- :pill: 自动构建环境检查器,支持自动修复
- :zap: 支持 `Linux``macOS``Windows`
- :wrench: 通过 vendor 模式和自定义注册表实现便捷扩展
- :books: 智能依赖管理
- 📦 自包含 `spc` 可执行文件,便于自安装
- :fire: 支持 100+ 热门 [PHP 扩展](https://static-php.dev/en/guide/extensions.html)
- :floppy_disk: 支持 UPX 压缩(二进制体积可缩小 30-50%
**单文件独立 php-cli**
<img width="700" alt="out1" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/01a2e60f-13b0-4242-a645-f7afa4936396">
**使用 phpmicro 将 PHP 代码与 PHP 解释器结合:**
<img width="700" alt="out2" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/46b7128d-fb72-4169-957e-48564c3ff3e2">
## 快速开始
### 1. 下载 spc 二进制
```bash
# For Linux x86_64
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-x86_64
# For Linux aarch64
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-aarch64
# macOS x86_64 (Intel)
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple)
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-macos-aarch64
# Windows (x86_64, win10 build 17063 or later, please install VS2022 first)
curl.exe -fsSL -o spc.exe https://dl.static-php.dev/v3/spc-bin/nightly/spc-windows-x64.exe
```
对于 macOS 和 Linux请先添加可执行权限
```bash
chmod +x ./spc
```
### 2. 构建静态 PHP
首先,创建 `craft.yml` 文件,并从 [扩展列表](https://static-php.dev/en/guide/extensions.html) 或 [命令生成器](https://static-php.dev/en/guide/cli-generator.html) 指定要包含的扩展:
```yml
# PHP version support: 8.1, 8.2, 8.3, 8.4, 8.5
php-version: 8.5
# Put your extension list here
extensions: "apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,gd,iconv,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,simplexml,sockets,sodium,sqlite3,tokenizer,xml,xmlreader,xmlwriter,xsl,zip,zlib"
sapi:
- cli
- micro
download-options:
parallel: 10
```
运行命令:
```bash
./spc craft
# 输出完整控制台日志
./spc craft -vvv
```
### 3. 静态 PHP 使用
现在你可以将 StaticPHP 构建的二进制复制到另一台机器并在无依赖环境下运行:
```
# php-cli
buildroot/bin/php -v
# phpmicro
echo '<?php echo "Hello world!\n";' > a.php
./spc micro:combine a.php -O my-app
./my-app
```
## 文档
当前 README 包含基础用法。有关 StaticPHP 的完整功能集,
请访问 <https://static-php.dev>。
## 直接下载
如果你暂时不想构建,或只想先测试,可以从 [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-bulk.yml) 下载示例预编译产物,或从自托管服务器下载。
我们为每个 PHP 版本提供 2 种扩展集合:
- **gigantic**:尽可能包含更多扩展,二进制大小约 100-150MB。
- **base**:仅包含 StaticPHP 自身使用的少量扩展,二进制大小约 10MB。
> WIP
### 在线构建(使用 GitHub Actions
当上方直接下载的二进制无法满足你的需求时,
你可以使用 GitHub Actions 轻松构建静态编译的 PHP
并同时自定义要编译的扩展列表。
1. Fork 此仓库。
2. 进入项目的 Actions 并选择 `CI`
3. 选择 `Run workflow`,填写你要编译的 PHP 版本、目标类型和扩展列表。(扩展用逗号分隔,例如 `bcmath,curl,mbstring`
4. 等待工作流执行完成后,进入对应运行记录并下载 `Artifacts`
如果你启用 `debug`,构建时将输出所有日志,包括编译日志,便于排查问题。
> 我们也计划在未来提供可复用的 GitHub Actions 工作流,
> 这样你无需 fork 本项目,也能在自己的仓库中轻松构建 static PHP。
## 贡献
如果你需要的扩展缺失,可以创建 issue。
如果你熟悉本项目,也欢迎发起 pull request。
如果你想贡献文档,请直接编辑 `docs/`
## 赞助本项目
你可以通过 [GitHub Sponsor](https://github.com/crazywhalecc) 赞助我或我的项目。你捐赠的一部分将用于维护 **static-php.dev** 服务器。
**特别感谢以下赞助商:**
<a href="https://beyondco.de/"><img src="/docs/public/images/beyondcode-seeklogo.png" width="300" alt="Beyond Code Logo" /></a>
<a href="https://nativephp.com/"><img src="/docs/public/images/nativephp-logo.svg" width="300" alt="NativePHP Logo" /></a>
## 开源许可证
本项目本身采用 MIT 许可证。
一些新添加的扩展和依赖可能来自其他项目。
这些源码文件头部也可能包含额外的 LICENSE 和 AUTHOR 信息。
请在编译后使用 `bin/spc dump-license` 命令导出项目中使用的开源许可证,
并遵守对应项目的 LICENSE。
# StaticPHP
[![Chinese readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87%20%F0%9F%87%A8%F0%9F%87%B3-moccasin?style=flat-square)](README-zh.md)
[![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md)
[![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
[![Discord](https://img.shields.io/discord/nrSRbpMJ?label=Discord&logo=discord&style=flat-square)](https://discord.gg/xf6Rd4pEAk)
**StaticPHP** 是一个强大的工具,用于构建可移植的可执行文件,包括 PHP、扩展等。
> [!IMPORTANT]
> 我们正准备发布 **v3** 版本,届时项目名称将从 **static-php-cli** 更改为 **StaticPHP**。
> 此分支对应 v3 版本。v2 版本请查看 [v2 分支](https://github.com/crazywhalecc/static-php-cli/tree/main)。
> 请更新您的参考资料,并关注正式版发布。
## 特性
- :elephant: 支持多个 PHP 版本 - PHP 8.1, 8.2, 8.3, 8.4, 8.5
- :handbag: 构建零依赖的单文件 PHP 可执行程序
- :hamburger: 构建 **[phpmicro](https://github.com/static-php/phpmicro)** 自解压可执行文件(将 PHP 二进制和源码合并为单个文件)
- :pill: 自动构建环境检查器,支持自动修复
- :zap: 支持 `Linux``macOS``Windows`
- :wrench: 通过 vendor 模式和自定义注册表实现便捷扩展
- :books: 智能依赖管理
- 📦 自包含 `spc` 可执行文件,便于自安装
- :fire: 支持 100+ 热门 [PHP 扩展](https://static-php.dev/en/guide/extensions.html)
- :floppy_disk: 支持 UPX 压缩(二进制体积可缩小 30-50%
**单文件独立 php-cli**
<img width="700" alt="out1" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/01a2e60f-13b0-4242-a645-f7afa4936396">
**使用 phpmicro 将 PHP 代码与 PHP 解释器结合:**
<img width="700" alt="out2" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/46b7128d-fb72-4169-957e-48564c3ff3e2">
## 快速开始
### 1. 下载 spc 二进制
```bash
# For Linux x86_64
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-x86_64
# For Linux aarch64
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-aarch64
# macOS x86_64 (Intel)
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple)
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-macos-aarch64
# Windows (x86_64, win10 build 17063 or later, please install VS2022 first)
curl.exe -fsSL -o spc.exe https://dl.static-php.dev/v3/spc-bin/nightly/spc-windows-x64.exe
```
对于 macOS 和 Linux请先添加可执行权限
```bash
chmod +x ./spc
```
### 2. 构建静态 PHP
首先,创建 `craft.yml` 文件,并从 [扩展列表](https://static-php.dev/en/guide/extensions.html) 或 [命令生成器](https://static-php.dev/en/guide/cli-generator.html) 指定要包含的扩展:
```yml
# PHP version support: 8.1, 8.2, 8.3, 8.4, 8.5
php-version: 8.5
# Put your extension list here
extensions: "apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,gd,iconv,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,simplexml,sockets,sodium,sqlite3,tokenizer,xml,xmlreader,xmlwriter,xsl,zip,zlib"
sapi:
- cli
- micro
download-options:
parallel: 10
```
运行命令:
```bash
./spc craft
# 输出完整控制台日志
./spc craft -vvv
```
### 3. 静态 PHP 使用
现在你可以将 StaticPHP 构建的二进制复制到另一台机器并在无依赖环境下运行:
```
# php-cli
buildroot/bin/php -v
# phpmicro
echo '<?php echo "Hello world!\n";' > a.php
./spc micro:combine a.php -O my-app
./my-app
```
## 文档
当前 README 包含基础用法。有关 StaticPHP 的完整功能集,
请访问 <https://static-php.dev>。
## 直接下载
如果你暂时不想构建,或只想先测试,可以从 [Actions](https://github.com/static-php/hosted/actions/workflows/build-php-bulk.yml) 下载示例预编译产物,或从自托管服务器下载。
我们为每个 PHP 版本提供 2 种扩展集合:
- **gigantic**:尽可能包含更多扩展,二进制大小约 100-150MB。
- **base**:仅包含 StaticPHP 自身使用的少量扩展,二进制大小约 10MB。
> WIP
### 在线构建(使用 GitHub Actions
当上方直接下载的二进制无法满足你的需求时,
你可以使用 GitHub Actions 轻松构建静态编译的 PHP
并同时自定义要编译的扩展列表。
1. Fork 此仓库。
2. 进入项目的 Actions 并选择 `CI`
3. 选择 `Run workflow`,填写你要编译的 PHP 版本、目标类型和扩展列表。(扩展用逗号分隔,例如 `bcmath,curl,mbstring`
4. 等待工作流执行完成后,进入对应运行记录并下载 `Artifacts`
如果你启用 `debug`,构建时将输出所有日志,包括编译日志,便于排查问题。
> 我们也计划在未来提供可复用的 GitHub Actions 工作流,
> 这样你无需 fork 本项目,也能在自己的仓库中轻松构建 static PHP。
## 贡献
如果你需要的扩展缺失,可以创建 issue。
如果你熟悉本项目,也欢迎发起 pull request。
如果你想贡献文档,请直接编辑 `docs/`
## 赞助本项目
你可以通过 [GitHub Sponsor](https://github.com/crazywhalecc) 赞助我或我的项目。你捐赠的一部分将用于维护 **static-php.dev** 服务器。
**特别感谢以下赞助商:**
<a href="https://beyondco.de/"><img src="/docs/public/images/beyondcode-seeklogo.png" width="300" alt="Beyond Code Logo" /></a>
<a href="https://nativephp.com/"><img src="/docs/public/images/nativephp-logo.svg" width="300" alt="NativePHP Logo" /></a>
## 开源许可证
本项目本身采用 MIT 许可证。
一些新添加的扩展和依赖可能来自其他项目。
这些源码文件头部也可能包含额外的 LICENSE 和 AUTHOR 信息。
请在编译后使用 `bin/spc dump-license` 命令导出项目中使用的开源许可证,
并遵守对应项目的 LICENSE。

View File

@@ -104,7 +104,7 @@ see <https://static-php.dev>.
## Direct Download
If you do not want to build yet or just want to test first, you can download example pre-compiled artifacts from [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-bulk.yml) or from a self-hosted server.
If you do not want to build yet or just want to test first, you can download example pre-compiled artifacts from [Actions](https://github.com/static-php/hosted/actions/workflows/build-php-bulk.yml) or from a self-hosted server.
We offer 2 types of extension sets for each PHP version:

View File

@@ -1,116 +1,116 @@
param (
[string] ${action}
)
function AddToPath {
param (
[string]$pathToAdd
)
$currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
if ($currentPath -notlike "*$pathToAdd*") {
$newPath = $currentPath + ";$pathToAdd"
[System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host "Added '$pathToAdd' to Path."
Write-Host "To remove path, use: " -NoNewline
Write-Host "bin/setup-runtime remove-path" -ForegroundColor Cyan
} else {
Write-Host "Path already exists."
}
}
function RemoveFromPath {
param (
[string]$pathToRemove
)
$currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
if ($currentPath -like "*$pathToRemove*") {
$newPath = $currentPath -replace [regex]::Escape(';' + $pathToRemove), ''
[System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host "Removed Path '$pathToRemove'"
} else {
Write-Host "Path '$pathToRemove' not in Path"
}
}
# working dir
$WorkingDir = (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Definition))
if ($action -eq 'add-path') {
AddToPath ($WorkingDir + '\runtime')
exit 0
} elseif ($action -eq 'remove-path') {
RemoveFromPath ($WorkingDir + '\runtime')
exit 0
} elseif (-not($action -eq '')) {
Write-Host ("Invalid action: " + $action) -ForegroundColor Red
exit 1
}
# get php 8.1 specific version
# php windows download
$PHPRuntimeUrl = "https://windows.php.net/downloads/releases/archives/php-8.4.4-nts-Win32-vs17-x64.zip"
Write-Host "Downloading PHP from: " -NoNewline
Write-Host $PHPRuntimeUrl -ForegroundColor Cyan
$ComposerUrl = "https://getcomposer.org/download/latest-stable/composer.phar"
# create dir
New-Item -Path "downloads" -ItemType Directory -Force | Out-Null
# download php
if (-not(Test-Path "downloads\php.zip"))
{
Write-Host "Downloading PHP ..."
Invoke-WebRequest $PHPRuntimeUrl -OutFile "downloads\php.zip"
}
# extract php
New-Item -Path "runtime" -ItemType Directory -Force | Out-Null
Write-Host "Extracting php.zip ..."
Expand-Archive -Path "downloads/php.zip" -DestinationPath "runtime" -Force
# make php.ini
Move-Item -Path "runtime\php.ini-production" -Destination "runtime\php.ini" -Force
$OriginINI = Get-Content -Path "runtime\php.ini" -Raw
$OriginINI = $OriginINI -replace ';extension=openssl', 'extension=openssl'
$OriginINI = $OriginINI -replace ';extension=curl', 'extension=curl'
$OriginINI = $OriginINI -replace ';extension=mbstring', 'extension=mbstring'
$OriginINI = $OriginINI -replace ';extension=sodium', 'extension=sodium'
$OriginINI = $OriginINI -replace ';extension_dir = "./"', ('extension_dir = "' + (Split-Path -Parent $MyInvocation.MyCommand.Definition) + '\..\runtime\ext"')
$OriginINI | Set-Content -Path "runtime\php.ini"
# download composer
if (-not(Test-Path "runtime\composer.phar"))
{
Write-Host "Downloading composer ..."
Invoke-WebRequest $ComposerUrl -OutFile "downloads\composer.phar"
Move-Item -Path "downloads\composer.phar" -Destination "runtime\composer.phar" -Force
}
# create runtime\composer.ps1
$ComposerContent = '
$WorkingDir = (Split-Path -Parent $MyInvocation.MyCommand.Definition)
& ($WorkingDir + "\php.exe") (Join-Path $WorkingDir "\composer.phar") @args
'
$ComposerContent | Set-Content -Path 'runtime\composer.ps1' -Encoding UTF8
Write-Host "Successfully downloaded PHP and Composer !" -ForegroundColor Green
Write-Host "Use static-php-cli: " -NoNewline
Write-Host "bin/spc" -ForegroundColor Cyan
Write-Host "Use php: " -NoNewline
Write-Host "runtime/php" -ForegroundColor Cyan
Write-Host "Use composer: " -NoNewline
Write-Host "runtime/composer" -ForegroundColor Cyan
Write-Host ""
Write-Host "Don't forget installing composer dependencies '" -NoNewline
Write-Host "runtime/composer install" -ForegroundColor Cyan -NoNewline
Write-Host "' before using static-php-cli !"
Write-Host ""
Write-Host "If you want to use this PHP for quality tools (like phpstan, php-cs-fixer) or other project,"
Write-Host "or use PHP, Composer as system executable,"
Write-Host "use '" -NoNewline
Write-Host "bin/setup-runtime add-path" -ForegroundColor Cyan -NoNewline
Write-Host "' to add runtime dir in Path."
param (
[string] ${action}
)
function AddToPath {
param (
[string]$pathToAdd
)
$currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
if ($currentPath -notlike "*$pathToAdd*") {
$newPath = $currentPath + ";$pathToAdd"
[System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host "Added '$pathToAdd' to Path."
Write-Host "To remove path, use: " -NoNewline
Write-Host "bin/setup-runtime remove-path" -ForegroundColor Cyan
} else {
Write-Host "Path already exists."
}
}
function RemoveFromPath {
param (
[string]$pathToRemove
)
$currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
if ($currentPath -like "*$pathToRemove*") {
$newPath = $currentPath -replace [regex]::Escape(';' + $pathToRemove), ''
[System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host "Removed Path '$pathToRemove'"
} else {
Write-Host "Path '$pathToRemove' not in Path"
}
}
# working dir
$WorkingDir = (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Definition))
if ($action -eq 'add-path') {
AddToPath ($WorkingDir + '\runtime')
exit 0
} elseif ($action -eq 'remove-path') {
RemoveFromPath ($WorkingDir + '\runtime')
exit 0
} elseif (-not($action -eq '')) {
Write-Host ("Invalid action: " + $action) -ForegroundColor Red
exit 1
}
# get php 8.1 specific version
# php windows download
$PHPRuntimeUrl = "https://windows.php.net/downloads/releases/archives/php-8.4.4-nts-Win32-vs17-x64.zip"
Write-Host "Downloading PHP from: " -NoNewline
Write-Host $PHPRuntimeUrl -ForegroundColor Cyan
$ComposerUrl = "https://getcomposer.org/download/latest-stable/composer.phar"
# create dir
New-Item -Path "downloads" -ItemType Directory -Force | Out-Null
# download php
if (-not(Test-Path "downloads\php.zip"))
{
Write-Host "Downloading PHP ..."
Invoke-WebRequest $PHPRuntimeUrl -OutFile "downloads\php.zip"
}
# extract php
New-Item -Path "runtime" -ItemType Directory -Force | Out-Null
Write-Host "Extracting php.zip ..."
Expand-Archive -Path "downloads/php.zip" -DestinationPath "runtime" -Force
# make php.ini
Move-Item -Path "runtime\php.ini-production" -Destination "runtime\php.ini" -Force
$OriginINI = Get-Content -Path "runtime\php.ini" -Raw
$OriginINI = $OriginINI -replace ';extension=openssl', 'extension=openssl'
$OriginINI = $OriginINI -replace ';extension=curl', 'extension=curl'
$OriginINI = $OriginINI -replace ';extension=mbstring', 'extension=mbstring'
$OriginINI = $OriginINI -replace ';extension=sodium', 'extension=sodium'
$OriginINI = $OriginINI -replace ';extension_dir = "./"', ('extension_dir = "' + (Split-Path -Parent $MyInvocation.MyCommand.Definition) + '\..\runtime\ext"')
$OriginINI | Set-Content -Path "runtime\php.ini"
# download composer
if (-not(Test-Path "runtime\composer.phar"))
{
Write-Host "Downloading composer ..."
Invoke-WebRequest $ComposerUrl -OutFile "downloads\composer.phar"
Move-Item -Path "downloads\composer.phar" -Destination "runtime\composer.phar" -Force
}
# create runtime\composer.ps1
$ComposerContent = '
$WorkingDir = (Split-Path -Parent $MyInvocation.MyCommand.Definition)
& ($WorkingDir + "\php.exe") (Join-Path $WorkingDir "\composer.phar") @args
'
$ComposerContent | Set-Content -Path 'runtime\composer.ps1' -Encoding UTF8
Write-Host "Successfully downloaded PHP and Composer !" -ForegroundColor Green
Write-Host "Use static-php-cli: " -NoNewline
Write-Host "bin/spc" -ForegroundColor Cyan
Write-Host "Use php: " -NoNewline
Write-Host "runtime/php" -ForegroundColor Cyan
Write-Host "Use composer: " -NoNewline
Write-Host "runtime/composer" -ForegroundColor Cyan
Write-Host ""
Write-Host "Don't forget installing composer dependencies '" -NoNewline
Write-Host "runtime/composer install" -ForegroundColor Cyan -NoNewline
Write-Host "' before using static-php-cli !"
Write-Host ""
Write-Host "If you want to use this PHP for quality tools (like phpstan, php-cs-fixer) or other project,"
Write-Host "or use PHP, Composer as system executable,"
Write-Host "use '" -NoNewline
Write-Host "bin/setup-runtime add-path" -ForegroundColor Cyan -NoNewline
Write-Host "' to add runtime dir in Path."

View File

@@ -1,12 +1,12 @@
$PHP_Exec = ".\runtime\php.exe"
if (-not(Test-Path $PHP_Exec)) {
$PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition
if (-not $PHP_Exec) {
Write-Host "Error: PHP not found, you need to install PHP on your system or use 'bin/setup-runtime'." -ForegroundColor Red
exit 1
}
}
& "$PHP_Exec" -d xdebug.mode=debug -d xdebug.client_host=127.0.0.1 -d xdebug.client_port=9003 -d xdebug.start_with_request=yes ("bin/spc") @args
exit $LASTEXITCODE
$PHP_Exec = ".\runtime\php.exe"
if (-not(Test-Path $PHP_Exec)) {
$PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition
if (-not $PHP_Exec) {
Write-Host "Error: PHP not found, you need to install PHP on your system or use 'bin/setup-runtime'." -ForegroundColor Red
exit 1
}
}
& "$PHP_Exec" -d xdebug.mode=debug -d xdebug.client_host=127.0.0.1 -d xdebug.client_port=9003 -d xdebug.start_with_request=yes ("bin/spc") @args
exit $LASTEXITCODE

View File

@@ -1,12 +1,12 @@
$PHP_Exec = ".\runtime\php.exe"
if (-not(Test-Path $PHP_Exec)) {
$PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition
if (-not $PHP_Exec) {
Write-Host "Error: PHP not found, you need to install PHP on your system or use 'bin/setup-runtime'." -ForegroundColor Red
exit 1
}
}
& "$PHP_Exec" ("bin/spc") @args
exit $LASTEXITCODE
$PHP_Exec = ".\runtime\php.exe"
if (-not(Test-Path $PHP_Exec)) {
$PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition
if (-not $PHP_Exec) {
Write-Host "Error: PHP not found, you need to install PHP on your system or use 'bin/setup-runtime'." -ForegroundColor Red
exit 1
}
}
& "$PHP_Exec" ("bin/spc") @args
exit $LASTEXITCODE

View File

@@ -10,7 +10,6 @@
"config",
"src",
"vendor/psr",
"vendor/laravel/prompts",
"vendor/symfony",
"vendor/php-di",
"vendor/zhamao"

View File

@@ -12,10 +12,8 @@
"php": ">=8.4",
"ext-mbstring": "*",
"ext-zlib": "*",
"laravel/prompts": "~0.1",
"php-di/php-di": "^7.1",
"symfony/console": "^5.4 || ^6 || ^7",
"symfony/process": "^7.2",
"symfony/yaml": "^7.2",
"zhamao/logger": "^1.1.4"
},

568
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,10 @@ ncurses:
license-files:
- COPYING
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/ncurses/'
regex: '/href="(?<file>ncurses-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist
url: 'https://ftpmirror.gnu.org/gnu/ncurses/'
regex: '/href="(?<file>ncurses-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -53,8 +53,8 @@ PHP_BUILD_PROVIDER="StaticPHP ${SPC_VERSION}"
SPC_ENABLE_LOG_FILE="yes"
; The LOG DIR for spc logs
SPC_LOGS_DIR="${WORKING_DIR}/log"
; Preserve old logs when running new builds
SPC_PRESERVE_LOGS="no"
; Preserve old logs when running new builds (code checks SPC_PRESERVE_LOG, not SPC_PRESERVE_LOGS)
SPC_PRESERVE_LOG="no"
; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them
; only useful for builds targeting not pure-static linking
@@ -68,8 +68,8 @@ SPC_PRESERVE_LOGS="no"
[windows]
; build target: win7-static
SPC_TARGET=native-windows
; php-sdk-binary-tools path
PHP_SDK_PATH="${WORKING_DIR}\php-sdk-binary-tools"
; MSYS2 root directory (msys64 subfolder), used by the Windows toolchain
SPC_MSYS2_PATH="${PKG_ROOT_PATH}\msys2-build-essentials\msys64"
; upx executable path
UPX_EXEC="${PKG_ROOT_PATH}\bin\upx.exe"
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches

View File

@@ -0,0 +1,20 @@
ext-fastchart:
type: php-extension
artifact:
source:
type: ghtar
repo: iliaal/fastchart
extract: php-src/ext/fastchart
prefer-stable: true
metadata:
license-files: [LICENSE]
depends:
- freetype
suggests:
- libpng
- libjpeg
- libwebp
php-extension:
os:
- Linux
- Darwin

View File

@@ -0,0 +1,14 @@
ext-fastjson:
type: php-extension
artifact:
source:
type: ghtar
repo: iliaal/fastjson
extract: php-src/ext/fastjson
prefer-stable: true
metadata:
license-files: [LICENSE]
php-extension:
os:
- Linux
- Darwin

View File

@@ -10,3 +10,5 @@ ext-gmssl:
license: PHP-3.01
depends:
- gmssl
php-extension:
arg-type: with-path

View File

@@ -10,6 +10,11 @@ ext-zip:
license: PHP-3.01
depends:
- libzip
depends@windows:
- libzip
- zlib
- xz
- bzip2
php-extension:
arg-type: custom
arg-type@windows: enable

View File

@@ -2,6 +2,10 @@ gettext:
type: library
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/gettext/'
regex: '/href="(?<file>gettext-(?<version>[^"]+)\.tar\.xz)"/'
source-mirror:
type: filelist
url: 'https://ftpmirror.gnu.org/gnu/gettext/'
regex: '/href="(?<file>gettext-(?<version>[^"]+)\.tar\.xz)"/'

View File

@@ -3,11 +3,12 @@ gmp:
artifact:
source:
type: filelist
url: 'https://ftpmirror.gnu.org/gnu/gmp/'
url: 'https://ftp.gnu.org/gnu/gmp/'
regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/'
source-mirror:
type: url
url: 'https://dl.static-php.dev/static-php-cli/deps/gmp/gmp-6.3.0.tar.xz'
type: filelist
url: 'https://ftpmirror.gnu.org/gnu/gmp/'
regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/'
metadata:
license-files: ['@/gmp.txt']
license: Custom

View File

@@ -2,6 +2,10 @@ idn2:
type: library
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/libidn/'
regex: '/href="(?<file>libidn2-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist
url: 'https://ftpmirror.gnu.org/gnu/libidn/'
regex: '/href="(?<file>libidn2-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -2,6 +2,10 @@ libiconv:
type: library
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/libiconv/'
regex: '/href="(?<file>libiconv-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist
url: 'https://ftpmirror.gnu.org/gnu/libiconv/'
regex: '/href="(?<file>libiconv-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -9,8 +9,10 @@ libssh2:
metadata:
license-files: [COPYING]
license: BSD-3-Clause
depends:
depends@unix:
- openssl
depends@windows:
- zlib
headers:
- libssh2.h
- libssh2_publickey.h

View File

@@ -2,6 +2,10 @@ libunistring:
type: library
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/libunistring/'
regex: '/href="(?<file>libunistring-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist
url: 'https://ftpmirror.gnu.org/gnu/libunistring/'
regex: '/href="(?<file>libunistring-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -17,7 +17,6 @@ openssl:
- zlib
depends@windows:
- zlib
- jom
headers:
- openssl
static-libs@unix:
@@ -26,3 +25,6 @@ openssl:
static-libs@windows:
- libssl.lib
- libcrypto.lib
tools@windows:
- jom
- strawberry-perl

View File

@@ -2,6 +2,10 @@ readline:
type: library
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/readline/'
regex: '/href="(?<file>readline-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist
url: 'https://ftpmirror.gnu.org/gnu/readline/'
regex: '/href="(?<file>readline-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -10,10 +10,8 @@ frankenphp:
license: MIT
depends:
- php-embed
- go-xcaddy
depends@windows:
- php-embed
- go-win
- pthreads4w
suggests@unix:
- brotli
@@ -24,3 +22,7 @@ frankenphp:
- frankenphp
static-bins@windows:
- frankenphp.exe
tools:
- go-xcaddy
tools@windows:
- go-win

View File

@@ -1,6 +0,0 @@
llvm-tools:
type: target
artifact:
binary: custom
depends:
- zig

View File

@@ -0,0 +1,8 @@
msys2-build-essentials:
type: target
artifact:
binary: custom
env:
SPC_MSYS2_PATH: '{pkg_root_path}/msys2-build-essentials/msys64'
path@windows:
- '{pkg_root_path}/msys2-build-essentials/msys64/usr/bin'

View File

@@ -1,5 +0,0 @@
nasm:
type: target
artifact:
binary:
windows-x86_64: { type: url, url: 'https://dl.static-php.dev/static-php-cli/deps/nasm/nasm-2.16.01-win64.zip', extract: { nasm.exe: '{php_sdk_path}/bin/nasm.exe', ndisasm.exe: '{php_sdk_path}/bin/ndisasm.exe' } }

View File

@@ -1,5 +0,0 @@
php-sdk-binary-tools:
type: target
artifact:
binary:
windows-x86_64: { type: git, rev: master, url: 'https://github.com/php/php-sdk-binary-tools.git', extract: '{php_sdk_path}' }

View File

@@ -1,9 +0,0 @@
pkg-config:
type: target
artifact:
source: 'https://dl.static-php.dev/static-php-cli/deps/pkg-config/pkg-config-0.29.2.tar.gz'
binary:
linux-x86_64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-x86_64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
linux-aarch64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-aarch64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
macos-x86_64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-x86_64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
macos-aarch64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-aarch64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }

View File

@@ -1,4 +0,0 @@
zig:
type: target
artifact:
binary: custom

View File

@@ -0,0 +1,9 @@
7za-win:
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://dl.static-php.dev/v3/tools/7zip/7za.exe', extract: '{pkg_root_path}/bin/7za.exe' }
tool:
provides:
- 7za.exe
binary-subdir: bin

View File

@@ -1,5 +1,5 @@
go-win:
type: target
type: tool
artifact:
binary: custom
env:
@@ -8,3 +8,8 @@ go-win:
GOPATH: '{pkg_root_path}/go-win/go'
path@windows:
- '{pkg_root_path}/go-win/bin'
tool:
provides:
- go.exe
- gofmt.exe
binary-subdir: go-win/bin

View File

@@ -1,5 +1,5 @@
go-xcaddy:
type: target
type: tool
artifact:
binary: custom
env:
@@ -8,5 +8,8 @@ go-xcaddy:
GOPATH: '{pkg_root_path}/go-xcaddy/go'
path@unix:
- '{pkg_root_path}/go-xcaddy/bin'
static-bins:
- xcaddy
tool:
provides:
- go
- gofmt
binary-subdir: go-xcaddy/bin

View File

@@ -1,7 +1,11 @@
jom:
type: target
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://download.qt.io/official_releases/jom/jom.zip', extract: '{pkg_root_path}/jom' }
path@windows:
- '{pkg_root_path}\jom'
tool:
provides:
- jom.exe
binary-subdir: jom

10
config/pkg/tool/nasm.yml Normal file
View File

@@ -0,0 +1,10 @@
nasm:
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://dl.static-php.dev/static-php-cli/deps/nasm/nasm-2.16.01-win64.zip', extract: { nasm.exe: '{pkg_root_path}/bin/nasm.exe', ndisasm.exe: '{pkg_root_path}/bin/ndisasm.exe' } }
tool:
provides:
- nasm.exe
- ndisasm.exe
binary-subdir: bin

View File

@@ -0,0 +1,13 @@
pkg-config:
type: tool
artifact:
source: 'https://dl.static-php.dev/static-php-cli/deps/pkg-config/pkg-config-0.29.2.tar.gz'
binary:
linux-x86_64: { type: ghrel, repo: static-php/hosted, match: pkg-config-x86_64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
linux-aarch64: { type: ghrel, repo: static-php/hosted, match: pkg-config-aarch64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
macos-x86_64: { type: ghrel, repo: static-php/hosted, match: pkg-config-x86_64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
macos-aarch64: { type: ghrel, repo: static-php/hosted, match: pkg-config-aarch64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
tool:
provides:
- pkg-config
binary-subdir: bin

View File

@@ -1,8 +1,12 @@
protoc:
type: target
type: tool
artifact:
binary:
linux-x86_64: { type: ghrel, repo: protocolbuffers/protobuf, match: 'protoc-([0-9.]+)-linux-x86_64\.zip', extract: '{pkg_root_path}/protoc' }
linux-aarch64: { type: ghrel, repo: protocolbuffers/protobuf, match: 'protoc-([0-9.]+)-linux-aarch_64\.zip', extract: '{pkg_root_path}/protoc' }
path:
- '{pkg_root_path}/protoc/bin'
tool:
provides:
- protoc
binary-subdir: protoc/bin

View File

@@ -1,5 +1,9 @@
strawberry-perl:
type: target
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip', extract: '{pkg_root_path}/strawberry-perl' }
tool:
provides:
- perl.exe
binary-subdir: strawberry-perl/perl/bin

View File

@@ -1,7 +1,13 @@
upx:
type: target
type: tool
artifact:
binary:
linux-x86_64: { type: ghrel, repo: upx/upx, match: upx.+-amd64_linux\.tar\.xz, extract: { upx: '{pkg_root_path}/bin/upx' } }
linux-aarch64: { type: ghrel, repo: upx/upx, match: upx.+-arm64_linux\.tar\.xz, extract: { upx: '{pkg_root_path}/bin/upx' } }
windows-x86_64: { type: ghrel, repo: upx/upx, match: upx.+-win64\.zip, extract: { upx.exe: '{pkg_root_path}/bin/upx.exe' } }
tool:
provides:
- upx
provides@windows:
- upx.exe
binary-subdir: bin

View File

@@ -1,5 +1,9 @@
vswhere:
type: target
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://github.com/microsoft/vswhere/releases/download/3.1.7/vswhere.exe', extract: '{pkg_root_path}/bin/vswhere.exe' }
tool:
provides:
- vswhere.exe
binary-subdir: bin

14
config/pkg/tool/zig.yml Normal file
View File

@@ -0,0 +1,14 @@
zig:
type: tool
artifact:
binary: custom
tool:
provides:
- zig
- zig-cc
- zig-c++
- zig-ar
- zig-ld.lld
- zig-ranlib
- zig-objcopy
binary-subdir: zig

View File

@@ -229,7 +229,7 @@ The following path placeholders are supported in string values of the `path`, `e
| `{working_dir}` | Working directory (project root) |
| `{download_path}` | Download cache directory (`downloads/`) |
| `{source_path}` | Extracted source directory (`source/`) |
| `{php_sdk_path}` | Windows PHP SDK directory |
| `{spc_msys2_path}` | MSYS2 root directory (`msys64/`) — Windows only |
## target Package Type

View File

@@ -58,7 +58,13 @@ A single-file hook API for lightweight patches may be provided in a future relea
### Windows-only: `--with-sdk-binary-dir` and `--vs-ver`
These options are no longer accepted on the command line. Instead, set the `PHP_SDK_PATH` environment variable to point to your PHP SDK binary tools directory. The Visual Studio version is now managed by the toolchain configuration.
These options are no longer accepted on the command line. In v3, the `php-sdk-binary-tools` dependency has been completely removed. v3 now manages its own **MSYS2** environment to support autotools-based library builds on Windows. Run `spc doctor --install` to download and configure MSYS2 automatically.
If you need to point to a custom MSYS2 installation, set the `SPC_MSYS2_PATH` environment variable to the `msys64` directory (e.g. `C:\msys64`). Visual Studio is now auto-detected by the toolchain — no manual version flag needed.
::: warning Migrating from v2
v2 relied on `php-sdk-binary-tools` and required `--with-sdk-binary-dir` and `--vs-ver` on every build invocation. In v3 these options are gone. Remove them from all CI scripts and run `spc doctor --install` once to set up the Windows build environment.
:::
## Renamed / Deprecated Options

View File

@@ -223,7 +223,7 @@ openssl:
| `{working_dir}` | 工作目录(项目根目录) |
| `{download_path}` | 下载缓存目录(`downloads/` |
| `{source_path}` | 解压源码目录(`source/` |
| `{php_sdk_path}` | Windows PHP SDK 目录 |
| `{spc_msys2_path}` | MSYS2 根目录(`msys64/`)——仅 Windows |
## target 包类型

View File

@@ -58,7 +58,13 @@ curl -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-x86_64
### Windows 专有:`--with-sdk-binary-dir` 和 `--vs-ver`
这两个选项已不再被命令行接受。请改为设置 `PHP_SDK_PATH` 环境变量,指向你的 PHP SDK binary tools 目录。Visual Studio 版本现在由工具链配置统一管理
这两个选项已不再被命令行接受。在 v3 中,`php-sdk-binary-tools` 依赖已被完全移除。v3 现在通过管理自己的 **MSYS2** 环境来支持 Windows 上基于 autotools 的库构建。运行 `spc doctor --install` 即可自动下载并配置 MSYS2
如需指向自定义 MSYS2 安装目录,请设置 `SPC_MSYS2_PATH` 环境变量,值为 `msys64` 目录路径(例如 `C:\msys64`。Visual Studio 版本现在由工具链自动检测,无需手动指定版本号。
::: warning 从 v2 迁移
v2 依赖 `php-sdk-binary-tools`,并在每次构建时需要传入 `--with-sdk-binary-dir``--vs-ver` 参数。在 v3 中这些选项已被移除。请从所有 CI 脚本中删除这些参数,并使用 `spc doctor --install` 一次性完成 Windows 构建环境的配置。
:::
## 已重命名 / 已弃用的选项

View File

@@ -1,6 +1,6 @@
parameters:
reportUnmatchedIgnoredErrors: false
level: 4
level: 5
phpVersion: 80400
paths:
- ./src/

View File

@@ -10,6 +10,7 @@ package:
- config/pkg/lib/
- config/pkg/target/
- config/pkg/ext/
- config/pkg/tool/
artifact:
config:
- config/artifact/

View File

@@ -1,162 +0,0 @@
<?php
declare(strict_types=1);
namespace Package\Artifact;
use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
use StaticPHP\Artifact\Downloader\Type\GitHubTokenSetupTrait;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Exception\BuildFailureException;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Package\PackageBuilder;
class llvm_tools
{
use GitHubTokenSetupTrait;
public const array TOOLS = ['llvm-objcopy', 'llvm-strip', 'llvm-profdata'];
/** Install prefix for the locally-built llvm-tools. */
public static function path(): string
{
return PKG_ROOT_PATH . '/llvm-tools';
}
/** Path to a binary under llvm-tools/bin (llvm-objcopy, llvm-strip, llvm-profdata, …). */
public static function binary(string $name = 'llvm-strip'): string
{
return self::path() . '/bin/' . $name;
}
/** True when every required TOOLS binary is present and executable. */
public static function isInstalled(): bool
{
foreach (self::TOOLS as $t) {
$p = self::binary($t);
if (!is_file($p) || !is_executable($p)) {
return false;
}
}
return true;
}
#[CustomBinary('llvm-tools', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
{
$llvmVersion = $this->detectLlvmVersion()
?? throw new DownloaderException('Could not detect a clang version on host; install zig or clang first');
$tarball = "llvm-project-{$llvmVersion}.src.tar.xz";
$url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-{$llvmVersion}/{$tarball}";
$tarballPath = DOWNLOAD_PATH . '/' . $tarball;
default_shell()->executeCurlDownload($url, $tarballPath, headers: $this->getGitHubTokenHeaders(), retries: $downloader->getRetry());
return DownloadResult::archive($tarball, ['url' => $url, 'version' => $llvmVersion], extract: '{source_path}/llvm-tools', verified: false, version: $llvmVersion);
}
#[CustomBinaryCheckUpdate('llvm-tools', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function checkUpdateBinary(?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
{
$llvmVersion = $this->detectLlvmVersion()
?? throw new DownloaderException('Could not detect a clang version on host; install zig or clang first');
return new CheckUpdateResult(
old: $old_version,
new: $llvmVersion,
needUpdate: $old_version === null || $llvmVersion !== $old_version,
);
}
#[AfterBinaryExtract('llvm-tools', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function postExtract(string $target_path): void
{
$this->buildForHost($target_path);
}
public function buildForHost(?string $sourceRoot = null): void
{
$sourceRoot ??= SOURCE_PATH . '/llvm-tools';
if (self::isInstalled()) {
return;
}
$llvmDir = "{$sourceRoot}/llvm";
if (!is_dir($llvmDir)) {
throw new BuildFailureException("llvm-tools: missing source at {$llvmDir} (extraction layout changed?)");
}
$buildDir = "{$sourceRoot}/build";
$installDir = self::path();
$binDir = self::path() . '/bin';
f_mkdir($buildDir, recursive: true);
f_mkdir($binDir, recursive: true);
$cmakeArgs = implode(' ', array_map('escapeshellarg', [
'-S', $llvmDir,
'-B', $buildDir,
'-DCMAKE_BUILD_TYPE=Release',
'-DLLVM_ENABLE_PROJECTS=',
'-DLLVM_TARGETS_TO_BUILD=',
'-DLLVM_INCLUDE_BENCHMARKS=OFF',
'-DLLVM_INCLUDE_TESTS=OFF',
'-DLLVM_INCLUDE_EXAMPLES=OFF',
'-DLLVM_INCLUDE_DOCS=OFF',
'-DLLVM_ENABLE_ZLIB=OFF',
'-DLLVM_ENABLE_ZSTD=OFF',
'-DLLVM_ENABLE_LIBXML2=OFF',
'-DLLVM_ENABLE_TERMINFO=OFF',
'-DLLVM_ENABLE_LIBEDIT=OFF',
'-DLLVM_ENABLE_LIBPFM=OFF',
'-DLLVM_BUILD_LLVM_DYLIB=OFF',
'-DLLVM_LINK_LLVM_DYLIB=OFF',
'-DBUILD_SHARED_LIBS=OFF',
'-DCMAKE_C_COMPILER=' . zig::binary('zig-cc'),
'-DCMAKE_CXX_COMPILER=' . zig::binary('zig-c++'),
'-DCMAKE_INSTALL_PREFIX=' . $installDir,
]));
$jobs = ApplicationContext::get(PackageBuilder::class)->concurrency;
$targetArgs = implode(' ', array_map(fn ($t) => '--target ' . escapeshellarg($t), self::TOOLS));
shell()
->setEnv(['SPC_TARGET' => GNU_ARCH . '-linux-musl'])
->exec('cmake ' . $cmakeArgs)
->exec('cmake --build ' . escapeshellarg($buildDir) . ' ' . $targetArgs . " -j {$jobs}");
foreach (self::TOOLS as $t) {
$built = "{$buildDir}/bin/{$t}";
if (!is_file($built)) {
throw new BuildFailureException("llvm-tools: missing build output {$built}");
}
copy($built, self::binary($t));
chmod(self::binary($t), 0755);
}
}
private function detectLlvmVersion(): ?string
{
if (!zig::isInstalled()) {
return null;
}
[$rc, $out] = shell()->execWithResult(escapeshellarg(zig::binary()) . ' cc --version', false);
if ($rc !== 0) {
return null;
}
return preg_match('/clang version (\d+\.\d+\.\d+)/', implode("\n", $out), $m) ? $m[1] : null;
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Package\Artifact;
use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\BinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\GlobalEnvManager;
class msys2_build_essentials
{
// MSYS subsystem packages required for autotools-based builds.
private const REQUIRED_PACKAGES = ['make', 'autoconf', 'automake', 'libtool', 'pkgconf', 'perl', 'bison', 're2c'];
#[CustomBinary('msys2-build-essentials', ['windows-x86_64'])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
{
// MSYS2 nightly self-extracting archive; running it with `-y -oTARGET` extracts to TARGET\msys64\.
$url = 'https://github.com/msys2/msys2-installer/releases/download/nightly-x86_64/msys2-base-x86_64-latest.sfx.exe';
$filename = 'msys2-base-x86_64-latest.sfx.exe';
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());
return DownloadResult::file(
$filename,
['url' => $url, 'version' => 'nightly'],
version: 'nightly',
extract: '{pkg_root_path}/msys2-build-essentials',
);
}
#[BinaryExtract('msys2-build-essentials', ['windows-x86_64'])]
public function extractBinary(string $source_file, string $target_path): void
{
$target_path = FileSystem::convertPath($target_path);
$source_file = FileSystem::convertPath($source_file);
// Guard: skip re-extraction if already initialized (marker written at end of this method).
$marker = "{$target_path}\\.spc-msys2-initialized";
if (file_exists($marker)) {
return;
}
if (!is_dir($target_path)) {
FileSystem::createDir($target_path);
}
cmd()->exec("\"{$source_file}\" -y -o\"{$target_path}\"");
$msys2_bin = "{$target_path}\\msys64\\usr\\bin";
if (!file_exists("{$msys2_bin}\\bash.exe")) {
throw new DownloaderException("MSYS2 extraction failed: bash.exe not found at {$msys2_bin}\\bash.exe");
}
// Add MSYS2 usr\bin to PATH so pacman.exe can load msys-2.0.dll.
GlobalEnvManager::addPathIfNotExists($msys2_bin);
GlobalEnvManager::putenv('CHERE_INVOKING=yes');
GlobalEnvManager::putenv('MSYSTEM=MSYS');
// Disable PGP signature checking: pacman-key --init requires a pseudo-TTY which is unavailable
// from PHP. Patching pacman.conf is the standard approach for CI pipelines.
$pacman_conf = "{$target_path}\\msys64\\etc\\pacman.conf";
FileSystem::replaceFileRegex($pacman_conf, '/^SigLevel\s*=.*$/m', 'SigLevel = Never');
$pacman = "{$target_path}\\msys64\\usr\\bin\\pacman.exe";
// Two-pass update as recommended by MSYS2 CI docs.
cmd()->exec("\"{$pacman}\" --noconfirm -Syuu");
cmd()->exec("\"{$pacman}\" --noconfirm -Syuu");
$pkgs = implode(' ', self::REQUIRED_PACKAGES);
cmd()->exec("\"{$pacman}\" --noconfirm -S --needed {$pkgs}");
FileSystem::writeFile($marker, date('Y-m-d H:i:s'));
}
#[AfterBinaryExtract('msys2-build-essentials', ['windows-x86_64'])]
public function afterExtract(string $target_path): void
{
$target_path = FileSystem::convertPath($target_path);
$msys2_root = "{$target_path}\\msys64";
GlobalEnvManager::putenv("SPC_MSYS2_PATH={$msys2_root}");
GlobalEnvManager::addPathIfNotExists("{$msys2_root}\\usr\\bin");
}
}

View File

@@ -15,23 +15,6 @@ use StaticPHP\Runtime\SystemTarget;
class zig
{
/** Directory zig extracts into. */
public static function path(): string
{
return PKG_ROOT_PATH . '/zig';
}
/** Path to a binary inside the zig install dir (zig, zig-cc, zig-c++, zig-ar, …). */
public static function binary(string $name = 'zig'): string
{
return self::path() . '/' . $name;
}
public static function isInstalled(): bool
{
return is_file(self::binary());
}
#[CustomBinary('zig', [
'linux-x86_64',
'linux-aarch64',

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Package\Extension;
use Package\Target\php;
use StaticPHP\Attribute\Package\BeforeStage;
use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Util\FileSystem;
#[Extension('gmssl')]
class gmssl extends PhpExtensionPackage
{
#[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-gmssl')]
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
#[PatchDescription('Fix ext-gmssl v1.1.1 compatibility with GmSSL >= 3.1.0 where SM2_VERIFY_CTX was removed (unified into SM2_SIGN_CTX)')]
public function patchSm2VerifyCtx(): void
{
// See: https://github.com/crazywhalecc/static-php-cli/issues/1182
FileSystem::replaceFileStr(
"{$this->getSourceDir()}/gmssl.c",
'SM2_VERIFY_CTX',
'SM2_SIGN_CTX'
);
}
#[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-gmssl')]
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
#[PatchDescription('Fix ext-gmssl v1.1.1: pbkdf2_hmac_sm3_genkey was renamed to sm3_pbkdf2 in GmSSL >= 3.2.0')]
public function patchPbkdf2Rename(): void
{
FileSystem::replaceFileStr(
"{$this->getSourceDir()}/gmssl.c",
'pbkdf2_hmac_sm3_genkey',
'sm3_pbkdf2'
);
}
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
#[PatchDescription('Add CHECK_LIB to config.w32 for static Windows builds')]
public function patchBeforeBuildconfWin(): bool
{
$configW32 = "{$this->getSourceDir()}/config.w32";
if (str_contains(FileSystem::readFile($configW32), 'CHECK_LIB(')) {
return false;
}
FileSystem::replaceFileStr(
$configW32,
'AC_DEFINE(',
'CHECK_LIB("gmssl.lib", "gmssl", PHP_GMSSL);' . PHP_EOL . 'AC_DEFINE('
);
return true;
}
}

View File

@@ -32,12 +32,15 @@ class xlswriter extends PhpExtensionPackage
#[PatchDescription('Fix Windows build: apply win32 patch and add UTF-8 BOM to theme.c')]
public function patchBeforeMakeForWindows(): void
{
$source_dir = $this->getSourceDir();
$theme_file = "{$source_dir}/library/libxlsx/src/theme.c";
// fix windows build with openssl extension duplicate symbol bug
SourcePatcher::patchFile('spc_fix_xlswriter_win32.patch', $this->getSourceDir());
$content = file_get_contents($this->getSourceDir() . '/library/libxlsxwriter/src/theme.c');
SourcePatcher::patchFile('spc_fix_xlswriter_win32.patch', $source_dir);
$content = file_get_contents($theme_file);
$bom = pack('CCC', 0xEF, 0xBB, 0xBF);
if (!str_starts_with($content, $bom)) {
file_put_contents($this->getSourceDir() . '/library/libxlsxwriter/src/theme.c', $bom . $content);
file_put_contents($theme_file, $bom . $content);
}
}
}

View File

@@ -22,6 +22,7 @@ class gettext_win
{
$ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the VS2022 (MSVC17) solution, which msbuild builds via forward compatibility.
'17' => '\MSVC17',
'16' => '\MSVC16',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"),
@@ -44,7 +45,9 @@ class gettext_win
{
$vs_ver_dir = ApplicationContext::get('gettext_win_vs_ver_dir');
cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}\\libintl_static")
->exec('msbuild libintl_static.vcxproj /t:Rebuild /p:Configuration=Release /p:Platform=x64 /p:WindowsTargetPlatformVersion=10.0');
// WholeProgramOptimization (/GL) emits LTCG objects that frankenphp's lld-link cannot
// read ("is not a native COFF file"); disable it so the .lib stays plain COFF.
->exec('msbuild libintl_static.vcxproj /t:Rebuild /p:Configuration=Release /p:Platform=x64 /p:WindowsTargetPlatformVersion=10.0 /p:WholeProgramOptimization=false');
FileSystem::createDir($lib->getLibDir());
FileSystem::createDir($lib->getIncludeDir());
// libintl_a.lib is the static library output; copy as libintl.lib for linker compatibility

View File

@@ -18,7 +18,9 @@ class gmssl
#[BuildFor('Darwin')]
public function build(LibraryPackage $lib): void
{
UnixCMakeExecutor::create($lib)->build();
UnixCMakeExecutor::create($lib)
->addConfigureArgs('-DENABLE_SM2_PRIVATE_KEY_EXPORT=ON')
->build();
}
#[BuildFor('Windows')]
@@ -33,6 +35,7 @@ class gmssl
'-G "NMake Makefiles"',
'-DWIN32=ON',
'-DBUILD_SHARED_LIBS=OFF',
'-DENABLE_SM2_PRIVATE_KEY_EXPORT=ON',
'-DCMAKE_BUILD_TYPE=Release',
'-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"',
'-DCMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"',
@@ -42,13 +45,13 @@ class gmssl
->toStep(1)
->build();
// fix cmake_install.cmake install prefix (GmSSL overrides it internally)
$installCmake = "{$buildDir}\\cmake_install.cmake";
FileSystem::writeFile(
$installCmake,
'set(CMAKE_INSTALL_PREFIX "' . str_replace('\\', '/', $lib->getBuildRootPath()) . '")' . PHP_EOL . FileSystem::readFile($installCmake)
);
cmd()->cd($buildDir)->exec('nmake gmssl XCFLAGS=/MT');
cmd()->cd($buildDir)->exec('nmake install XCFLAGS=/MT');
$libPath = "{$lib->getBuildRootPath()}/lib";
$incPath = "{$lib->getBuildRootPath()}/include/gmssl";
FileSystem::createDir($libPath);
FileSystem::createDir($incPath);
FileSystem::copy("{$buildDir}\\bin\\gmssl.lib", "{$libPath}/gmssl.lib");
FileSystem::copyDir("{$lib->getSourceDir()}\\include\\gmssl", $incPath);
}
}

View File

@@ -21,6 +21,7 @@ class libffi_win
{
$ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the vs17 solution, which msbuild builds via forward compatibility.
'17' => '\win32\vs17_x64',
'16' => '\win32\vs16_x64',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported!"),
@@ -33,7 +34,9 @@ class libffi_win
{
$vs_ver_dir = ApplicationContext::get('libffi_win_vs_ver_dir');
cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}")
->exec('msbuild libffi-msvc.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64');
// WholeProgramOptimization (/GL) emits LTCG objects that frankenphp's lld-link cannot
// read ("is not a native COFF file"); disable it so the .lib stays plain COFF.
->exec('msbuild libffi-msvc.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64 /p:WholeProgramOptimization=false');
FileSystem::createDir($lib->getLibDir());
FileSystem::createDir($lib->getIncludeDir());

View File

@@ -21,6 +21,7 @@ class libiconv_win
{
$ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the VS2022 (MSVC17) solution, which msbuild builds via forward compatibility.
'17' => '\MSVC17',
'16' => '\MSVC16',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"),
@@ -33,7 +34,9 @@ class libiconv_win
{
$vs_ver_dir = ApplicationContext::get('vs_ver_dir');
cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}")
->exec('msbuild libiconv.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64');
// WholeProgramOptimization (/GL) emits LTCG objects that frankenphp's lld-link cannot
// read ("is not a native COFF file"); disable it so the .lib stays plain COFF.
->exec('msbuild libiconv.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64 /p:WholeProgramOptimization=false');
FileSystem::createDir($lib->getLibDir());
FileSystem::createDir($lib->getIncludeDir());
FileSystem::copy("{$lib->getSourceDir()}{$vs_ver_dir}\\x64\\lib\\libiconv.lib", "{$lib->getLibDir()}\\libiconv.lib");

View File

@@ -42,13 +42,16 @@ class libsodium
{
$ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the vs2022 solution, which msbuild builds via forward compatibility.
'17' => '\vs2022',
'16' => '\vs2019',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"),
};
cmd()->cd("{$lib->getSourceDir()}\\builds\\msvc{$vs_ver_dir}")
->exec('msbuild libsodium.sln /t:Rebuild /p:Configuration=StaticRelease /p:Platform=x64 /p:PreprocessorDefinitions="SODIUM_STATIC=1"');
// WholeProgramOptimization (/GL) emits LTCG objects that frankenphp's lld-link cannot
// read ("is not a native COFF file"); disable it so the .lib stays plain COFF.
->exec('msbuild libsodium.sln /t:Rebuild /p:Configuration=StaticRelease /p:Platform=x64 /p:WholeProgramOptimization=false /p:PreprocessorDefinitions="SODIUM_STATIC=1"');
FileSystem::createDir($lib->getLibDir());
FileSystem::createDir($lib->getIncludeDir());

View File

@@ -18,6 +18,7 @@ class libssh2
{
WindowsCMakeExecutor::create($lib)
->addConfigureArgs(
'-DCRYPTO_BACKEND=WinCNG',
'-DENABLE_ZLIB_COMPRESSION=ON',
'-DBUILD_TESTING=OFF'
)

View File

@@ -21,6 +21,7 @@ class mpir
{
$ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the build.vc17 solution, which msbuild builds via forward compatibility.
'17' => '\build.vc17',
'16' => '\build.vc16',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"),

View File

@@ -24,7 +24,7 @@ class openssl
{
if (SystemTarget::getTargetOS() === 'Windows') {
global $argv;
$perl_path_native = PKG_ROOT_PATH . '\strawberry-perl-' . arch2gnu(php_uname('m')) . '-win\perl\bin\perl.exe';
$perl_path_native = PKG_ROOT_PATH . '\strawberry-perl\perl\bin\perl.exe';
$perl = file_exists($perl_path_native) ? ($perl_path_native) : WindowsUtil::findCommand('perl.exe');
if ($perl === null) {
throw new EnvironmentException(

View File

@@ -20,8 +20,8 @@ class unixodbc extends LibraryPackage
{
$sysconf_selector = match ($os = SystemTarget::getTargetOS()) {
'Darwin' => match (SystemTarget::getTargetArch()) {
'x86_64' => '/usr/local/etc',
'aarch64' => '/opt/homebrew/etc',
'x86_64' => is_dir('/usr/local/etc') ? '/usr/local/etc' : '/opt/local/etc',
'aarch64' => is_dir('/opt/homebrew/etc') ? '/opt/homebrew/etc' : '/opt/local/etc',
default => throw new WrongUsageException('Unsupported architecture: ' . GNU_ARCH),
},
'Linux' => '/etc',

View File

@@ -40,7 +40,7 @@ class curl
->optionalPackage('brotli', ...cmake_boolean_args('CURL_BROTLI'))
->addConfigureArgs(
'-DBUILD_CURL_EXE=ON',
'-DZSTD_LIBRARY=zstd_static.lib',
'-DZSTD_LIBRARY=' . BUILD_LIB_PATH . '/zstd_static.lib',
'-DBUILD_TESTING=OFF',
'-DBUILD_EXAMPLES=OFF',
'-DUSE_LIBIDN2=OFF',

View File

@@ -255,6 +255,13 @@ class php extends TargetPackage
$installer->addBuildPackage('php-embed');
}
// UPX compression: ensure the upx tool package is installed when requested.
// upx is a ToolPackage now, so it's installed directly rather than added as a
// library dependency (tool packages bypass the DependencyResolver graph).
if ($package->getBuildOption('with-upx-pack')) {
$installer->addInstallPackage('upx');
}
return [...$extensions_pkg, ...$additional_packages];
}

View File

@@ -39,6 +39,13 @@ trait windows
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf.bat'));
cmd()->cd($package->getSourceDir())->exec('.\buildconf.bat');
// Bypass the phpsdk_version check in configure.js: we use MSVC + msys2 instead of PHP SDK, so phpsdk_version is not available and the check would always fail.
FileSystem::replaceFileStr(
"{$package->getSourceDir()}\\configure.js",
'check_binary_tools_sdk();',
'/* check_binary_tools_sdk(); skipped: using MSVC + msys2 without PHP SDK */'
);
if ($package->getBuildOption('enable-micro-win32') && $installer->isPackageResolved('php-micro')) {
SourcePatcher::patchMicroWin32();
} else {
@@ -88,6 +95,17 @@ trait windows
cmd()->cd($package->getSourceDir())->exec(".\\configure.bat {$args} {$static_extension_str}");
}
#[BeforeStage('php', [self::class, 'makeCliForWindows'])]
#[PatchDescription('Patch Makefile to ensure buildroot/include comes before extension CFLAGS (fixes zip.h conflict with minizip)')]
public function patchMakefileIncludeOrder(TargetPackage $package): void
{
FileSystem::replaceFileStr(
"{$package->getSourceDir()}\\Makefile",
'$(CFLAGS_PHP_OBJ) $(CFLAGS)',
'$(CFLAGS) $(CFLAGS_PHP_OBJ)'
);
}
#[BeforeStage('php', [self::class, 'makeCliForWindows'])]
#[PatchDescription('Patch Windows Makefile for CLI target')]
public function patchCLITarget(TargetPackage $package): void
@@ -512,6 +530,7 @@ HEADER;
$vc_matches = ['unknown', 'unknown'];
} else {
$vc_matches = match ($vc['major_version']) {
'18', // VS 2026 shares the VS2022 (v143) runtime conventions, so it reports as VS17.
'17' => ['VS17', 'Visual C++ 2022'],
'16' => ['VS16', 'Visual C++ 2019'],
default => ['unknown', 'unknown'],

View File

@@ -2,17 +2,17 @@
declare(strict_types=1);
namespace Package\Target;
namespace Package\Tool;
use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\InitPackage;
use StaticPHP\Attribute\Package\Target;
use StaticPHP\Attribute\Package\Tool;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
use StaticPHP\Toolchain\Interface\ToolchainInterface;
#[Target('pkg-config')]
#[Tool('pkg-config')]
class pkgconfig
{
#[InitPackage]
@@ -23,7 +23,7 @@ class pkgconfig
#[BuildFor('Linux')]
#[BuildFor('Darwin')]
public function build(TargetPackage $package, ToolchainInterface $toolchain): void
public function build(ToolPackage $package, ToolchainInterface $toolchain): void
{
UnixAutoconfExecutor::create($package)
->appendEnv([

View File

@@ -644,7 +644,7 @@ class Artifact
'{artifact_name}' => $this->name,
'{pkg_root_path}' => PKG_ROOT_PATH,
'{build_root_path}' => BUILD_ROOT_PATH,
'{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: WORKING_DIR . '/php-sdk-binary-tools',
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH'),
'{working_dir}' => WORKING_DIR,
'{download_path}' => DOWNLOAD_PATH,
'{source_path}' => SOURCE_PATH,

View File

@@ -26,6 +26,7 @@ use StaticPHP\Exception\DownloaderException;
use StaticPHP\Exception\ExecutionException;
use StaticPHP\Exception\ValidationException;
use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Package\ToolVersionRegistry;
use StaticPHP\Registry\ArtifactLoader;
use StaticPHP\Runtime\Shell\Shell;
use StaticPHP\Runtime\SystemTarget;
@@ -346,12 +347,32 @@ class ArtifactDownloader
}
}
public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false): CheckUpdateResult
public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false, bool $use_installed = false): CheckUpdateResult
{
$artifact = ArtifactLoader::getArtifactInstance($artifact_name);
if ($artifact === null) {
throw new WrongUsageException("Artifact '{$artifact_name}' not found, please check the name.");
}
// --installed: prefer the version actually installed on disk (tool packages only) as the
// baseline, instead of the download cache. Falls back to the normal cache/bare logic below
// if this artifact has no recorded installed version (not a tool package, or not installed
// via the package installer yet).
if ($use_installed) {
$installed_version = ToolVersionRegistry::get($artifact_name);
if ($installed_version !== null) {
[$first, $second] = $prefer_source
? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name, $installed_version), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name, $installed_version)]
: [fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name, $installed_version), fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name, $installed_version)];
$result = $first() ?? $second();
if ($result !== null) {
return $result;
}
return new CheckUpdateResult(old: $installed_version, new: null, needUpdate: false, unsupported: true);
}
logger()->warning("Artifact '{$artifact_name}' has no recorded installed version (not a tool package, or not installed yet); falling back to download-cache based check.");
}
if ($bare) {
[$first, $second] = $prefer_source
? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name)]
@@ -403,16 +424,17 @@ class ArtifactDownloader
* @param bool $prefer_source Whether to prefer source over binary
* @param bool $bare Check without requiring artifact to be downloaded first
* @param null|callable $onResult Called immediately with (string $name, CheckUpdateResult) as each result arrives
* @param bool $use_installed Prefer the installed (on-disk) version over the download cache (tool packages only)
* @return array<string, CheckUpdateResult> Results keyed by artifact name
*/
public function checkUpdates(array $artifact_names, bool $prefer_source = false, bool $bare = false, ?callable $onResult = null): array
public function checkUpdates(array $artifact_names, bool $prefer_source = false, bool $bare = false, ?callable $onResult = null, bool $use_installed = false): array
{
if ($this->parallel > 1 && count($artifact_names) > 1) {
return $this->checkUpdatesWithConcurrency($artifact_names, $prefer_source, $bare, $onResult);
return $this->checkUpdatesWithConcurrency($artifact_names, $prefer_source, $bare, $onResult, $use_installed);
}
$results = [];
foreach ($artifact_names as $name) {
$result = $this->checkUpdate($name, $prefer_source, $bare);
$result = $this->checkUpdate($name, $prefer_source, $bare, $use_installed);
$results[$name] = $result;
if ($onResult !== null) {
($onResult)($name, $result);
@@ -436,7 +458,7 @@ class ArtifactDownloader
return $this->options[$name] ?? $default;
}
private function checkUpdatesWithConcurrency(array $artifact_names, bool $prefer_source, bool $bare, ?callable $onResult): array
private function checkUpdatesWithConcurrency(array $artifact_names, bool $prefer_source, bool $bare, ?callable $onResult, bool $use_installed = false): array
{
$results = [];
$fiber_pool = [];
@@ -451,8 +473,8 @@ class ArtifactDownloader
// fill pool
while (count($fiber_pool) < $this->parallel && !empty($remaining)) {
$name = array_shift($remaining);
$fiber = new \Fiber(function () use ($name, $prefer_source, $bare) {
return [$name, $this->checkUpdate($name, $prefer_source, $bare)];
$fiber = new \Fiber(function () use ($name, $prefer_source, $bare, $use_installed) {
return [$name, $this->checkUpdate($name, $prefer_source, $bare, $use_installed)];
});
$fiber->start();
$fiber_pool[$name] = $fiber;
@@ -491,12 +513,12 @@ class ArtifactDownloader
return $results;
}
private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name, ?string $old_version = null): ?CheckUpdateResult
{
if (($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this,
'old_version' => null,
'old_version' => $old_version,
]);
}
$config = $artifact->getDownloadConfig('source');
@@ -509,16 +531,16 @@ class ArtifactDownloader
}
/** @var CheckUpdateInterface $dl */
$dl = new $cls();
return $dl->checkUpdate($artifact_name, $config, null, $this);
return $dl->checkUpdate($artifact_name, $config, $old_version, $this);
}
private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_name, ?string $old_version = null): ?CheckUpdateResult
{
// custom binary callback takes precedence over config-based binary
if (($callback = $artifact->getCustomBinaryCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this,
'old_version' => null,
'old_version' => $old_version,
]);
}
$binary_config = $artifact->getDownloadConfig('binary');
@@ -532,7 +554,7 @@ class ArtifactDownloader
}
/** @var CheckUpdateInterface $dl */
$dl = new $cls();
return $dl->checkUpdate($artifact_name, $platform_config, null, $this);
return $dl->checkUpdate($artifact_name, $platform_config, $old_version, $this);
}
private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false): int

View File

@@ -614,7 +614,7 @@ class ArtifactExtractor
'{source_path}' => SOURCE_PATH,
'{download_path}' => DOWNLOAD_PATH,
'{working_dir}' => WORKING_DIR,
'{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: '',
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH') ?: '',
];
return str_replace(array_keys($replacement), array_values($replacement), $path);
}

View File

@@ -76,9 +76,10 @@ class DownloadResult
?string $version = null,
array $metadata = [],
?string $downloader = null,
mixed $extract = null,
): DownloadResult {
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
return new self($cache_type, config: $config, filename: $filename, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
return new self($cache_type, config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
}
/**

View File

@@ -45,7 +45,11 @@ class FileList implements DownloadTypeInterface, CheckUpdateInterface
throw new DownloaderException("Failed to get {$name} file list from {$config['url']}");
}
$versions = [];
logger()->debug('Matched ' . count($matches['version']) . " versions for {$name}");
$cnt = count($matches['version']);
if ($cnt === 0) {
throw new DownloaderException("Failed to get {$name} file list from {$config['url']}: no version parsed");
}
logger()->debug("Matched {$cnt} versions for {$name}");
foreach ($matches['version'] as $i => $version) {
$lower = strtolower($version);
foreach (['alpha', 'beta', 'rc', 'pre', 'nightly', 'snapshot', 'dev'] as $beta) {

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Attribute\Package;
/**
* Indicates that the annotated class defines a tool package.
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
readonly class Tool
{
public function __construct(public string $name) {}
}

View File

@@ -54,6 +54,10 @@ abstract class BaseCommand extends Command
}
set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) {
// Respect the @ suppression operator (error_reporting() returns 0 when @ is used)
if (error_reporting() === 0) {
return true;
}
$tips = [
E_WARNING => ['PHP Warning: ', 'warning'],
E_NOTICE => ['PHP Notice: ', 'notice'],

View File

@@ -23,6 +23,7 @@ class CheckUpdateCommand extends BaseCommand
$this->addArgument('artifact', InputArgument::OPTIONAL, 'The name of the artifact(s) to check for updates, comma-separated (default: all downloaded artifacts)');
$this->addOption('json', null, null, 'Output result in JSON format');
$this->addOption('bare', null, null, 'Check update without requiring the artifact to be downloaded first (old version will be null)');
$this->addOption('installed', null, null, 'Compare against the version actually installed on disk (tool packages only) instead of the download cache; falls back to normal behavior for artifacts with no recorded installed version');
$this->addOption('parallel', 'p', InputOption::VALUE_REQUIRED, 'Number of parallel update checks (default: 10)', 10);
// --with-php option for checking updates with a specific PHP version context
@@ -45,8 +46,9 @@ class CheckUpdateCommand extends BaseCommand
try {
$downloader = new ArtifactDownloader($this->input->getOptions());
$bare = (bool) $this->getOption('bare');
$use_installed = (bool) $this->getOption('installed');
if ($this->getOption('json')) {
$results = $downloader->checkUpdates($artifacts, bare: $bare);
$results = $downloader->checkUpdates($artifacts, bare: $bare, use_installed: $use_installed);
$outputs = [];
foreach ($results as $artifact => $result) {
$outputs[$artifact] = [
@@ -59,7 +61,7 @@ class CheckUpdateCommand extends BaseCommand
$this->output->writeln(json_encode($outputs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
return static::OK;
}
$downloader->checkUpdates($artifacts, bare: $bare, onResult: function (string $artifact, CheckUpdateResult $result) {
$downloader->checkUpdates($artifacts, bare: $bare, use_installed: $use_installed, onResult: function (string $artifact, CheckUpdateResult $result) {
if ($result->unsupported) {
$this->output->writeln("Artifact <info>{$artifact}</info> does not support update checking, <comment>skipped</comment>");
} elseif (!$result->needUpdate) {

View File

@@ -36,6 +36,15 @@ class CraftCommand extends BaseCommand
// set verbosity
$this->output->setVerbosity($craft['verbosity']);
// sync logger level and ApplicationContext debug mode to match the new verbosity
$level = match ($this->output->getVerbosity()) {
OutputInterface::VERBOSITY_VERBOSE => 'info',
OutputInterface::VERBOSITY_VERY_VERBOSE, OutputInterface::VERBOSITY_DEBUG => 'debug',
default => 'warning',
};
logger()->setLevel($level);
ApplicationContext::setDebug($this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG);
// apply env
array_walk($craft['extra-env'], fn ($v, $k) => f_putenv("{$k}={$v}"));
@@ -110,7 +119,7 @@ class CraftCommand extends BaseCommand
* shared-extensions: array<string>,
* packages: array<string>,
* sapi: array<string>,
* verbosity: int,
* verbosity: 128|16|256|32|64|8,
* debug: bool,
* clean-build: bool,
* build-options: array<string, mixed>,
@@ -171,11 +180,16 @@ class CraftCommand extends BaseCommand
}
// verbosity
$verbosity_level = $craft['verbosity'] ?? OutputInterface::VERBOSITY_NORMAL;
$debug = $craft['debug'] ?? false;
if ($debug) {
$verbosity_level = OutputInterface::VERBOSITY_DEBUG;
}
$verbosity_level = $debug
? OutputInterface::VERBOSITY_DEBUG
: match ((int) ($craft['verbosity'] ?? 0)) {
OutputInterface::VERBOSITY_QUIET => OutputInterface::VERBOSITY_QUIET,
OutputInterface::VERBOSITY_VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
OutputInterface::VERBOSITY_VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
OutputInterface::VERBOSITY_DEBUG => OutputInterface::VERBOSITY_DEBUG,
default => OutputInterface::VERBOSITY_NORMAL,
};
$craft['verbosity'] = $verbosity_level;
// clean-build (if true, reset before all builds)

View File

@@ -54,8 +54,8 @@ class DumpCapabilitiesCommand extends BaseCommand
{
$result = [];
// library / target / virtual-target
foreach (PackageLoader::getPackages(['library', 'target', 'virtual-target']) as $name => $pkg) {
// library / target / virtual-target / tool
foreach (PackageLoader::getPackages(['library', 'target', 'virtual-target', 'tool']) as $name => $pkg) {
$installable = [];
$artifact = $pkg->getArtifact();
if ($artifact !== null) {

View File

@@ -16,7 +16,7 @@ class GenExtTestMatrixCommand extends BaseCommand
private const array OS_RUNNERS = [
'linux' => ['arch' => 'x86_64', 'runner' => 'ubuntu-latest', 'os_key' => 'Linux'],
'windows' => ['arch' => 'x86_64', 'runner' => 'windows-latest', 'os_key' => 'Windows'],
'windows' => ['arch' => 'x86_64', 'runner' => 'windows-2025', 'os_key' => 'Windows'],
'macos' => ['arch' => 'aarch64', 'runner' => 'macos-15', 'os_key' => 'Darwin'],
];
@@ -60,6 +60,8 @@ class GenExtTestMatrixCommand extends BaseCommand
'glfw',
'imagick',
'intl',
'mongodb',
'gmssl',
];
/**

View File

@@ -154,7 +154,7 @@ class TestBotCommand extends BaseCommand
'targets' => array_values($targets),
'gen_matrix_args' => $gen_matrix_args,
'gen_matrix_args_tier2' => $gen_matrix_args_tier2,
'php_versions' => array_values($php_versions),
'php_versions' => $php_versions,
'tier2' => $tier2,
'comment_body' => $comment_body,
];
@@ -253,6 +253,13 @@ class TestBotCommand extends BaseCommand
$fmt($targets),
);
$available_labels = implode(', ', [
'`need-test` (gate)',
'`test/linux` `test/windows` `test/macos` (platform)',
'`test/tier2` (extra arch)',
'`test/php-83` `test/php-84` (PHP version)',
]);
// Case 1: need-test absent → invite the author to add it
if (!$need_test) {
return implode("\n", [
@@ -261,11 +268,9 @@ class TestBotCommand extends BaseCommand
'',
$detected,
'',
'To trigger extension build tests on this PR, add the `need-test` label:',
'To trigger extension build tests on this PR, add the `need-test` label.',
'',
'**Gate**: `need-test`',
'**Platform filter** (optional, default all): `test/linux` `test/windows` `test/macos` · `test/tier2`',
'**PHP version** (optional, default 8.5): `test/php-83` `test/php-84`',
'**Available labels**: ' . $available_labels,
]);
}
@@ -307,6 +312,7 @@ class TestBotCommand extends BaseCommand
'',
$detected,
'**Active labels**: ' . $labels_str,
'**Available labels**: ' . $available_labels,
'**Config**: ' . implode(' + ', $platform_parts) . ' | ' . $php_str,
]);
}

View File

@@ -13,7 +13,7 @@ class DoctorCommand extends BaseCommand
{
public function configure(): void
{
$this->addOption('auto-fix', null, InputOption::VALUE_OPTIONAL, 'Automatically fix failed items (if possible)', false);
$this->addOption('auto-fix', 'y', InputOption::VALUE_OPTIONAL, 'Automatically fix failed items (if possible)', false);
}
public function handle(): int

View File

@@ -23,7 +23,7 @@ class InstallPackageCommand extends BaseCommand
'The package to install (name or path)',
suggestedValues: function (CompletionInput $input) {
$packages = [];
foreach (PackageLoader::getPackages(['target', 'virtual-target']) as $name => $_) {
foreach (PackageLoader::getPackages(['target', 'virtual-target', 'tool']) as $name => $_) {
$packages[] = $name;
}
$val = $input->getCompletionValue();

View File

@@ -4,13 +4,14 @@ declare(strict_types=1);
namespace StaticPHP\Command;
use StaticPHP\Exception\SPCInternalException;
use StaticPHP\Runtime\Shell\Shell;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputOption;
use function Laravel\Prompts\confirm;
use Symfony\Component\Console\Question\ConfirmationQuestion;
#[AsCommand('reset')]
class ResetCommand extends BaseCommand
@@ -46,7 +47,11 @@ class ResetCommand extends BaseCommand
// Confirm with user unless --yes is specified
if (!$this->input->getOption('yes')) {
if (!confirm('Are you sure you want to continue?', false)) {
$helper = $this->getHelper('question');
if (!$helper instanceof QuestionHelper) {
throw new SPCInternalException('Question helper not provided');
}
if (!$helper->ask($this->input, $this->output, new ConfirmationQuestion('Are you sure you want to continue? [y/N] ', false))) {
InteractiveTerm::error(message: 'Reset operation cancelled.');
return static::SUCCESS;
}

View File

@@ -38,7 +38,7 @@ class ArtifactConfig
*/
public static function loadFromFile(string $file, string $registry_name): string
{
$content = file_get_contents($file);
$content = @file_get_contents($file);
if ($content === false) {
throw new WrongUsageException("Failed to read artifact config file: {$file}");
}

View File

@@ -19,6 +19,7 @@ enum ConfigType
'php-extension',
'target',
'virtual-target',
'tool',
];
public static function validateLicenseField(mixed $value): bool

View File

@@ -24,6 +24,10 @@ class ConfigValidator
'lang' => ConfigType::STRING,
'frameworks' => ConfigType::LIST_ARRAY, // @
// build-time tool dependency declaration (resolved independently of the library
// dependency graph, see PackageInstaller::collectRequiredTools())
'tools' => ConfigType::LIST_ARRAY, // @
// php-extension type fields
'php-extension' => ConfigType::ASSOC_ARRAY,
'zend-extension' => ConfigType::BOOL,
@@ -44,6 +48,13 @@ class ConfigValidator
'path' => ConfigType::LIST_ARRAY, // @
'env' => ConfigType::ASSOC_ARRAY, // @
'append-env' => ConfigType::ASSOC_ARRAY, // @
// tool type fields (nested under 'tool' key)
'tool' => ConfigType::ASSOC_ARRAY,
'provides' => ConfigType::LIST_ARRAY,
'binary-subdir' => ConfigType::STRING,
'install-root' => ConfigType::STRING,
'min-version' => ConfigType::STRING,
];
public const array PACKAGE_FIELDS = [
@@ -56,6 +67,9 @@ class ConfigValidator
'lang' => false,
'frameworks' => false, // @
// build-time tool dependency declaration
'tools' => false, // @
// php-extension type fields
'php-extension' => false,
@@ -67,6 +81,9 @@ class ConfigValidator
'path' => false, // @
'env' => false, // @
'append-env' => false, // @
// tool fields (nested object)
'tool' => false,
];
public const array SUFFIX_ALLOWED_FIELDS = [
@@ -78,6 +95,7 @@ class ConfigValidator
'path',
'env',
'append-env',
'tools',
];
public const array PHP_EXTENSION_FIELDS = [
@@ -92,6 +110,13 @@ class ConfigValidator
'os' => false,
];
public const array TOOL_FIELDS = [
'provides' => true,
'binary-subdir' => false,
'install-root' => false,
'min-version' => false,
];
public const array ARTIFACT_TYPE_FIELDS = [ // [required_fields, optional_fields]
'filelist' => [['url', 'regex'], ['extract']],
'git' => [['url'], ['extract', 'submodules', 'rev', 'regex']],
@@ -220,8 +245,8 @@ class ConfigValidator
$fields = self::SUFFIX_ALLOWED_FIELDS;
self::validateSuffixAllowedFields($name, $pkg, $fields, $suffixes);
// check if "library|target" package has artifact field for target and library types
if (in_array($pkg['type'], ['target', 'library']) && !isset($pkg['artifact'])) {
// check if "library|target|tool" package has artifact field
if (in_array($pkg['type'], ['target', 'library', 'tool']) && !isset($pkg['artifact'])) {
throw new ValidationException("Package [{$name}] in {$config_file_name} of type '{$pkg['type']}' must have an 'artifact' field");
}
@@ -235,6 +260,11 @@ class ConfigValidator
self::validatePhpExtensionFields($name, $pkg);
}
// check if "tool" package has tool specific fields and validate inside
if ($pkg['type'] === 'tool') {
self::validateToolFields($name, $pkg);
}
// check for unknown fields
self::validateNoInvalidFields('package', $name, $pkg, array_keys(self::PACKAGE_FIELD_TYPES));
}
@@ -397,6 +427,29 @@ class ConfigValidator
self::validateNoInvalidFields('php-extension', $name, $pkg['php-extension'], array_keys(self::PHP_EXTENSION_FIELDS));
}
/**
* Validate tool specific fields for tool package type.
*/
private static function validateToolFields(int|string $name, mixed $pkg): void
{
if (!isset($pkg['tool'])) {
throw new ValidationException("Package {$name} of type 'tool' must have a 'tool' field");
}
if (!is_assoc_array($pkg['tool'])) {
throw new ValidationException("Package {$name} [tool] must be an object");
}
foreach (self::TOOL_FIELDS as $field => $required) {
if ($required && !isset($pkg['tool'][$field])) {
throw new ValidationException("Package {$name} [tool] must have required field [{$field}]");
}
if (isset($pkg['tool'][$field])) {
self::validatePackageFieldType($field, $pkg['tool'][$field], $name);
}
}
// check for unknown fields in tool
self::validateNoInvalidFields('tool', $name, $pkg['tool'], array_keys(self::TOOL_FIELDS));
}
private static function validateNoInvalidFields(string $config_type, int|string $item_name, mixed $item_content, array $allowed_fields): void
{
foreach ($item_content as $k => $v) {

View File

@@ -16,7 +16,7 @@ class PackageConfig
/**
* Load package configurations from a specified directory.
* It will look for files matching the pattern 'pkg.*.json' and 'pkg.json'.
* Only processes .json, .yml, and .yaml files (skips .gitkeep etc.).
*/
public static function loadFromDir(string $dir, string $registry_name): array
{
@@ -28,6 +28,10 @@ class PackageConfig
$files = FileSystem::scanDirFiles($dir, false);
if (is_array($files)) {
foreach ($files as $file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
if (!in_array($ext, ['json', 'yml', 'yaml'], true)) {
continue;
}
self::loadFromFile($file, $registry_name);
$loaded[] = $file;
}

View File

@@ -32,7 +32,10 @@ use StaticPHP\Command\SPCConfigCommand;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Registry\Registry;
use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ConsoleApplication extends Application
{
@@ -104,4 +107,15 @@ class ConsoleApplication extends Application
{
self::$additional_commands = array_merge(self::$additional_commands, $additional_commands);
}
/**
* Hook Symfony's doRun() to inject the real input/output into InteractiveTerm before
* any command executes. From this point forward, InteractiveTerm uses the configured
* Console (respecting --no-ansi, --verbose, etc.) instead of the lazy STDERR fallback.
*/
public function doRun(InputInterface $input, OutputInterface $output): int
{
InteractiveTerm::init($input, $output);
return parent::doRun($input, $output);
}
}

View File

@@ -79,11 +79,11 @@ class ApplicationContext
/**
* Get a service from the container.
*
* @template T
* @template T of object
*
* @param class-string<T> $id Service identifier
* @param class-string<T>|string $id Service identifier
*
* @return null|T
* @return ($id is class-string<T> ? T : mixed)
*/
public static function get(string $id): mixed
{
@@ -98,25 +98,6 @@ class ApplicationContext
return self::getContainer()->has($id);
}
/**
* Resolve $id, returning null if it can't be constructed.
* PHP-DI's has() returns true for any autowirable class even when get()
* would throw on missing scalar args — for "is this resolvable right now"
* semantics use this.
*
* @template T
* @param class-string<T> $id
* @return null|T
*/
public static function tryGet(string $id): mixed
{
try {
return self::getContainer()->get($id);
} catch (\Throwable) {
return null;
}
}
/**
* Set a service in the container.
* Use sparingly - prefer configuration-based definitions.

View File

@@ -11,11 +11,14 @@ use StaticPHP\Registry\DoctorLoader;
use StaticPHP\Runtime\Shell\Shell;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use ZM\Logger\ConsoleColor;
use function Laravel\Prompts\confirm;
readonly class Doctor
{
public function __construct(private ?OutputInterface $output = null, private int $auto_fix = FIX_POLICY_PROMPT, public readonly bool $interactive = true)
@@ -125,9 +128,14 @@ readonly class Doctor
return false;
}
// prompt for fix
if ($this->auto_fix === FIX_POLICY_PROMPT && !confirm('Do you want to try to fix this issue now?')) {
$this->output?->writeln('<comment>You canceled fix.</comment>');
return false;
if ($this->auto_fix === FIX_POLICY_PROMPT) {
$helper = new QuestionHelper();
$input = ApplicationContext::has(InputInterface::class) ? ApplicationContext::get(InputInterface::class) : new ArrayInput([]);
$output = ApplicationContext::has(OutputInterface::class) ? ApplicationContext::get(OutputInterface::class) : $this->output ?? new ConsoleOutput();
if (!$helper->ask($input, $output, new ConfirmationQuestion('Do you want to try to fix this issue now? [Y/n] ', true))) {
$this->output?->writeln('<comment>You canceled fix.</comment>');
return false;
}
}
// perform fix
InteractiveTerm::indicateProgress("Fixing {$result->getFixItem()} ... ");

View File

@@ -73,13 +73,20 @@ class LinuxMuslCheck
$prefix = 'sudo ';
logger()->warning('Current user is not root, using sudo for running command');
}
$sysEnv = ['CC' => 'gcc', 'CXX' => 'g++', 'AR' => 'ar', 'LD' => 'ld', 'RANLIB' => 'ranlib'];
$envFlags = '';
foreach ($sysEnv as $k => $v) {
$envFlags .= "{$k}={$v} ";
}
$envFlags = rtrim($envFlags);
$shell = shell()->cd(SOURCE_PATH . '/musl-wrapper')
->exec('CC=gcc CXX=g++ AR=ar LD=ld ./configure --disable-gcc-wrapper')
->exec('CC=gcc CXX=g++ AR=ar LD=ld make -j');
->setEnv($sysEnv)
->exec('./configure --disable-gcc-wrapper')
->exec('make -j');
if ($prefix !== '') {
f_passthru('cd ' . SOURCE_PATH . "/musl-wrapper && CC=gcc CXX=g++ AR=ar LD=ld {$prefix}make install");
f_passthru('cd ' . SOURCE_PATH . "/musl-wrapper && {$envFlags} {$prefix}make install");
} else {
$shell->exec("CC=gcc CXX=g++ AR=ar LD=ld {$prefix}make install");
$shell->exec("{$prefix}make install");
}
return true;
}

View File

@@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Doctor\Item;
use Package\Artifact\llvm_tools;
use StaticPHP\Attribute\Doctor\CheckItem;
use StaticPHP\Attribute\Doctor\FixItem;
use StaticPHP\Attribute\Doctor\OptionalCheck;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Doctor\CheckResult;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Toolchain\Interface\ToolchainInterface;
use StaticPHP\Toolchain\ZigToolchain;
#[OptionalCheck([self::class, 'optionalCheck'])]
class LlvmToolsCheck
{
public static function optionalCheck(): bool
{
return ApplicationContext::get(ToolchainInterface::class) instanceof ZigToolchain;
}
/** @noinspection PhpUnused */
#[CheckItem('if llvm-tools (objcopy/strip/profdata) are built', level: 798)]
public function checkLlvmTools(): CheckResult
{
if (llvm_tools::isInstalled()) {
return CheckResult::ok(llvm_tools::path() . '/bin');
}
return CheckResult::fail('llvm-tools are not built', 'build-llvm-tools');
}
#[FixItem('build-llvm-tools')]
public function fixLlvmTools(): bool
{
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('llvm-tools');
$installer->run(true);
new llvm_tools()->buildForHost();
return llvm_tools::isInstalled();
}
}

Some files were not shown because too many files have changed in this diff Show More