Status: draft 2 — step two underway (2026-06-20)
Author: initial survey pass + bp/cmake comparison audit
Scope of this document: orientation + locked decisions. Step one (survey) and
the cmake/bp audit are done. Step two (build the converter, prototype on
libbase) is now in progress.
- Converter language: Python. (
python3already required; AOSP codegen is already Python; no JVM/Gradle in the codegen path.) - Blueprint parsing: hand-written parser. Small grammar, no external dependency, exactly the features we need. (Section 6.1.)
- First target snapshot: the archive's 2023 submodule pins. Keeps the
hand-written
.cmakeas a validation baseline while we build the converter; submodule updates are a separate later step. - libc: glibc only for now; musl deferred. (Section 5.2 item 5, Section 7.)
- Verified host toolchain: python 3.14, cmake 4.2, ninja 1.13, clang 21, gcc 15.
- Converter built (
tools/bp2cmake/): Layer 1 (lexer/parser/evaluator), Layer 2 (overlay/port_policy.py), Layer 3 (emitter). 16 unit tests pass. - Foundational layer DONE and validated: all 8 libs the archive builds
before
art/—libbase,liblog,libnativehelper,libprocinfo,libziparchive,libtinyxml2,liblzma,libcpu_features— are converted from.bp, compile, and link together (real liblog, no stub). Seetools/verify/foundational/RESULT.md. Generated CMake already fixes two archive bugs (droppederrors_unix.cpp; warning flags only on C TUs). - Project-owned
//compatinclude root established for vendored shim headers replacing absent Android deps (first:gtest/gtest_prod.h). Distinct from the read-only AOSP tree and fromnative/jdwpheader-style glue. - Next milestone: the ART core (
libartbase,libdexfile,runtime,dalvikvm, ...). Substantially larger: requires modellingart.goflag injection (Section 5.1), the generated-source pipeline (operator_out, mterp asm, asm_defines), and the bigger overlay surface from the audit (palette fake, CMS GC, ART_TARGET_LINUX, dropped statsd/libdl_android, etc.).
- Layer 1 extended for ART:
soong_config_variables(resolvesource_build=true→ ART modules enabled),codegenselect (x86_64 → x86_64+x86),avx2sub-select (off).art_defaultsitself is a normalcc_defaults, so its cflags inherit automatically. - Generated-source pipeline DONE: emitter models
gensrcs→add_custom_commandrunning the Python codegen tool, outputs staged under${MDVM_GENSRC_DIR}and compiled into the consumer. - art.go-injected defines handled via a PUBLIC overlay channel
(
add_public_defines), so knobs absent from any.bp(stack gaps, frame limit) propagate to consumers. - Validated:
libartpalette.so(no codegen) andlibartbase.so(2.9 MB, 3 generatedoperator_out.cc, full dep chain incl. host libcap) both build and link. Seetools/verify/{artpalette,artbase}/RESULT.md. - 13-module combined graph builds (
tools/verify/artcore/): 8 foundationallibartpalette,libartbase,libdexfile,libelffile,libprofile.libprofile.solinks the full converted closure. PUBLIC-define propagation confirmed (dexfile gets artbase's art.go defines via the link, unrestated).
libart (the runtime) AND the dalvikvm executable now build end-to-end from
the converter + codegen driver, and the VM RUNS:
$ LD_LIBRARY_PATH=. ./dalvikvm -showversion
ART version 2.1.0 x86_64
dalvikvm(PIE, 136 KB) links the full 19-module graph;libart.sobuilds with 237 srcs + 30 operator_out + mterp_x86_64.S + asm_defines.h + 5 .S entrypoint objects. Seetools/verify/runtime/RESULT.md.- The Python codegen driver (operator_out via emitter custom-commands; mterp + asm_defines via the driver at configure time) all works in the real build.
One authorized source change to the read-only archive made this possible:
art_method-inl.h FillVRegs terminal overload lost its value parameters to
resolve a clang≥17 variadic-overload ambiguity (behavior-preserving; recorded in
archive-patches/README.md). This is the source rot the submodule-update goal
will eliminate upstream.
Final converter mechanisms added: recursive whole_static_libs absorption,
:name source skipping, shell-quoted defines, absorbed-source cflags, ldflags +
global ldflag drops, executable kind, absorb_whole_static=False option, and
libdexfile absorbing dex_file_supp.cc. Build needs ASM language enabled and
-DCMAKE_BUILD_TYPE=Release (Debug expects d-suffixed libs).
The original goal — "something converting Android.bp to CMakeLists.txt so we
don't hand-write CMake" — is achieved for the whole dalvikvm native graph,
with a clean single-entry build:
native/generate.sh # Android.bp -> generated/dalvikvm.cmake
cmake -S native -B build/native -G Ninja -DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release
cmake --build build/native # -> dalvikvm + 17 libs
LD_LIBRARY_PATH=build/native build/native/dalvikvm -showversion
# ART version 2.1.0 x86_64- Dependency closure:
bp2cmake --root-module dalvikvmwalks the link graph and emits all 18 buildable targets deps-first — no hand-maintained module list (tools/bp2cmake/bp2cmake/closure.py). - One hand-written CMake file (
native/CMakeLists.txt): only project-owned glue the converter can't derive — host imported libs (z/cap/lz4), the codegen driver invocation, and the clang-21 toolchain-drift shims. Everything else is generated.
Remaining project work is breadth/polish:
- Bump submodules to current AOSP (drops the one
FillVRegspatch and the toolchain-drift shims). - The Java side (
boot.jar, already close in the archive) for a full end-to-endcommand-example.txt-style run.
Validated the converter-built dalvikvm (built RelWithDebInfo) against the
archive's prebuilt boot.jar (javalib/build/merged/output.jar) + ICU data + a
d8-compiled HelloWorld. The VM boots, brings up the CMS GC, falls back to
imageless running, links the core libcore classes, and executes Java bytecode
in the interpreter (nterp) — exercising the entire generated runtime (dex
loading, class linker, GC, interpreter, JNI).
Build type matters: use RelWithDebInfo (-O2), not Release. Release compiles
ART at -O3, which miscompiles it (an earlier -O3 run faked a "LinkMethods
hang"; that diagnosis was wrong). Debug's GC is unusably slow. RelWithDebInfo
(-O2 -DNDEBUG -g) is correct and keeps symbols.
Current blocker to main(): the libcore JNI natives aren't loaded.
libart's runtime_libs: ["libjavacore"] (runtime/Android.bp:533) is a
dlopen-at-runtime dependency (correctly not a link edge), and the
javacorenatives group (libjavacore, libopenjdk, + ICU/crypto/expat JNI)
isn't converted yet. Without it, libcore native methods (e.g.
Class.getNameNative) are unregistered, so the first exception's toString
recurses to a StackOverflow → SIGSEGV. See tools/verify/e2e/RESULT.md.
Next milestone: convert the javacorenatives group (the audit already analyzed
its port decisions) and load it, so Hello.main() runs to completion.
The goal is a minimal ART runtime (the dalvikvm / dalvikvm64 executable
plus its supporting libraries and a boot.jar core library) that runs on a
normal GNU/Linux desktop or server — glibc only for now (musl is explicitly
deferred, see Section 6/7) — no Android device, no Android platform services, no
libandroid, no framework. You hand it a dex
(or a jar of dex) and a main class and it executes Java bytecode.
Concretely, the end product is the kind of invocation already captured in the
archive's command-example.txt: an LD_PRELOAD/LD_LIBRARY_PATH wrapper around
dalvikvm64 -Xbootclasspath:0:boot.jar -cp app.jar <MainClass>, used to run a
plain-Java application (a Telegram bot, in that example) on top of ART instead
of OpenJDK.
The sources are AOSP components vendored as git submodules, pinned to a
2023-era snapshot (see Section 3). We are starting a fresh repository
(dalvikvm-multiplatform / historical dalvikvm-linux fallback) rather than continuing inside the archive.
Read-only reference. AOSP-derived, last touched early 2023. Layout:
MinDalvikVM-Archive/
settings.gradle.kts Gradle root: include(":native", ":javalib")
build.gradle.kts just a clean task
buildSrc/ Kotlin/Java build helpers (see below)
gradle/ gradlew Gradle wrapper (mixed 7.5 / 8.0.1 state)
local.properties points at an Android SDK (sdk.dir=...Android/Sdk)
command-example.txt sample real-world invocation of the built VM
javalib/ the Java side -> produces boot.jar (dex)
build.gradle.kts compiles libcore + icu + okhttp + conscrypt + bouncycastle,
then d8 -> dex -> merged output.jar
android-annotation-stub/ hand-written stubs for @annotations libcore needs
external/ submodules: okhttp, conscrypt, fdlibm, bouncycastle, icu
libcore/ AOSP libcore submodule (the Java core library source)
native/ the C/C++ side -> produces dalvikvm + .so libraries
build.gradle.kts orchestrates python codegen + drives CMake
cmake/ ~40 HAND-WRITTEN .cmake/CMakeLists.txt files (the mess)
art/ AOSP art submodule (runtime, compiler, dex2oat, dalvikvm...)
libbase libnativehelper logging libziparchive libprocinfo
fmtlib unwinding AOSP system libs (submodules)
external/ boringssl, cpu_features, dlmalloc, lzma,
oj-libjdwp, tinyxml2 (submodules)
javalib/build.gradle.kts is a single, fairly linear Gradle script. It:
- pulls Java source from many
srcDirsacrosslibcore/and theexternal/submodules (ojluni, libart, dalvik, luni, xml, json, dom, icu4j, okhttp, conscrypt, bouncycastle) plus local annotation stubs; - compiles with
--system=noneand--patch-module java.base=.../ojluni; - depends on
:native:generateConscryptNativeConstants(one generatedNativeConstants.javacomes out of the native build); - runs
d8from the Android SDK build-tools to dex, then zips a mergedoutput.jar.
This part is comparatively healthy: it is declarative-ish, one file, and the
only real external coupling is (a) the Android SDK d8 tool and (b) one
generated source file from the native side. Main concerns going forward are the
Android SDK dependency and keeping the long srcDirs list in sync after
submodule updates.
Two layers:
-
native/build.gradle.kts— a Gradle script that does code generation and CMake orchestration, not compilation itself. It:- runs AOSP python generators:
generate_operator_out.py(enumoperator<<.ccfiles for a long hand-maintained list of headers),gen_mterp.py(interpreter assembly for arm/arm64/x86/x86_64),make_header.py(asm_defines.hfrom a compiledasm_defines.s); - builds a couple of intermediate CMake targets to generate more source
(
asm_defines_s,conscrypt_generate_constants); - then invokes CMake (Ninja, clang/clang++) via the
CMakeProjecthelper.
- runs AOSP python generators:
-
native/cmake/**— ~40 hand-written CMake files, one per library, e.g.libbase.cmake,runtime.cmake(346 lines),compiler.cmake,dex2oat.cmake,dalvikvm.cmake. Each one re-lists every source file, include dir, compile flag and link dependency by hand, transcribed from the corresponding AOSPAndroid.bp.
This is the pain point. Every one of those source lists, include dirs and flag
sets already exists, authoritatively, in an Android.bp next to the sources.
The hand-written .cmake files are a manual, lossy, drift-prone copy. When a
submodule is updated, someone has to diff the Android.bp by eye and patch the
.cmake. That is what makes updates miserable.
CMakeProject.kt— thin wrapper: runcmake -G Ninja ... -S -Bthencmake --build --target. Reusable.Common.kt— git version code via jgit,findInPath, readlocal.properties.AbiUtils.java— host arch -> Android ABI name mapping.
The archive uses git submodules pinned to ~2023:
artHEAD:f76ca8c3cb, 2023-04-06libcoreHEAD:399a1e7563, 2023-02-23 (udc-preview2 era)- plus
libbase,libnativehelper,logging,libprocinfo,libziparchive,fmtlib, andexternal/{boringssl,cpu_features,dlmalloc,lzma,oj-libjdwp, tinyxml2}, and the javaexternal/{okhttp,conscrypt,fdlibm,bouncycastle}.
git status in the archive shows many submodules dirty / modified, and the
gradle wrapper itself is in a half-migrated state. So the archive is not a clean
baseline — it is a working tree someone left mid-change.
All submodule URLs point at android.googlesource.com/platform/.... Updating
means moving these pins forward to a coherent newer AOSP snapshot, which will
change source lists, flags, and dependencies — exactly the churn the
hand-written CMake can't absorb cheaply.
Android builds these components with Soong, which reads
Android.bp(Blueprint) files. We are not on Android and don't want Soong. Today we bridge that gap by hand-writing CMake. That hand-translation is the mess and the maintenance tax.
Scale of the Android.bp surface (counts across the subset we actually use —
libbase, libnativehelper, art, logging, libziparchive, libprocinfo,
fmtlib):
- 810
Android.bpfiles total undernative/(most are tests/tools we ignore). - The module types we care about are mainly:
cc_library,cc_library_shared,cc_library_static,cc_library_headers,cc_binary,cc_defaults,cc_object,filegroup,genrule/gensrcs, and ART's customart_cc_library/art_cc_binary/art_cc_defaultsfamily. - Important:
art_cc_*are not built-in Soong types. They are defined in Go (native/art/build/art.go,codegen.go,makevars.go) and viasoong_config_module_type_importfromart/build/SoongConfig.bp. A converter must treat these as "cc_* plus extra ART defaults/codegen flags", not parse the Go. We hardcode/approximate their behavior. - Features that appear and must be handled:
defaultsinheritance,srcs/exclude_srcs,shared_libs/static_libs/header_libs,include_dirs/export_include_dirs,cflags/cppflags,generated_sources/generated_headers, andarch: {}/target: {}conditioning (we only need the linux + host + arm/arm64/x86/x86_64 branches). soong_configvariables appear (~34 references) but for our minimal Linux target most can be resolved to fixed values.
We audited the ~40 hand-written .cmake files against their source Android.bp
files, module by module. Critical finding: the existing CMake is not a
mechanical translation of the .bp. It is a translation plus a thick layer of
deliberate Android-to-Linux engineering decisions, plus a number of outright
bugs. This reshapes the converter design (Section 6/7) and is the single most
important thing to internalize before writing any code.
Two consequences up front:
- A naive "parse
.bp-> emit CMake" converter produces a wrong build. It would faithfully reproduce Android behavior we explicitly do not want, and it cannot invent the fakes/substitutions the port depends on. - The old
.cmakefiles are a quarry to mine the port decisions from, not a golden reference to reproduce — they contain real defects (listed in 5.8).
The .bp describe how to build ART for an Android device on bionic, inside
the APEX/Soong world. Much of what we must change is not even visible in the
.bp text:
- ART's build flags are injected by Go, not declared in
.bp.ART_TARGET,ART_TARGET_ANDROID/ART_TARGET_LINUX, the default GC type,ART_FRAME_SIZE_LIMIT,ART_STACK_OVERFLOW_GAP_*, the per-archART_ENABLE_CODEGEN_*, and the whole base cflag list come fromnative/art/build/art.go/codegen.goload hooks. They appear nowhere in the module.bp. The hand-written CMake reconstructs them by hand innative/cmake/art/CMakeLists.txt(ART_COMPILE_C_FLAGS) andruntime.cmake. A.bp-only parser is blind to all of it. - AOSP
art.godefines-DART_TARGET/-DART_TARGET_ANDROIDvs_LINUXbased on anART_TARGET_LINUXenv var, and ONLY for the android target. A real host build defines neither. The port instead force-definesART_TARGET+ART_TARGET_LINUXon the runtime — compiling ART in a "Linux-flavored target" mode that is its own third thing, not host and not device.
- Linkage swaps (static<->shared). Almost every
cc_library/cc_library_staticis forced toadd_library(... SHARED), with a few forced STATIC (elffile,cpu_features,fdlibm,icuuc_stubdata,androidicuinit). Concrete inversions:libicu_static-> sharedicu; sharedlibicuuc_stubdata-> STATIC; AOSP'swhole_static_libs(fmtlib into libbase, cpu_features/tinyxml2 into their consumers) become either inlined sources or plain shared links. This is a global policy, tied to the next item. - Global "self-contained .so" link policy. Top-level CMake forces
-Wl,--no-undefined,--no-allow-shlib-undefined -static-libgcc -static-libstdc++on every target. That is why every dropped source or dep matters: there is no lazy Android runtime symbol resolution to paper over gaps. - dlopen/linker-namespace model replaced by direct linking.
libart-disassemblerflips fromruntime_libs(Android lazy dlopen) to a hard link in the compiler.libnativeloaderdrops its entire namespace engine (library_namespaces.cpp,native_loader_namespace.cpp,public_libraries.cpp,libdl_android,libPlatformProperties) down to baredlopenonnative_loader.cpp.libnativebridgestripslibdl_android+dlext_namespaces.h. - Android platform services replaced by fakes / Linux source variants.
libartpalettebuilds ONLYsystem/palette_fake.cc(no-op thread priority/ashmem/tracing) instead of the Androiddlopen(libartpalette-system)loader. ART picksos_linux.cc,monitor_linux.cc,runtime_linux.cc,thread_linux.cc,unix_file/*and DROPS the*_android.cctrio +metrics/statsd.cc. liblog is reduced to a minimal write-only stub (logd/pmsg transport sources dropped). - libc identity forced; musl path collapsed.
libjavacore/libopenjdkhand-define__GLIBC__,_GNU_SOURCE,_LARGEFILE64_SOURCE,LINUX(literally commented# Sigh.);cpu_featuresforce-defines-DHAVE_STRONG_GETAUXVAL. The.bpkeep a realtarget.muslbranch; the CMake collapses to glibc-only. We are keeping glibc-only by choice (musl deferred, Section 6/7), so this collapse is acceptable for now — but it is a decision to record in Layer 2, not an accident, so musl can be restored later without rediscovering why__GLIBC__was forced. - Dependency rewiring / renaming. AOSP names map to short targets
(
libbase->base,liblog->log,libz->z= host system zlib,liblzma->lzma); Android-only deps are dropped fromart(libdl_android,libstatssocket,heapprofd_client_api,libstatslog_art,libodrstatslog); boringssllibcryptois built SHARED + non-FIPS (the entirebcm_object/FIPS hashing machinery dropped), which then forces conscrypt'slibjavacryptoto link crypto shared (AOSP links it static on host). - Behavioral pins. GC default swapped from CMC (mark-compact, needs
userfaultfd) back to CMS (
ART_DEFAULT_GC_TYPE_IS_CMS) inruntime.cmake. - Metadata dropped wholesale. All
version_script:/stubs:(APEX symbol ABI),apex_available,min_sdk_version,vndk,sanitize,tidyblocks are dropped.-Werrorand-Wthread-safetyare removed globally (host clang+glibc warns where the Android toolchain does not), with assorted-Wno-*added. - Module merges with no
.bpequivalent.libandroidiois collapsed intolibjavacore(its.map.txtsymbol-versioned stub has no CMake analogue);libdexfileabsorbsdex_file_supp.ccfrom AOSP's separatelibdexfile_support; the standalonedexlayoutbinary andlibbacktraceare simply not built. - Generated code pre-baked.
:libart_mterp.*ng,operator_out,asm_definesgenrules are replaced by pre-generated files staged underbuild/gensrc/and consumed directly.
native/jdwpheader/jdwpTransport.h is an OpenJDK header (not from any
.bp) hand-placed to satisfy ART's JVMTI/JDWP debug transport ABI. A converter
could never derive this from the .bp; it must be carried as project-owned glue.
The audit surfaced real defects in the hand-written files, which is further proof they cannot be a golden reference:
libbasedropserrors_unix.cppentirely (picked no OStargetbranch), so the host build is missingSystemErrorCodeToString.libsslaccidentally compiles thebsslcommand-line tool sources (src/tool/*.cc) into the shared SSL library.- The FIPS
fips_shared.ldsversion script +-Bsymbolicare applied to a non-FIPS crypto build (wrong context). disassemblercompiles BOTHdisassembler_arm64.ccanddisassembler_arm.ccfor arm64; never linkslibvixl.asm_definespasses-UDEBUGwhere AOSP undefines-UNDEBUG.libtinyxml2force-enables Android logcat macros (-DDEBUG -DANDROID_NDK) on Linux while NOT linkingliblog.liblzmasetsCXX_STANDARD 20on a pure-C target;libbaseapplies-Werror-class flags only to C TUs though it is all C++.libunwindstackshipsDexFile.cppbut never definesDEXFILE_SUPPORT, so dex-frame symbolization is compiled in its disabled form.- Duplicate entries:
dex_file_verifier.cc(libdexfile), ziparchive include (libartbase). - ICU
.datdata file is never wired in by any CMake; the build leans on an empty stubdata + externalICU_DATA.
The converter cannot be a single pass. It must be three layers, separating "what AOSP says" from "what we decided" from "how CMake spells it":
- Layer 1 — Blueprint evaluator. Parse
.bp; resolvedefaults, variables,+concatenation; evaluatearch{}/target{}selects against a FIXED config (host,linux_glibc, chosen arch —linux_muslis a future config value, not wired now). Include a hardcoded model ofart.go/codegen.gosoart_cc_*modules receive their real injected flags. Output: a normalized, config-resolved module graph. - Layer 2 — port-policy overlay (WE own this file; it is the valuable part).
The small, relatively stable set of deliberate decisions from 5.2: per-module
kind overrides, source substitutions (
palette_fake.cc,os_linux.cc, drop*_android.cc), dependency rewrites (droplibdl_android, maplibz->host zlib, drop FIPS), define overrides (CMS GC, force__GLIBC__, forceART_TARGET_LINUX), flag policy (drop-Werror), and "don't build this" exclusions. Mined from the existing.cmake(minus its bugs). - Layer 3 — CMake emission. Deterministic, dumb. Graph + overlay -> CMake.
This split is exactly what makes updates cheap (the project's core goal): a submodule bump re-runs Layer 1, refreshing the tedious, drift-prone source lists and dependency edges automatically, while Layer 2 — the human judgment — rarely changes. The hand-written CMake conflated all three layers into one, which is precisely why it rots on every update.
The dalvikvm-multiplatform project (historically started as dalvikvm-linux) where:
- Native build is generated, not hand-written. A tool reads the AOSP
Android.bpfiles and emitsCMakeLists.txt. When we bump a submodule, we re-run the generator instead of diffing source lists by hand. - Updates are cheap. Submodule bump -> regenerate -> build. Drift between the AOSP source and our build description is structurally impossible because the build description is derived.
- Linux-first, no Android platform API. Target glibc only for now (musl
deferred — see Section 7), host clang,
arm/arm64/x86/x86_64. Resolve Soong's Android-only branches away. - The Java side keeps working (it is already close), fed by the one generated source the native side produces.
- Reproducible end artifact:
dalvikvm+ the needed.sos +boot.jar, runnable likecommand-example.txt.
The centerpiece, structured as the three layers established in Section 5.5 (Blueprint evaluator -> port-policy overlay -> CMake emission). Decisions to lock in next phase, current leaning:
- Language: Python. Rationale: the existing codegen the build already shells
out to is Python (
generate_operator_out.py,gen_mterp.py,make_header.py);python3is already a required tool; Blueprint is close to JSON and easy to parse in Python; no JVM/Gradle needed just to generate a build. (Kotlin is the alternative — it matchesbuildSrcand is type-safe, but drags Gradle into the codegen step. We will confirm the choice in step 2.) - Reuse Soong's own parser if practical. Blueprint's grammar is small but
real (
defaults, variables,+concat,arch/targetselects). AOSP ships a Go Blueprint parser; there are also third-party Python.bpparsers. To confirm in step 2 whether to reuse one vs. write a small one. - Layer 1 must model
art.go. Because ART's real flags live in Go, not.bp(Section 5.1), the evaluator needs a hardcoded table reproducing theart_cc_*defaults andART_ENABLE_CODEGEN_*logic. This is unavoidable and is the part most coupled to the AOSP snapshot. - Layer 2 is a checked-in, human-authored policy file (format TBD — likely declarative: per-module overrides keyed by module name). It is the durable asset; treat it as code, with comments citing why (Android vs Linux) for each override, since the audit showed those reasons are easy to lose.
- What Layer 3 emits: per-module CMake targets (
add_library/add_executable,target_sources,target_include_directories,target_compile_options,target_link_libraries) wired by module name — structurally like today's files, minus the bugs. - Allowlist, not whole-tree: only convert modules reachable from the
dalvikvmtarget (and the boot-jar codegen). Not all 810 blueprints. - Validation strategy: diff generated output against the existing
hand-written
.cmakefor each module, using the audit notes to distinguish "intended port decision" (must match, encode in Layer 2) from "old bug" (generated output should differ and be correct).
- Blueprint is not trivial — and the
.bpis not even the whole truth.defaultsinheritance,arch/targetselects,soong_config, the Go-definedart_cc_*types, AND theart.goflag injection (Section 5.1) mean a naive parser is not just incomplete but actively misleading. Mitigation: the three-layer design (Section 5.5), scope to the reachable module set, hardcode the ART/art.goexpansion, resolve config to fixed Linux values. - The port-policy overlay (Layer 2) is the hard, valuable part. It captures the Section 5.2 decisions and is mined from the buggy hand-written CMake. Risk: getting it wrong silently reintroduces Android behavior or an old bug. It needs per-entry rationale and per-module validation against the audit notes.
- musl is explicitly deferred (decided). glibc is the only step-one target.
The hand-written CMake force-defines
__GLIBC__and collapses Soong'starget.muslbranch (Section 5.2 item 5); we keep that collapse for now, but record it as a deliberate Layer 2 entry rather than leaving it implicit. Real musl support later means restoring that branch in Layer 1 and parameterizing the libc in Layer 2 — a future milestone, not a freebie, and not blocking anything now. - Generated sources are load-bearing.
operator_out, mterp asm,asm_defines.h,conscrypt_generate_constantsmust run in the right order. The converter must emit these as CMake custom commands / targets, or we keep driving them from a thin outer script. Decide which. - Non-AOSP glue must be carried.
native/jdwpheader/jdwpTransport.h(Section 5.3) and any future shim are project-owned; the converter must know they exist and where they plug in. - Submodule bump = source churn — but that is the point. Newer AOSP may add files, flags, deps, or new Android-only branches the converter doesn't model. The win is that breakage shows up at regeneration/compile time in a known place (Layer 1 output or build error), not as silent drift. New Android-only behavior may require new Layer 2 overrides.
- The existing
.cmakehas bugs (Section 5.4). Validation must not blindly match it; the audit notes are the arbiter of correct-vs-bug. - Android SDK dependency (
d8). The Java side needsd8. Acceptable for now; note it as an external prerequisite. - No build attempted yet. Everything above is from reading and comparing the archive, not from a successful build of either tree. First real build is a future step.
This document is step one and the only deliverable for now. The proposed sequence after sign-off:
- Lock the converter decision — Python vs Kotlin; reuse a Blueprint parser vs write one; confirm the three-layer structure. (Section 6.1.)
- Initialize the new repo —
git init, decide submodule set + URLs, pin to a chosen AOSP snapshot (start from the archive's pins, plan an update path). Port the reusablebuildSrchelpers (CMakeProject,AbiUtils) if we keep any Gradle orchestration. Carry overnative/jdwpheader/. - Seed the port-policy overlay from the audit. Capture the Section 5.2 decisions and Section 5.4 bug-fixes as the first Layer 2 entries, each with a why. This is the durable artifact and should exist before broad conversion.
- Prototype the converter on one leaf module —
libbaseis ideal: small, self-contained, and we have a known-good (but buggy — missingerrors_unix.cpp) hand-writtenlibbase.cmaketo diff against. Success = generated CMake buildslibbase, matches the intended result, and FIXES theerrors_unix.cppbug via Layer 1'starget.linuxresolution. - Expand outward along the dependency graph — liblog, libnativehelper,
fmtlib, libziparchive, ... up to
runtimeand finally thedalvikvmexecutable, validating each against the archive's.cmakeand the audit notes (intended decision vs old bug). - Re-wire codegen + the Java side, then attempt a full
dalvikvm+ boot.jar build and run thecommand-example.txt-style smoke test.
- Sample real invocation:
../MinDalvikVM-Archive/command-example.txt - Native orchestration:
../MinDalvikVM-Archive/native/build.gradle.kts - Hand-written CMake (the mess, also the policy quarry):
native/cmake/** - ART flag injection (NOT in
.bp):native/art/build/{art,codegen,makevars}.go - A clean small example to mirror (and a bug to fix):
native/cmake/libbase.cmake<->native/libbase/Android.bp(note the missingerrors_unix.cpp) - The platform-fake pattern:
native/cmake/art/libartpalette.cmake<->native/art/libartpalette/Android.bp(palette_fake.cc) - Final exe:
native/cmake/art/dalvikvm.cmake<->native/art/dalvikvm/ - Project-owned non-AOSP glue:
native/jdwpheader/jdwpTransport.h - Java build:
../MinDalvikVM-Archive/javalib/build.gradle.kts - Reusable helpers:
../MinDalvikVM-Archive/buildSrc/src/main/ - Submodule list + URLs:
../MinDalvikVM-Archive/.gitmodules