Files
static-php-cli/.github/skills/staticphp-package-maintenance/references/build-class-patterns.md

144 lines
4.9 KiB
Markdown

# 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.