APK build
This is the stage between a verified merged stock APK and a signed, installable output. It consumes only artifacts that earlier stages already verified, and never touches the original split APKs.
Inputs
Every build input must be a regular file, not a symlink, and is refused otherwise:
| Input | Produced by |
|---|---|
| Merged stock APK | merge.md |
| Merge provenance | merge.md |
| Source provenance | google-play-integrity.md or providers.md |
| Morphe CLI, patch bundle, signer | toolchain.md |
| Toolchain provenance | toolchain.md |
| Signing keystore | this document |
Job expansion
expand_build_jobs turns configuration into concrete jobs. arch = "both" and arch = "all" expand into independent ARM64 and ARMv7 jobs, each resolving its own version, downloading its own splits, and writing its own output directory. Output paths and cache keys include package and architecture, so two architectures never overwrite each other.
build-mode = "both" additionally produces a root variant next to the non-root one.
Non-root and root variants
The two variants differ in package identity and in one patch:
| Non-root | Root | |
|---|---|---|
| Output package | patched-package when set, else stock | always stock |
GmsCore support | applied when selected | always removed |
| Output filename | <slug>-<version>-<arch>.apk | <slug>-<version>-<arch>-root.apk |
A root build must keep the stock package so it can be bind-mounted over the installed app, which also means GmsCore support is meaningless there and is stripped from includes, exclusives, and patch options. If an exclusive-patches selection consists only of GmsCore support, the root build fails rather than producing an unpatched APK.
Patching
Morphe Desktop CLI arguments were checked against live patch --help. The CLI is invoked as an argument array with the patch bundle passed explicitly, producing an unsigned architecture-specific APK.
Patches are enabled and disabled through argv, and patch options are passed as scalar values without shell interpolation. Morphe's option keys are global rather than per-patch, so two selected patches declaring the same option key fail instead of one silently winning.
A non-zero exit, a timeout, or a missing/symlinked output file fails the build with exit code 7.
Signing
uber-apk-signer arguments were checked against live --help. The keystore password is passed through process stdin, never through argv, logs, provenance, or filenames.
The signed output must then satisfy all of:
- readable APK with valid ZIP structure
- expected package (patched or stock, per variant)
- source version name and code preserved
- requested architecture preserved
- valid v2/v3 signature
- architecture-qualified filename
Any mismatch fails with exit code 7. An existing output file is refused rather than overwritten (exit code 9).
Keystores
Local builds accept any PKCS12 or JKS keystore. create_keystore invokes keytool with environment-backed password options, so the password never appears in command arguments.
CI defaults to the committed .github/template-release.p12, whose alias and password are both public. That makes template forks runnable and update-compatible out of the box, but it proves nothing about publisher identity: anyone can sign an APK with that certificate. A private keystore can override it through repository secrets.
Warning: back up a private keystore and its password in separate encrypted storage. Losing it makes future releases signature-incompatible with already-installed apps, which forces users to uninstall and lose app data.
Full policy: security.md and releases.md.
Output provenance
Each APK is written with a sibling <name>.provenance.json recording:
- resulting package, source package, and build mode
- version name and code, and architecture
- included, excluded, and exclusive patches plus resolved patch options
- linked hashes for source provenance, merge provenance, merged stock APK, Morphe CLI, patch bundle, signer, and toolchain provenance
- output filename, size, SHA-256, and signing certificate SHA-256
- a
smoke_testentry,pendinguntil the APK has been installed and launched on a matching device
The provenance chain means a published APK can be traced back through every tool version and every stock input hash that produced it.
Device validation status
Static verification proves the artifact is internally consistent; it does not prove the app runs. YouTube ARM64 has been installed and launched on a CPH2581 running Android 16. YouTube Music and ARMv7 device validation are still pending, and module runtime validation is tracked separately in modules.md.
