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

4.9 KiB

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:

#[Library('example')]
class example
{
    #[BuildFor('Linux')]
    public function build(LibraryPackage $pkg): void
    {
        (new UnixAutoconfExecutor($pkg))
            ->configure()
            ->make();
    }
}

Extension hook into PHP build:

#[Extension('curl')]
class curl
{
    #[BeforeStage('php', [php::class, 'makeForWindows'], 'ext-curl')]
    public function patchBeforePhpBuild(): void
    {
        // Adjust env, generated build files, or linker flags.
    }
}

Validation:

#[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:

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