Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0279d6afb | ||
|
|
02a8a155c3 | ||
|
|
0b8287eb13 |
@@ -195,6 +195,15 @@ jobs:
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
run: ./gradlew assembleDebug
|
||||
|
||||
# Debug builds aren't minified, so a keep rule that drops a reflectively
|
||||
# created class's constructor only breaks release builds (#1, #75). The
|
||||
# releaseTest twin is R8-shrunk with the release rules and debug-signed.
|
||||
- name: R8 keeps reflective constructors
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
run: |
|
||||
./gradlew assembleFullReleaseTest
|
||||
python3 scripts/check_r8_keeps.py
|
||||
|
||||
- name: Trivy filesystem scan
|
||||
if: steps.scope.outputs.code == 'true'
|
||||
run: |
|
||||
|
||||
@@ -55,6 +55,8 @@ All notable changes to this project are documented here. The format follows
|
||||
whose dates put a word after the year (#70).
|
||||
- Widgets no longer turn into the Today widget or show "Can't load content"
|
||||
after a task is ticked off (#92).
|
||||
- Ticking off a task in the Tasks and Up next widgets now works in release
|
||||
builds; until now the tap did nothing (#75).
|
||||
- The Tasks widget leaves the same gap under its header as its preview (#86).
|
||||
- The Starts and Due labels in the task form no longer get cut off at large
|
||||
font sizes (#76).
|
||||
|
||||
Vendored
+2
-7
@@ -7,8 +7,8 @@
|
||||
# nothing calls it directly — Room then throws InstantiationException, reported
|
||||
# as "Failed to create an instance of ...". This first bit us through a
|
||||
# transitive Room (Glance -> WorkManager -> WorkDatabase, built at startup:
|
||||
# issue #1); Glance is gone and Room is now our own task store, so the rule
|
||||
# matters more, not less — TasksDatabase is built on the first store read.
|
||||
# issue #1); Room is now also our own task store, so the rule matters more,
|
||||
# not less — TasksDatabase is built on the first store read.
|
||||
-keep class * extends androidx.room.RoomDatabase { <init>(); }
|
||||
|
||||
# Compose Compiler may keep its own; defaults are fine
|
||||
@@ -20,8 +20,3 @@
|
||||
-dontwarn javax.naming.**
|
||||
-dontwarn lombok.Generated
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
|
||||
# Glance maps each widget to its receivers by the provider's class name. R8's
|
||||
# class merging folded the GlanceAppWidget subclasses into one class, so they
|
||||
# all shared a name and redrew each other's slots (#92).
|
||||
-keep class * extends androidx.glance.appwidget.GlanceAppWidget { <init>(); }
|
||||
|
||||
+1
-1
Submodule floret-kit updated: e2b1430ffd...e8d33329c8
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail when R8 keeps one of our classes by name but strips its no-arg constructor.
|
||||
|
||||
A keep rule that names a class but no members keeps the class and lets R8 drop
|
||||
the constructor nothing calls directly. Anything that then instantiates it by
|
||||
reflection (Room, Glance's ActionCallback, WorkManager) fails at runtime only,
|
||||
in release builds only (#1, #75). R8 reports both halves: seeds.txt lists what
|
||||
keep rules pinned, usage.txt what it removed.
|
||||
|
||||
scripts/check_r8_keeps.py [mapping dir] default: fullReleaseTest's
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
DEFAULT_DIR = ROOT / "app/build/outputs/mapping/fullReleaseTest"
|
||||
PACKAGES = ("de.jeanlucmakiola.",)
|
||||
|
||||
|
||||
def kept_classes(seeds: Path) -> set[str]:
|
||||
return {
|
||||
line.strip()
|
||||
for line in seeds.read_text().splitlines()
|
||||
if line.startswith(PACKAGES) and ":" not in line
|
||||
}
|
||||
|
||||
|
||||
def classes_without_default_ctor(usage: Path) -> set[str]:
|
||||
stripped, current = set(), None
|
||||
for line in usage.read_text().splitlines():
|
||||
if not line.startswith(" "):
|
||||
current = line.rstrip(":") if line.endswith(":") else None
|
||||
elif current and line.strip().endswith(" <init>()"):
|
||||
stripped.add(current)
|
||||
return stripped
|
||||
|
||||
|
||||
def main() -> int:
|
||||
mapping = Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_DIR
|
||||
seeds, usage = mapping / "seeds.txt", mapping / "usage.txt"
|
||||
if not seeds.exists() or not usage.exists():
|
||||
print(f"ERROR: no seeds.txt/usage.txt in {mapping}; build a minified variant first.", file=sys.stderr)
|
||||
return 1
|
||||
broken = sorted(kept_classes(seeds) & classes_without_default_ctor(usage))
|
||||
for name in broken:
|
||||
print(f"ERROR: {name} is kept by a rule but R8 removed its no-arg constructor.", file=sys.stderr)
|
||||
if broken:
|
||||
print("Keep the constructor too: -keep class ... { <init>(); }", file=sys.stderr)
|
||||
return 1
|
||||
print(f"OK: every kept class under {', '.join(PACKAGES)} still has its no-arg constructor.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user