Architecture
Morphe Builder is a synchronous Python CLI orchestrating pinned external tools. It turns declarative TOML configuration and untrusted upstream inputs into verified APKs, optional root modules, and linked provenance.
Design priorities:
- verify before trust
- preserve original split APKs
- invoke external tools through public CLI boundaries
- publish complete results atomically
- keep configuration, not Python code, responsible for app matrix changes
System context
Python owns orchestration, validation, cache policy, provenance, and atomic publication. goopdl, Morphe CLI, APKEditor, and uber-apk-signer remain separate subprocesses.
Package responsibilities
| Module | Responsibility |
|---|---|
__init__.py | CLI parsing and dispatch, goopdl subprocess wrapper, Google manifest verification, APK inspection, split-set verification, source provenance. |
config.py | Strict tomllib parsing into immutable configuration records. |
toolchain.py | Java validation, GitHub release resolution, asset selection, checksums, cache locking, toolchain provenance. |
compatibility.py | Morphe patch listing, patch selection, compatible version resolution, confirmed version-code cache. |
providers.py | Fixed provider fallback order, URL validation, bounded downloads, bundle extraction, provider provenance. |
apkmirror.py | Resolve APKMirror catalog pages to concrete architecture-specific assets. |
uptodown.py | Resolve Uptodown pages to concrete assets. |
merge.py | Required split selection, APKEditor merge, merged-stock verification and provenance. |
build.py | Job expansion, Morphe patch command, signing, output verification, APK provenance. |
module.py | Deterministic Magisk/KernelSU module packaging and archive verification. |
orchestrator.py | End-to-end build, reporting, cache reuse, staging, fallback integration, summaries. |
release.py | Release artifact verification and preparation used by GitHub workflows. |
No general plugin or provider interface exists beyond callable boundaries already required by tests and fixed orchestration. Provider order is product policy, not runtime configuration.
Build lifecycle
Each app with arch = "both" or arch = "all" expands into independent ARM64 and ARMv7 jobs. build-mode = "both" additionally produces root APK and module artifacts.
If no provider can serve one app/architecture, that job is recorded as skipped. Build succeeds only when at least one job completes. Any integrity, merge, patch, signing, module, or publication failure aborts complete staged output.
Trust boundaries
Configuration
config.py rejects unknown keys, malformed URLs, duplicate packages or slugs, invalid signer pins, conflicting patch selections, and unsupported values before network access.
Configuration chooses desired inputs. It does not make downloaded bytes trusted.
External tools
Tool releases resolve to exact tags and unambiguous assets. Upstream checksums are used when available; local SHA-256, size, URL, repository, and resolved tag are always recorded.
Cached assets are validated before reuse. Exclusive lock files and atomic replacement protect concurrent writers.
Stock downloads
Provider content is untrusted until verify_apk_set confirms:
- expected package
- requested version name and code
- required split relationships
- requested architecture and density policy
- valid APK ZIP structure
- signing certificate consistency and optional signer pin
- Google manifest sizes and digests when Google Play supplied the set
Only verified sets move into cache/trusted/. Reuse triggers verification again.
Build outputs
Merged and patched APKs are inspected after each transformation. Signing password travels through environment or process stdin, never argv. Final files must match expected identity, version, architecture, ZIP structure, and signature before publication.
Static checks establish artifact consistency, not app behavior. Device launch and root-module behavior remain separate validation.
Provider fallback
Provider order is fixed:
google-play → direct → archive → apkmirror → uptodownFallback advances for provider or version unavailability, mismatched candidate identity, or ambiguous catalog resolution where nothing was trusted. Integrity metadata and signer failures remain terminal.
This distinction prevents a hostile or corrupted source from being hidden by a later successful source.
Storage model
Default cache root:
- Windows:
%LOCALAPPDATA%/morphe-builder - Linux:
$XDG_CACHE_HOME/morphe-builderor~/.cache/morphe-builder
cache/
├── tools/ # pinned public external tools
├── trusted/ # verified original stock split sets
└── work/ # disposable intermediate stateclean removes only work/. Trusted stock and tools survive cleanup and are revalidated before reuse.
Build output uses a temporary sibling directory. Final os.replace occurs only after all completed jobs, provenance, and summary are ready. Existing output paths are refused.
Provenance chain
Provenance records use hashes and relative links so each result can be traced to downloaded files, selected splits, external tools, patch choices, signer, architecture, and version.
Provenance proves which recorded inputs produced an artifact. It does not audit patch behavior or establish publisher identity when public template key is used.
CI architecture
| Workflow | Role |
|---|---|
ci.yml | Offline unit tests, lock validation, bytecode compilation across supported Python versions. |
security.yml | Scheduled dependency and license checks. |
manual-verified-build.yml | Reusable verified Android build and optional release publication. |
daily-verified-build.yml | Detect upstream tool changes and call release build only when needed. |
publish-pypi.yml | Build and publish Python distribution through Trusted Publishing. |
Pull-request CI receives no Google Play or signing secrets. Release jobs use protected environments when account credentials or private replacement keystores are configured.
