Skip to content

Support apktool 2.12+/3.x builds while keeping aapt2 on older apktool - #24

Merged
tkmru merged 2 commits into
mainfrom
fix/apktool-3
Sep 20, 2026
Merged

tkmru merged 2 commits into
mainfrom
fix/apktool-3

Conversation

@tkmru

@tkmru tkmru commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

apkutil build / network / debuggable / all fail on the current apktool (Homebrew 3.0.3) with 'ascii' codec can't decode byte 0xc5 in position 128. This PR fixes the three underlying issues in apkutil/util.py without changing any other subcommand, the ~/apkutil.json schema, or the supported Python versions (verified on 3.8.5 and 3.14.7).

Bug A: --use-aapt2 is rejected by apktool >= 2.12.0

apktool removed --use-aapt2 in 2.12.0 (aapt2 has been its default since 2.9.0). Passing it to 2.12+/3.x fails with Unrecognized option: --use-aapt2 and nothing is built.

Chosen approach: option 1 (runtime detection). build() now runs apktool --version, parses the version from the start of a line with an optional Apktool / v prefix (tolerating suffixes like 2.12.0-dirty / 2.11.1-SNAPSHOT), and appends --use-aapt2 only when the version is < 2.12.0.

Note on 2.12.x: it is the one series that rejects apktool --version (Unrecognized command) and prints its banner (Apktool 2.12.0 - a tool for ... / with smali 3.0.9 ...) instead. The parser is anchored to line start so it reads 2.12.0 from the banner and never the bundled smali version.

Why option 1 over option 2 (drop the flag + document apktool >= 2.9.0):

  • It keeps the existing guarantee that apkutil builds with aapt2 on every apktool version. Simply dropping the flag would silently switch apktool <= 2.8 users to aapt1 builds, which can change the produced APK without any visible error.
  • No new minimum requirement for users; nothing to document or warn about.
  • Cost is one extra apktool --version call per build. If the version can't be parsed the flag is omitted, which is the safe choice for every apktool >= 2.9. apktool missing still hits the existing FileNotFoundError -> "apktool not found." path.

Bug B: _run_subprocess() decoded as ASCII

apktool's usage text contains Wiśniewski, so the real error was swallowed by UnicodeDecodeError. Output is now decoded as UTF-8 with errors='replace', which also covers non-ASCII APK paths and localized aapt2/apksigner messages.

Bug C: success detection matched only I: Built apk...

apktool >= 2.7.0 prints I: Built apk into: <path>, so is_built was never true and any stderr noise on a successful build raised. The check is now "I: Built apk" in outs, matching both forms.

Version

Bumped setup.py to 0.1.11: 0.1.10 is already on main (installed via git+ssh), and this changes user-visible build behavior, so a new version helps pipx/uv tool upgrade users see the update.

Tests

tests/test_cli.py (same mock-driven style, no apktool/SDK/keystore/APK needed) now also locks in:

  • --use-aapt2 is passed for apktool 2.4.1 / 2.8.1 / 2.9.3 / 2.11.0 / 2.11.1-SNAPSHOT and not for 2.12.0 / 2.12.0-dirty / 2.12.1 / 3.0.3 / 3.1.0-SNAPSHOT, the 2.12.x banner output, or an unparseable version
  • _parse_apktool_version() edge cases, including the 2.12.x / 2.8.x banner formats and not matching with smali 3.0.9 ...
  • build() returns True for both I: Built apk... and I: Built apk into: /path/x.apk, including when apktool also writes a warning to stderr; a real failure still raises with the stderr text
  • _run_subprocess() returns str for UTF-8 and for invalid byte sequences instead of raising
$ python3.8 -m unittest discover -s tests   # Ran 19 tests ... OK
$ python3.14 -m unittest discover -s tests  # Ran 19 tests ... OK

Against the unpatched util.py the new tests fail (7 failures, 10 errors).

Manual verification

Sample APK: a minimal manifest + resources.arsc APK built with aapt2 link; ANDROID_HOME=~/Library/Android/sdk, build-tools 36.0.0, OpenJDK 25.0.4. Non-Homebrew versions were run via java -jar apktool_<ver>.jar wrappers on PATH from GitHub Releases.

util.build() on a directory decoded by apktool 3.0.3:

apktool apktool command apkutil ran result
2.8.1 (aapt1 default) apktool b <dir> -o <apk> --use-aapt2 I: Built apk into: OK
2.9.3 apktool b <dir> -o <apk> --use-aapt2 OK
2.11.0 apktool b <dir> -o <apk> --use-aapt2 OK
2.12.0 (no --use-aapt2, no --version) apktool b <dir> -o <apk> OK
3.0.3 (Homebrew) apktool b <dir> -o <apk> OK

Full CLI flows:

apktool decode build network debuggable all
3.0.3 OK OK OK OK OK
2.11.0 OK OK OK OK –

All outputs pass apksigner verify (v1/v2/v3). (2.8.1 could not decode the aapt2-36-built sample itself and needed its own framework dir; both are apktool-version issues in the lab setup, not apkutil.)

Not addressed here (pre-existing, out of scope)

On JDK 25, apksigner prints WARNING: A restricted method in java.lang.System has been called ... to stderr. The APK is signed correctly (Signed, verify passes), but the existing sign() treats any stderr as failure, so the run ends with Failed instead of Output: .... This reproduces identically on main and is unrelated to apktool 3. Possible follow-up: pass -J-enable-native-access=ALL-UNNAMED to apksigner (verified to silence the warning with build-tools 36.0.0), or ignore WARNING: lines in sign().

🤖 Generated with Claude Code

tkmru and others added 2 commits September 21, 2026 06:19
`apkutil build` / `network` / `debuggable` / `all` failed with the current
apktool (3.0.3) because `--use-aapt2` was removed in apktool 2.12.0. Detect
the apktool version at build time and pass the flag only to versions that
still accept it (< 2.12.0), so apktool <= 2.8 (aapt1 by default) keeps
building with aapt2.

Also decode subprocess output as UTF-8 with replacement instead of ASCII,
which raised UnicodeDecodeError on apktool's usage text and hid the real
error, and recognize the `I: Built apk into: <path>` success message used
since apktool 2.7.0 so stderr warnings on a successful build are not
treated as failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
apktool 2.12.x does not accept `--version` and prints its banner
("Apktool 2.12.0 - a tool for ...", "with smali 3.0.9 ...") instead of a
bare version. Anchor the version regex to the start of a line with an
optional "Apktool " prefix so the bundled smali version is never mistaken
for apktool's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tkmru
tkmru merged commit 8e8b6af into main Sep 20, 2026
3 checks passed
@tkmru
tkmru deleted the fix/apktool-3 branch September 20, 2026 21:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant