Skip to content

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:

InputProduced by
Merged stock APKmerge.md
Merge provenancemerge.md
Source provenancegoogle-play-integrity.md or providers.md
Morphe CLI, patch bundle, signertoolchain.md
Toolchain provenancetoolchain.md
Signing keystorethis 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-rootRoot
Output packagepatched-package when set, else stockalways stock
GmsCore supportapplied when selectedalways 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_test entry, pending until 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.

Released under the MIT License.