Compare commits

..

2 Commits

Author SHA1 Message Date
Jerry Ma
5a1fd1f388 Merge branch 'v3' into v3c/artifact-static-helpers 2026-05-29 17:32:02 +09:00
henderkes
91cf4f83b5 artifact: add path/binary/isInstalled static helpers
Give zig, rust, go_win and go_xcaddy a small consistent surface for
locating the install directory and a binary inside it:

- path(): install/extract root for the artifact
- binary($name = '<default>'): full path to a binary under that root,
  picking the artifact's natural layout (top-level for zig, bin/ for
  rust and the go toolchains)
- isInstalled(): is the default binary present on disk

Callers that previously concatenated PKG_ROOT_PATH . '/zig/zig' (and
the equivalents for the other artifacts) by hand can call the helpers
instead, and any later code that needs to ask "is this toolchain
available" can use isInstalled() without rebuilding the path.
2026-05-24 21:39:56 +07:00
145 changed files with 1472 additions and 3969 deletions

24
.gitattributes vendored
View File

@@ -1,24 +0,0 @@
# 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

View File

@@ -1,23 +0,0 @@
# 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

@@ -1,56 +0,0 @@
---
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

@@ -1,4 +0,0 @@
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

@@ -1,209 +0,0 @@
# 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

@@ -1,55 +0,0 @@
---
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

@@ -1,4 +0,0 @@
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

@@ -1,143 +0,0 @@
# 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

@@ -1,157 +0,0 @@
# 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,9 +38,6 @@ jobs:
- name: "windows-x64" - name: "windows-x64"
os: "ubuntu-latest" os: "ubuntu-latest"
filename: "spc-windows-x64.exe" filename: "spc-windows-x64.exe"
permissions:
id-token: write
attestations: write
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: "actions/checkout@v5" uses: "actions/checkout@v5"
@@ -108,12 +105,6 @@ jobs:
fi fi
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" - name: "Copy file"
run: | run: |
if [ "${{ matrix.operating-system.name }}" != "windows-x64" ]; then if [ "${{ matrix.operating-system.name }}" != "windows-x64" ]; then

View File

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

View File

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

View File

@@ -104,7 +104,7 @@ see <https://static-php.dev>.
## Direct Download ## 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/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/static-php-cli-hosted/actions/workflows/build-php-bulk.yml) or from a self-hosted server.
We offer 2 types of extension sets for each PHP version: We offer 2 types of extension sets for each PHP version:

View File

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

View File

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

View File

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

View File

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

View File

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

568
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -1,20 +0,0 @@
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

@@ -1,14 +0,0 @@
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,5 +10,3 @@ ext-gmssl:
license: PHP-3.01 license: PHP-3.01
depends: depends:
- gmssl - gmssl
php-extension:
arg-type: with-path

View File

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

View File

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

View File

@@ -2,13 +2,12 @@ gmp:
type: library type: library
artifact: artifact:
source: source:
type: filelist
url: 'https://ftp.gnu.org/gnu/gmp/'
regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/'
source-mirror:
type: filelist type: filelist
url: 'https://ftpmirror.gnu.org/gnu/gmp/' url: 'https://ftpmirror.gnu.org/gnu/gmp/'
regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/' 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'
metadata: metadata:
license-files: ['@/gmp.txt'] license-files: ['@/gmp.txt']
license: Custom license: Custom

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
go-win: go-win:
type: tool type: target
artifact: artifact:
binary: custom binary: custom
env: env:
@@ -8,8 +8,3 @@ go-win:
GOPATH: '{pkg_root_path}/go-win/go' GOPATH: '{pkg_root_path}/go-win/go'
path@windows: path@windows:
- '{pkg_root_path}/go-win/bin' - '{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: go-xcaddy:
type: tool type: target
artifact: artifact:
binary: custom binary: custom
env: env:
@@ -8,8 +8,5 @@ go-xcaddy:
GOPATH: '{pkg_root_path}/go-xcaddy/go' GOPATH: '{pkg_root_path}/go-xcaddy/go'
path@unix: path@unix:
- '{pkg_root_path}/go-xcaddy/bin' - '{pkg_root_path}/go-xcaddy/bin'
tool: static-bins:
provides: - xcaddy
- go
- gofmt
binary-subdir: go-xcaddy/bin

View File

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

View File

@@ -1,8 +0,0 @@
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

@@ -0,0 +1,5 @@
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

@@ -0,0 +1,5 @@
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

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

View File

@@ -1,9 +1,5 @@
strawberry-perl: strawberry-perl:
type: tool type: target
artifact: artifact:
binary: 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' } 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,13 +1,7 @@
upx: upx:
type: tool type: target
artifact: artifact:
binary: binary:
linux-x86_64: { type: ghrel, repo: upx/upx, match: upx.+-amd64_linux\.tar\.xz, extract: { upx: '{pkg_root_path}/bin/upx' } } 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' } } 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' } } 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,9 +1,5 @@
vswhere: vswhere:
type: tool type: target
artifact: artifact:
binary: 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' } 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

View File

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

View File

@@ -1,9 +0,0 @@
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,10 +0,0 @@
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

@@ -1,13 +0,0 @@
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,14 +0,0 @@
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) | | `{working_dir}` | Working directory (project root) |
| `{download_path}` | Download cache directory (`downloads/`) | | `{download_path}` | Download cache directory (`downloads/`) |
| `{source_path}` | Extracted source directory (`source/`) | | `{source_path}` | Extracted source directory (`source/`) |
| `{spc_msys2_path}` | MSYS2 root directory (`msys64/`) — Windows only | | `{php_sdk_path}` | Windows PHP SDK directory |
## target Package Type ## target Package Type

View File

@@ -58,13 +58,7 @@ A single-file hook API for lightweight patches may be provided in a future relea
### Windows-only: `--with-sdk-binary-dir` and `--vs-ver` ### Windows-only: `--with-sdk-binary-dir` and `--vs-ver`
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. 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.
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 ## Renamed / Deprecated Options

View File

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

View File

@@ -58,13 +58,7 @@ curl -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-x86_64
### Windows 专有:`--with-sdk-binary-dir` 和 `--vs-ver` ### Windows 专有:`--with-sdk-binary-dir` 和 `--vs-ver`
这两个选项已不再被命令行接受。在 v3 中,`php-sdk-binary-tools` 依赖已被完全移除。v3 现在通过管理自己的 **MSYS2** 环境来支持 Windows 上基于 autotools 的库构建。运行 `spc doctor --install` 即可自动下载并配置 MSYS2 这两个选项已不再被命令行接受。请改为设置 `PHP_SDK_PATH` 环境变量,指向你的 PHP SDK binary tools 目录。Visual Studio 版本现在由工具链配置统一管理
如需指向自定义 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: parameters:
reportUnmatchedIgnoredErrors: false reportUnmatchedIgnoredErrors: false
level: 5 level: 4
phpVersion: 80400 phpVersion: 80400
paths: paths:
- ./src/ - ./src/

View File

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

View File

@@ -15,6 +15,23 @@ use StaticPHP\Util\GlobalEnvManager;
class go_win class go_win
{ {
/** GOROOT for the Windows Go toolchain. */
public static function path(): string
{
return PKG_ROOT_PATH . '/go-win';
}
/** Path to a binary inside go-win's bin/ (go.exe, gofmt.exe, …). */
public static function binary(string $name = 'go.exe'): string
{
return self::path() . '/bin/' . $name;
}
public static function isInstalled(): bool
{
return is_file(self::binary());
}
#[CustomBinary('go-win', [ #[CustomBinary('go-win', [
'windows-x86_64', 'windows-x86_64',
])] ])]

View File

@@ -17,6 +17,23 @@ use StaticPHP\Util\System\LinuxUtil;
class go_xcaddy class go_xcaddy
{ {
/** GOROOT for the bundled Go toolchain used to build xcaddy. */
public static function path(): string
{
return PKG_ROOT_PATH . '/go-xcaddy';
}
/** Path to a binary inside go-xcaddy's bin/ (xcaddy, go, …). */
public static function binary(string $name = 'xcaddy'): string
{
return self::path() . '/bin/' . $name;
}
public static function isInstalled(): bool
{
return is_file(self::binary());
}
#[CustomBinary('go-xcaddy', [ #[CustomBinary('go-xcaddy', [
'linux-x86_64', 'linux-x86_64',
'linux-aarch64', 'linux-aarch64',

View File

@@ -1,93 +0,0 @@
<?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

@@ -16,6 +16,23 @@ use StaticPHP\Util\System\LinuxUtil;
class rust class rust
{ {
/** Install prefix the rust tarball's install.sh writes into. */
public static function path(): string
{
return PKG_ROOT_PATH . '/rust';
}
/** Path to a binary inside the rust install dir (cargo, rustc, rustup, …). */
public static function binary(string $name = 'cargo'): string
{
return self::path() . '/bin/' . $name;
}
public static function isInstalled(): bool
{
return is_file(self::binary());
}
#[CustomBinary('rust', [ #[CustomBinary('rust', [
'linux-x86_64', 'linux-x86_64',
'linux-aarch64', 'linux-aarch64',

View File

@@ -15,6 +15,23 @@ use StaticPHP\Runtime\SystemTarget;
class zig 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', [ #[CustomBinary('zig', [
'linux-x86_64', 'linux-x86_64',
'linux-aarch64', 'linux-aarch64',

View File

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

View File

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

View File

@@ -18,9 +18,7 @@ class gmssl
#[BuildFor('Darwin')] #[BuildFor('Darwin')]
public function build(LibraryPackage $lib): void public function build(LibraryPackage $lib): void
{ {
UnixCMakeExecutor::create($lib) UnixCMakeExecutor::create($lib)->build();
->addConfigureArgs('-DENABLE_SM2_PRIVATE_KEY_EXPORT=ON')
->build();
} }
#[BuildFor('Windows')] #[BuildFor('Windows')]
@@ -35,7 +33,6 @@ class gmssl
'-G "NMake Makefiles"', '-G "NMake Makefiles"',
'-DWIN32=ON', '-DWIN32=ON',
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
'-DENABLE_SM2_PRIVATE_KEY_EXPORT=ON',
'-DCMAKE_BUILD_TYPE=Release', '-DCMAKE_BUILD_TYPE=Release',
'-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"', '-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"',
'-DCMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"', '-DCMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"',
@@ -45,13 +42,13 @@ class gmssl
->toStep(1) ->toStep(1)
->build(); ->build();
cmd()->cd($buildDir)->exec('nmake gmssl XCFLAGS=/MT'); // 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)
);
$libPath = "{$lib->getBuildRootPath()}/lib"; cmd()->cd($buildDir)->exec('nmake install XCFLAGS=/MT');
$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,7 +21,6 @@ class libffi_win
{ {
$ver = WindowsUtil::findVisualStudio(); $ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) { $vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the vs17 solution, which msbuild builds via forward compatibility.
'17' => '\win32\vs17_x64', '17' => '\win32\vs17_x64',
'16' => '\win32\vs16_x64', '16' => '\win32\vs16_x64',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported!"), default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported!"),
@@ -34,9 +33,7 @@ class libffi_win
{ {
$vs_ver_dir = ApplicationContext::get('libffi_win_vs_ver_dir'); $vs_ver_dir = ApplicationContext::get('libffi_win_vs_ver_dir');
cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}") cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}")
// WholeProgramOptimization (/GL) emits LTCG objects that frankenphp's lld-link cannot ->exec('msbuild libffi-msvc.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64');
// 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->getLibDir());
FileSystem::createDir($lib->getIncludeDir()); FileSystem::createDir($lib->getIncludeDir());

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,6 @@ class mpir
{ {
$ver = WindowsUtil::findVisualStudio(); $ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) { $vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the build.vc17 solution, which msbuild builds via forward compatibility.
'17' => '\build.vc17', '17' => '\build.vc17',
'16' => '\build.vc16', '16' => '\build.vc16',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"), 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') { if (SystemTarget::getTargetOS() === 'Windows') {
global $argv; global $argv;
$perl_path_native = PKG_ROOT_PATH . '\strawberry-perl\perl\bin\perl.exe'; $perl_path_native = PKG_ROOT_PATH . '\strawberry-perl-' . arch2gnu(php_uname('m')) . '-win\perl\bin\perl.exe';
$perl = file_exists($perl_path_native) ? ($perl_path_native) : WindowsUtil::findCommand('perl.exe'); $perl = file_exists($perl_path_native) ? ($perl_path_native) : WindowsUtil::findCommand('perl.exe');
if ($perl === null) { if ($perl === null) {
throw new EnvironmentException( throw new EnvironmentException(

View File

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

View File

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

View File

@@ -255,13 +255,6 @@ class php extends TargetPackage
$installer->addBuildPackage('php-embed'); $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]; return [...$extensions_pkg, ...$additional_packages];
} }

View File

@@ -39,13 +39,6 @@ trait windows
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf.bat')); InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf.bat'));
cmd()->cd($package->getSourceDir())->exec('.\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')) { if ($package->getBuildOption('enable-micro-win32') && $installer->isPackageResolved('php-micro')) {
SourcePatcher::patchMicroWin32(); SourcePatcher::patchMicroWin32();
} else { } else {
@@ -95,17 +88,6 @@ trait windows
cmd()->cd($package->getSourceDir())->exec(".\\configure.bat {$args} {$static_extension_str}"); 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'])] #[BeforeStage('php', [self::class, 'makeCliForWindows'])]
#[PatchDescription('Patch Windows Makefile for CLI target')] #[PatchDescription('Patch Windows Makefile for CLI target')]
public function patchCLITarget(TargetPackage $package): void public function patchCLITarget(TargetPackage $package): void
@@ -530,7 +512,6 @@ HEADER;
$vc_matches = ['unknown', 'unknown']; $vc_matches = ['unknown', 'unknown'];
} else { } else {
$vc_matches = match ($vc['major_version']) { $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'], '17' => ['VS17', 'Visual C++ 2022'],
'16' => ['VS16', 'Visual C++ 2019'], '16' => ['VS16', 'Visual C++ 2019'],
default => ['unknown', 'unknown'], default => ['unknown', 'unknown'],

View File

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

View File

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

View File

@@ -26,7 +26,6 @@ use StaticPHP\Exception\DownloaderException;
use StaticPHP\Exception\ExecutionException; use StaticPHP\Exception\ExecutionException;
use StaticPHP\Exception\ValidationException; use StaticPHP\Exception\ValidationException;
use StaticPHP\Exception\WrongUsageException; use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Package\ToolVersionRegistry;
use StaticPHP\Registry\ArtifactLoader; use StaticPHP\Registry\ArtifactLoader;
use StaticPHP\Runtime\Shell\Shell; use StaticPHP\Runtime\Shell\Shell;
use StaticPHP\Runtime\SystemTarget; use StaticPHP\Runtime\SystemTarget;
@@ -347,32 +346,12 @@ class ArtifactDownloader
} }
} }
public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false, bool $use_installed = false): CheckUpdateResult public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false): CheckUpdateResult
{ {
$artifact = ArtifactLoader::getArtifactInstance($artifact_name); $artifact = ArtifactLoader::getArtifactInstance($artifact_name);
if ($artifact === null) { if ($artifact === null) {
throw new WrongUsageException("Artifact '{$artifact_name}' not found, please check the name."); 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) { if ($bare) {
[$first, $second] = $prefer_source [$first, $second] = $prefer_source
? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name)] ? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name)]
@@ -424,17 +403,16 @@ class ArtifactDownloader
* @param bool $prefer_source Whether to prefer source over binary * @param bool $prefer_source Whether to prefer source over binary
* @param bool $bare Check without requiring artifact to be downloaded first * @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 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 * @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, bool $use_installed = false): array public function checkUpdates(array $artifact_names, bool $prefer_source = false, bool $bare = false, ?callable $onResult = null): array
{ {
if ($this->parallel > 1 && count($artifact_names) > 1) { if ($this->parallel > 1 && count($artifact_names) > 1) {
return $this->checkUpdatesWithConcurrency($artifact_names, $prefer_source, $bare, $onResult, $use_installed); return $this->checkUpdatesWithConcurrency($artifact_names, $prefer_source, $bare, $onResult);
} }
$results = []; $results = [];
foreach ($artifact_names as $name) { foreach ($artifact_names as $name) {
$result = $this->checkUpdate($name, $prefer_source, $bare, $use_installed); $result = $this->checkUpdate($name, $prefer_source, $bare);
$results[$name] = $result; $results[$name] = $result;
if ($onResult !== null) { if ($onResult !== null) {
($onResult)($name, $result); ($onResult)($name, $result);
@@ -458,7 +436,7 @@ class ArtifactDownloader
return $this->options[$name] ?? $default; return $this->options[$name] ?? $default;
} }
private function checkUpdatesWithConcurrency(array $artifact_names, bool $prefer_source, bool $bare, ?callable $onResult, bool $use_installed = false): array private function checkUpdatesWithConcurrency(array $artifact_names, bool $prefer_source, bool $bare, ?callable $onResult): array
{ {
$results = []; $results = [];
$fiber_pool = []; $fiber_pool = [];
@@ -473,8 +451,8 @@ class ArtifactDownloader
// fill pool // fill pool
while (count($fiber_pool) < $this->parallel && !empty($remaining)) { while (count($fiber_pool) < $this->parallel && !empty($remaining)) {
$name = array_shift($remaining); $name = array_shift($remaining);
$fiber = new \Fiber(function () use ($name, $prefer_source, $bare, $use_installed) { $fiber = new \Fiber(function () use ($name, $prefer_source, $bare) {
return [$name, $this->checkUpdate($name, $prefer_source, $bare, $use_installed)]; return [$name, $this->checkUpdate($name, $prefer_source, $bare)];
}); });
$fiber->start(); $fiber->start();
$fiber_pool[$name] = $fiber; $fiber_pool[$name] = $fiber;
@@ -513,12 +491,12 @@ class ArtifactDownloader
return $results; return $results;
} }
private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name, ?string $old_version = null): ?CheckUpdateResult private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
{ {
if (($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) { if (($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [ return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this, ArtifactDownloader::class => $this,
'old_version' => $old_version, 'old_version' => null,
]); ]);
} }
$config = $artifact->getDownloadConfig('source'); $config = $artifact->getDownloadConfig('source');
@@ -531,16 +509,16 @@ class ArtifactDownloader
} }
/** @var CheckUpdateInterface $dl */ /** @var CheckUpdateInterface $dl */
$dl = new $cls(); $dl = new $cls();
return $dl->checkUpdate($artifact_name, $config, $old_version, $this); return $dl->checkUpdate($artifact_name, $config, null, $this);
} }
private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_name, ?string $old_version = null): ?CheckUpdateResult private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
{ {
// custom binary callback takes precedence over config-based binary // custom binary callback takes precedence over config-based binary
if (($callback = $artifact->getCustomBinaryCheckUpdateCallback()) !== null) { if (($callback = $artifact->getCustomBinaryCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [ return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this, ArtifactDownloader::class => $this,
'old_version' => $old_version, 'old_version' => null,
]); ]);
} }
$binary_config = $artifact->getDownloadConfig('binary'); $binary_config = $artifact->getDownloadConfig('binary');
@@ -554,7 +532,7 @@ class ArtifactDownloader
} }
/** @var CheckUpdateInterface $dl */ /** @var CheckUpdateInterface $dl */
$dl = new $cls(); $dl = new $cls();
return $dl->checkUpdate($artifact_name, $platform_config, $old_version, $this); return $dl->checkUpdate($artifact_name, $platform_config, null, $this);
} }
private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false): int 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, '{source_path}' => SOURCE_PATH,
'{download_path}' => DOWNLOAD_PATH, '{download_path}' => DOWNLOAD_PATH,
'{working_dir}' => WORKING_DIR, '{working_dir}' => WORKING_DIR,
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH') ?: '', '{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: '',
]; ];
return str_replace(array_keys($replacement), array_values($replacement), $path); return str_replace(array_keys($replacement), array_values($replacement), $path);
} }

View File

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

View File

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

View File

@@ -1,14 +0,0 @@
<?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,10 +54,6 @@ abstract class BaseCommand extends Command
} }
set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) { 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 = [ $tips = [
E_WARNING => ['PHP Warning: ', 'warning'], E_WARNING => ['PHP Warning: ', 'warning'],
E_NOTICE => ['PHP Notice: ', 'notice'], E_NOTICE => ['PHP Notice: ', 'notice'],

View File

@@ -23,7 +23,6 @@ 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->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('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('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); $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 // --with-php option for checking updates with a specific PHP version context
@@ -46,9 +45,8 @@ class CheckUpdateCommand extends BaseCommand
try { try {
$downloader = new ArtifactDownloader($this->input->getOptions()); $downloader = new ArtifactDownloader($this->input->getOptions());
$bare = (bool) $this->getOption('bare'); $bare = (bool) $this->getOption('bare');
$use_installed = (bool) $this->getOption('installed');
if ($this->getOption('json')) { if ($this->getOption('json')) {
$results = $downloader->checkUpdates($artifacts, bare: $bare, use_installed: $use_installed); $results = $downloader->checkUpdates($artifacts, bare: $bare);
$outputs = []; $outputs = [];
foreach ($results as $artifact => $result) { foreach ($results as $artifact => $result) {
$outputs[$artifact] = [ $outputs[$artifact] = [
@@ -61,7 +59,7 @@ class CheckUpdateCommand extends BaseCommand
$this->output->writeln(json_encode($outputs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); $this->output->writeln(json_encode($outputs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
return static::OK; return static::OK;
} }
$downloader->checkUpdates($artifacts, bare: $bare, use_installed: $use_installed, onResult: function (string $artifact, CheckUpdateResult $result) { $downloader->checkUpdates($artifacts, bare: $bare, onResult: function (string $artifact, CheckUpdateResult $result) {
if ($result->unsupported) { if ($result->unsupported) {
$this->output->writeln("Artifact <info>{$artifact}</info> does not support update checking, <comment>skipped</comment>"); $this->output->writeln("Artifact <info>{$artifact}</info> does not support update checking, <comment>skipped</comment>");
} elseif (!$result->needUpdate) { } elseif (!$result->needUpdate) {

View File

@@ -36,15 +36,6 @@ class CraftCommand extends BaseCommand
// set verbosity // set verbosity
$this->output->setVerbosity($craft['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 // apply env
array_walk($craft['extra-env'], fn ($v, $k) => f_putenv("{$k}={$v}")); array_walk($craft['extra-env'], fn ($v, $k) => f_putenv("{$k}={$v}"));
@@ -119,7 +110,7 @@ class CraftCommand extends BaseCommand
* shared-extensions: array<string>, * shared-extensions: array<string>,
* packages: array<string>, * packages: array<string>,
* sapi: array<string>, * sapi: array<string>,
* verbosity: 128|16|256|32|64|8, * verbosity: int,
* debug: bool, * debug: bool,
* clean-build: bool, * clean-build: bool,
* build-options: array<string, mixed>, * build-options: array<string, mixed>,
@@ -180,16 +171,11 @@ class CraftCommand extends BaseCommand
} }
// verbosity // verbosity
$verbosity_level = $craft['verbosity'] ?? OutputInterface::VERBOSITY_NORMAL;
$debug = $craft['debug'] ?? false; $debug = $craft['debug'] ?? false;
$verbosity_level = $debug if ($debug) {
? OutputInterface::VERBOSITY_DEBUG $verbosity_level = 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; $craft['verbosity'] = $verbosity_level;
// clean-build (if true, reset before all builds) // clean-build (if true, reset before all builds)

View File

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

View File

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

View File

@@ -154,7 +154,7 @@ class TestBotCommand extends BaseCommand
'targets' => array_values($targets), 'targets' => array_values($targets),
'gen_matrix_args' => $gen_matrix_args, 'gen_matrix_args' => $gen_matrix_args,
'gen_matrix_args_tier2' => $gen_matrix_args_tier2, 'gen_matrix_args_tier2' => $gen_matrix_args_tier2,
'php_versions' => $php_versions, 'php_versions' => array_values($php_versions),
'tier2' => $tier2, 'tier2' => $tier2,
'comment_body' => $comment_body, 'comment_body' => $comment_body,
]; ];
@@ -253,13 +253,6 @@ class TestBotCommand extends BaseCommand
$fmt($targets), $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 // Case 1: need-test absent → invite the author to add it
if (!$need_test) { if (!$need_test) {
return implode("\n", [ return implode("\n", [
@@ -268,9 +261,11 @@ class TestBotCommand extends BaseCommand
'', '',
$detected, $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:',
'', '',
'**Available labels**: ' . $available_labels, '**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`',
]); ]);
} }
@@ -312,7 +307,6 @@ class TestBotCommand extends BaseCommand
'', '',
$detected, $detected,
'**Active labels**: ' . $labels_str, '**Active labels**: ' . $labels_str,
'**Available labels**: ' . $available_labels,
'**Config**: ' . implode(' + ', $platform_parts) . ' | ' . $php_str, '**Config**: ' . implode(' + ', $platform_parts) . ' | ' . $php_str,
]); ]);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,10 +32,7 @@ use StaticPHP\Command\SPCConfigCommand;
use StaticPHP\Package\TargetPackage; use StaticPHP\Package\TargetPackage;
use StaticPHP\Registry\PackageLoader; use StaticPHP\Registry\PackageLoader;
use StaticPHP\Registry\Registry; use StaticPHP\Registry\Registry;
use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ConsoleApplication extends Application class ConsoleApplication extends Application
{ {
@@ -107,15 +104,4 @@ class ConsoleApplication extends Application
{ {
self::$additional_commands = array_merge(self::$additional_commands, $additional_commands); 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. * Get a service from the container.
* *
* @template T of object * @template T
* *
* @param class-string<T>|string $id Service identifier * @param class-string<T> $id Service identifier
* *
* @return ($id is class-string<T> ? T : mixed) * @return null|T
*/ */
public static function get(string $id): mixed public static function get(string $id): mixed
{ {

View File

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

View File

@@ -33,20 +33,15 @@ class MacOSToolCheck
'glibtoolize', 'glibtoolize',
]; ];
#[CheckItem('if homebrew or macports has installed', limit_os: 'Darwin', level: 998)] #[CheckItem('if homebrew has installed', limit_os: 'Darwin', level: 998)]
public function checkBrewOrPorts(): ?CheckResult public function checkBrew(): ?CheckResult
{ {
$brewPath = MacOSUtil::findCommand('brew'); if (($path = MacOSUtil::findCommand('brew')) === null) {
$portPath = MacOSUtil::findCommand('port');
if ($brewPath && $brewPath !== '/opt/homebrew/bin/brew' && getenv('GNU_ARCH') === 'aarch64') {
return CheckResult::fail('Current homebrew (/usr/local/bin/homebrew) is not installed for M1 Mac, please re-install homebrew in /opt/homebrew/ !');
}
if ($brewPath === null && $portPath === null) {
return CheckResult::fail('Homebrew is not installed', 'brew'); return CheckResult::fail('Homebrew is not installed', 'brew');
} }
if ($path !== '/opt/homebrew/bin/brew' && getenv('GNU_ARCH') === 'aarch64') {
return CheckResult::fail('Current homebrew (/usr/local/bin/homebrew) is not installed for M1 Mac, please re-install homebrew in /opt/homebrew/ !');
}
return CheckResult::ok(); return CheckResult::ok();
} }
@@ -65,8 +60,8 @@ class MacOSToolCheck
return CheckResult::ok(); return CheckResult::ok();
} }
#[CheckItem('if homebrew or macports llvm are installed', limit_os: 'Darwin')] #[CheckItem('if homebrew llvm are installed', limit_os: 'Darwin')]
public function checkBrewOrPortsLLVM(): ?CheckResult public function checkBrewLLVM(): ?CheckResult
{ {
if (getenv('SPC_USE_LLVM') === 'brew') { if (getenv('SPC_USE_LLVM') === 'brew') {
$homebrew_prefix = getenv('HOMEBREW_PREFIX') ?: (SystemTarget::getTargetArch() === 'aarch64' ? '/opt/homebrew' : '/usr/local/homebrew'); $homebrew_prefix = getenv('HOMEBREW_PREFIX') ?: (SystemTarget::getTargetArch() === 'aarch64' ? '/opt/homebrew' : '/usr/local/homebrew');
@@ -76,16 +71,6 @@ class MacOSToolCheck
} }
return CheckResult::ok($path); return CheckResult::ok($path);
} }
if (getenv('SPC_USE_LLVM') === 'port') {
$macportsPrefix = '/opt/local';
if (($path = MacOSUtil::findCommand('clang', ["{$macportsPrefix}/bin"])) === null) {
return CheckResult::fail('MacPorts llvm is not installed', 'build-tools', ['missing' => ['llvm']]);
}
return CheckResult::ok($path);
}
return null; return null;
} }
@@ -106,7 +91,7 @@ class MacOSToolCheck
if ($command_path !== []) { if ($command_path !== []) {
return CheckResult::fail("Current {$bison} version is too old: " . $matches[0]); return CheckResult::fail("Current {$bison} version is too old: " . $matches[0]);
} }
return $this->checkBisonVersion(['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin', '/opt/local/bin']); return $this->checkBisonVersion(['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin']);
} }
return CheckResult::ok($matches[0]); return CheckResult::ok($matches[0]);
} }
@@ -123,9 +108,6 @@ class MacOSToolCheck
#[FixItem('build-tools')] #[FixItem('build-tools')]
public function fixBuildTools(array $missing): bool public function fixBuildTools(array $missing): bool
{ {
$brewPath = MacOSUtil::findCommand('brew');
$portPath = MacOSUtil::findCommand('port');
$replacement = [ $replacement = [
'glibtoolize' => 'libtool', 'glibtoolize' => 'libtool',
]; ];
@@ -133,18 +115,7 @@ class MacOSToolCheck
if (isset($replacement[$cmd])) { if (isset($replacement[$cmd])) {
$cmd = $replacement[$cmd]; $cmd = $replacement[$cmd];
} }
shell()->exec('brew install --formula ' . escapeshellarg($cmd));
if ($brewPath !== null) {
shell()->exec('brew install --formula ' . escapeshellarg($cmd));
continue;
}
if ($portPath !== null) {
shell()->exec('port install ' . escapeshellarg($cmd));
continue;
}
return false;
} }
return true; return true;
} }

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