Source of the bionic-static libaapt2.so binaries that ship inside the
apktool-android AAR. Binaries are produced from a vendored copy of
ReVanced/aapt2's CMake recipe, with
iBotPeaches/Tumbleson PR #3130 layered on top so the result handles the
modern <item type="layout" format="..."> resource patterns that
ReVanced's v1.1.0 prebuilts reject.
aapt2-build/
├── README.md — this file
├── SELF_BUILD_PLAN.md — full execution plan + recipe gotchas
├── patches/ — Apktool leniency patches (see patches/README.md)
│ ├── 0001-aapt2-port-changes-for-apktool.patch
│ └── 0002-aapt2-unrestrict-item-tag-value-format.patch
└── recipe/ — vendored ReVanced/aapt2 build recipe
├── CMakeLists.txt
├── build.sh — host-aware, lightly patched
├── patch.sh — host-aware, switched to patch -p1
├── cmake/
├── misc/
├── patches/ — recipe-local patch set; sees both
│ │ ReVanced's patches AND our 0002
│ ├── apktool_ibotpeaches.patch — ReVanced (kept verbatim)
│ ├── protobuf.patch — ReVanced (build adaptation)
│ ├── 32bsystem_on_armv8.patch — ReVanced (armv7 BusError)
│ └── 0002-aapt2-unrestrict-item-tag-value-format.patch
└── README.md — upstream ReVanced README + our delta
recipe/submodules/ (the AOSP source roots) is intentionally NOT
checked in; developers symlink it from a scratch clone of
ReVanced/aapt2. recipe/build/ and recipe/out/ are also gitignored.
The end state is the three files Gradle picks up:
brut.apktool/apktool-android/src/main/jniLibs/arm64-v8a/libaapt2.so
brut.apktool/apktool-android/src/main/jniLibs/armeabi-v7a/libaapt2.so
brut.apktool/apktool-android/src/main/jniLibs/x86_64/libaapt2.so
Each .so MUST be statically linked (no DT_NEEDED entries). Verify
with the NDK's llvm-readelf:
"$ANDROID_NDK/toolchains/llvm/prebuilt/$NDK_HOST/bin/llvm-readelf" \
-d brut.apktool/apktool-android/src/main/jniLibs/arm64-v8a/libaapt2.so \
| grep NEEDED
# (no output expected — fully static)
file brut.apktool/apktool-android/src/main/jniLibs/arm64-v8a/libaapt2.so
# ELF 64-bit LSB executable, ARM aarch64, ..., statically linked, strippedIf NEEDED entries do appear (e.g. libc++_shared.so,
libprotobuf-lite.so, libpng16.so), the binary will fail to load
from nativeLibraryDir on stock Android. The recipe's CMakeLists.txt
sets CMAKE_EXE_LINKER_FLAGS "-static" and PNG_SHARED=OFF; if you
end up with dynamic deps, that flag isn't reaching every dep.
Five steps, ~5 minutes once the deps are installed.
The 17 AOSP submodules (~1.2 GB shallow) live OUTSIDE this repo to keep git history clean. Clone them once into a scratch dir:
git clone --recurse-submodules --shallow-submodules --depth 1 \
https://github.com/ReVanced/aapt2 ~/work/revanced-aapt2ln -sfn ~/work/revanced-aapt2/submodules \
aapt2-build/recipe/submodules(The symlink is gitignored.)
The recipe pins this exact protoc version; newer ones generate
incompatible .pb.h headers.
# macOS
brew install protobuf@21
brew link --force protobuf@21
protoc --version # expected: libprotoc 3.21.12
# Linux
wget https://github.com/protocolbuffers/protobuf/releases/download/v21.12/protoc-21.12-linux-x86_64.zip
sudo unzip -p protoc-*.zip bin/protoc -d /usr/local/bin/
protoc --version # expected: libprotoc 3.21.12cd aapt2-build/recipe
./patch.shpatch.sh mutates files in the symlinked AOSP tree. To re-run:
cd ~/work/revanced-aapt2
git submodule foreach --recursive 'git checkout -- . && git clean -fd'
git checkout -- .
git clean -fd
cd -
cd aapt2-build/recipe
./patch.shcd aapt2-build/recipe
export ANDROID_NDK="$HOME/Library/Android/sdk/ndk/27.1.12297006" # adjust
mkdir -p out
for abi in arm64-v8a armeabi-v7a x86_64; do
rm -rf build # CMake refuses to swap toolchain mid-config
./build.sh $abi
cp build/bin/aapt2-$abi out/
done
# Install over the previously committed binaries.
cd ../..
for abi in arm64-v8a armeabi-v7a x86_64; do
cp aapt2-build/recipe/out/aapt2-$abi \
brut.apktool/apktool-android/src/main/jniLibs/$abi/libaapt2.so
chmod 0755 brut.apktool/apktool-android/src/main/jniLibs/$abi/libaapt2.so
done./gradlew :brut.apktool:apktool-android-test:uninstallDebugAndroidTest \
:brut.apktool:apktool-android-test:connectedDebugAndroidTest \
--stacktrace
# Expected: 3 of 3 tests pass, including featureFlagsBuild against modern.apk.Applied by recipe/patch.sh in this exact order against
recipe/submodules/base/... (i.e. AOSP frameworks/base):
| # | Patch | Source | Purpose |
|---|---|---|---|
| 1 | apktool_ibotpeaches.patch |
ReVanced (vendored) | Apktool leniency baseline + AOSP runtime fixes (MCC/MNC parser, stringToFloat, etc.). Already covers everything in our patches/0001-...patch. |
| 2 | protobuf.patch |
ReVanced | Build adaptation. |
| 3 | 32bsystem_on_armv8.patch |
ReVanced | armv7 BusError fix. |
| 4 | 0002-aapt2-unrestrict-item-tag-value-format.patch |
iBotPeaches PR #3130 (apktool-3.0.x), re-rooted to submodules/base/... paths and with android::DiagMessage qualifier preserved |
Fixes error: invalid value for type 'layout'. Expected a reference. against modern Play-Store APKs. The reason we're here. |
patches/0001-aapt2-port-changes-for-apktool.patch is kept in-repo as
a documentary record of what iBotPeaches has on his apktool-3.0.x
branch; it is NOT applied during the build, because ReVanced's patch
(1) already includes everything in it (and more).
ReVanced's build scripts (under recipe/) are GPL-3.0; ReVanced's
recipe/LICENSE file is preserved verbatim. The OUTPUT binary is
derived from:
- AOSP
frameworks/basesource (Apache-2.0) - iBotPeaches/Tumbleson patches (Apache-2.0)
- Statically-linked deps: AOSP
libpng(libpng licence),expat(MIT),protobuf-lite(BSD-3-Clause),fmtlib(MIT),boringssl(OpenSSL-style),pcre(BSD),jsoncpp(MIT/Public).
No GPL-3.0 code is statically linked into the output binary. The binary itself stays under the AOSP/Apktool licence stack (Apache-2.0), matching the licence Apktool uses for the desktop aapt2 it bundles.
The AAR (apktool-android.aar) only contains the .so files — Gradle
never sees the GPL-3.0 build scripts under recipe/. Hosts consuming
the AAR are unaffected by the GPL-3.0 of the build recipe.
- The AOSP submodule tree (~1.2 GB shallow, ~20 GB if you go deep) is too unwieldy to vendor inside the repo; Gradle would also try to download it on first sync.
- Building
aapt2from source takes ~1 minute per ABI (~3 min total); running on every./gradlew assemblewould be unacceptable. - The artifacts only need refreshing when the AOSP pin or a leniency patch changes (yearly cadence at most).
So the contract is: humans run the steps above when those events
happen, commit the .so files into
apktool-android/src/main/jniLibs/, and Gradle just packages them
into the AAR.
SELF_BUILD_PLAN.md— phase-by-phase execution plan (what shipped, what's optional, the full risk register, and the recipe gotchas worth knowing if you ever need to refresh the binaries)recipe/README.md— upstream ReVanced README + an Apktool-Android delta headerpatches/README.md— patch curation rules + how to re-export from iBotPeaches'sapktool-3.0.xbranch