Troubleshooting the NextPDF Backport Builder
Build tooling — NOT a runtime dependency. Every symptom on this page is a build-time condition on a maintainer or continuous integration (CI) host. None of these conditions appears in a downstream application.
At a glance
Section titled “At a glance”The build runs five ordered stages. It stops at the first failure and prints the stage name and message. Read the stage name, then find the matching cause below. The stages are merge sources, run Rector downgrade, generate composer.json, copy static assets, and validate output. Verified against scripts/build.php (run() and step()).
Stage: merge sources
Section titled “Stage: merge sources””Source repo '' not found at: ”
Section titled “”Source repo '' not found at: ””The merge validates every expected source repository before it copies files. If a repository is missing, the build aborts and prints its name and path. The PHP 8.1 target expects nextpdf, nextpdf-Artisan, nextpdf-compat-legacy, nextpdf-Laravel, nextpdf-Symfony, nextpdf-CodeIgniter, and — when Pro is included — nextpdf-Pro. The PHP 7.4 target expects only nextpdf. Verified against scripts/merge-sources.php (run() validation loop, __construct() repository map).
Resolution: check out the repositories as sibling directories under the path passed to --source-dir, and use the exact directory names above. A dry run lists every repository it would read. Use it to confirm the layout before you run a full build.
Stage: run Rector downgrade
Section titled “Stage: run Rector downgrade”Rector exits non-zero
Section titled “Rector exits non-zero”The orchestrator reports Rector failed on <label> (exit code: N) and stops. The label identifies the pass that failed: public package, pro package, enum pre-processing, or full downgrade. Verified against scripts/build.php (runRectorPass()).
Default-parameter resolver crash on enum defaults (PHP 7.4)
Section titled “Default-parameter resolver crash on enum defaults (PHP 7.4)”This is why the PHP 7.4 target uses two passes. Rector’s default-parameter-value resolver crashes when an enum case is used as a constructor-promotion default. Pass 1 (rector-php74-enums.php) converts enums to constant-list classes first. The full pass in Pass 2 then never sees an enum case default. If you bypass the orchestrator and run the full PHP 7.4 configuration directly against source that contains enums, expect this crash. Verified against scripts/build.php (runRector() comment and two-pass sequence) and rector/config/rector-php74-enums.php.
Benchmark scripts crash Rector
Section titled “Benchmark scripts crash Rector”rector-php81.php and rector-php74.php skip */tests/Benchmark/*. Those scripts reference external Portable Document Format (PDF) libraries that Rector cannot resolve, which crashes the default-parameter resolver. If a benchmark path is processed, the skip glob is missing or the path differs. Verified against the withSkip() calls.
MHASH_XXH* crash (PHP 7.4)
Section titled “MHASH_XXH* crash (PHP 7.4)”rector-php74.php skips DowngradeHashAlgorithmXxHashRector. That built-in rule crashes on the xxHash constants. The source does not use xxHash, so the skip is safe. Verified against rector/config/rector-php74.php (withSkip()).
Stage: post-Rector fix-ups (PHP 7.4 only)
Section titled “Stage: post-Rector fix-ups (PHP 7.4 only)”The fix-ups run between the two passes. They rewrite patterns left by the enum-to-class rule. If the PHP 7.4 output has a parse error involving EnumClass::Case->value, ->name, a former enum method called as an instance method, or a named argument on an unresolved type, the fix-up did not match that pattern. The clone-with limitation also applies here: argument matching is non-recursive, so an override value with nested parentheses is not rewritten. Verified against scripts/build.php (postProcessFixups(), fixEnumMethodCallSites(), applyFixups()) and rector/rules/DowngradeCloneWithRector.php (documented limitation).
Stage: generate composer.json
Section titled “Stage: generate composer.json”This stage moves the processed src/ and tests/ directories from the build-temp directory to the output directory. It then writes the generated composer.json. A failure here is almost always a file system condition: the output directory is not writable, or the build-temp tree is missing because Rector produced nothing. Verified against scripts/build.php (adjustComposer(), moveTree()).
Stage: copy static assets
Section titled “Stage: copy static assets”This stage copies LICENSE from the core source repository and writes a generated CHANGELOG.md. If LICENSE is absent, the copy is skipped silently and the build continues; the changelog is always written. A failure here means the output directory became unwritable mid-build. Verified against scripts/build.php (copyStaticAssets()).
Stage: validate output
Section titled “Stage: validate output””Output src/ directory not found” / “No PHP files found in output”
Section titled “”Output src/ directory not found” / “No PHP files found in output””Validation requires a non-empty output/src. An empty tree means the merge copied nothing, or the file move failed. Verified against scripts/build.php (validateOutput()).
”Syntax validation: skipped (requires PHP runtime)”
Section titled “”Syntax validation: skipped (requires PHP runtime)””This is expected, not an error. The build host runs modern PHP, not the target runtime, so the local stage only counts files and prints the Docker command for a real syntax check. The authoritative syntax gate is the post-build php -l step in the release workflow, which runs under the actual target runtime. Verified against scripts/build.php (validateOutput()) and .github/workflows/build.yml (the PHP 8.1 / PHP 7.4 syntax-check steps).
Known limitations
Section titled “Known limitations”These limitations are inherent to the downgrade approach. They are verified against the rules and the project README.md “Known Limitations”:
- Readonly properties are removed. The build strips
readonlyso clone-with expansion can assign properties explicitly on the older runtime. The downgraded output no longer has runtime-enforced immutability. #[Override]is not enforced on PHP 8.1. The attribute may remain, but the older runtime does not act on it.- The PHP 7.4 target is core only. Framework adapters, the tcpdf-compatibility layer, and Pro are excluded from the PHP 7.4 distribution by construction of the build script.
- Pro is a separate package and PHP 8.1 only. There is no PHP 7.4 Pro build.
- Clone-with argument matching is non-recursive. Override values that contain nested parentheses are not transformed, and only string array keys resolve to property names.
- /integrations/backport/configuration/ — the rule and flag reference.
- /integrations/backport/production-usage/ — the CI gate and release lanes.