build: move the app to AGP 8 / Gradle 8 and AndroidX#8
Merged
Conversation
The .gitignore `/gradle` entry (a typo for `/.gradle`, already listed on the line above) matched the whole gradle/ directory, so gradle-wrapper.jar was never committed and ./gradlew could not run on a clean checkout at all — only gradle-wrapper.properties had been force-added. Narrow the ignore and commit the jar, generated by the 8.7 distribution after checking it against the published sha256. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
Replaces the 2017 build: jcenter() (dead) gives way to google()/mavenCentral() in settings.gradle, AGP 2.3.2 to 8.6.0 via the plugins DSL, and compileSdk 11 to 35. AGP 8.6 is the floor for compileSdk 35, and it pairs with Gradle 8.7. minSdk is 24, matching the API 24 the native layer already requires for fseeko/nl_langinfo. targetSdk stays at 28 so the File-based crawl keeps legacy storage; moving it to 35 belongs with the scoped-storage rework. support-v4 is gone in favour of androidx.core/androidx.fragment. The FileProvider meta-data key stays `android.support.FILE_PROVIDER_PATHS`: androidx.core.content.FileProvider still reads that exact name, so renaming it would break sharing at runtime. AndroidX drags in kotlin-stdlib 1.8.22 while coroutines pins the jdk7/jdk8 shims at 1.6.21, whose classes 1.8.22 absorbed; the Kotlin BOM aligns them and settles the duplicate-class clash. Three things only failed once the toolchain was modern: - ButtonStyle set its parent to @android:attr/buttonStyle. An attr is not a style, so aapt1 quietly resolved it to nothing; aapt2 rejects the file. Buttons already inherit the theme's buttonStyle as their default style, so dropping the parent keeps the appearance the app has always had. - OptionsActivity drives its tabs off @fields({R.id.x}) annotations, which need R fields to be constants. AGP 8 made them non-final by default, hence android.nonFinalResIds=false. - unserialize(getParcelableExtra(...)) turned ambiguous: Java 8+ can infer T as File & Parcelable, matching unserialize(File) too. A Parcelable local pins it. versionCode/versionName move from the manifest to defaultConfig, and the dead HelpActivity entry (no such class) goes. proguard-rules.txt never existed and was inert only because minifyEnabled is false; it is now a real file keeping the jni package, which native code resolves reflectively. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
Turns the documented assemble stub into a real job. It runs in the GHCR image, which already carries JDK 17, SDK 35 and NDK r26d, and stages the OpenSSL statics and libiconv with the same two scripts the native job uses — AGP drives ndk-build, so those inputs must exist before ./gradlew. Keeping the staging in CI rather than a preBuild hook leaves Gradle offline-clean for local builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
Review follow-ups. The comment on the unserialize fix said the opposite of what
the code does ("left inline" describing an extracted local); a dropped "if"
inverted it, and it is the one comment a future reader would misread.
gradle-wrapper.properties now pins distributionSha256Sum, so the 130 MB
distribution is checked rather than trusted on TLS alone; the wrapper jar was
already verified against the published checksum.
The rest trims comments back to the repo's one-line default, dropping the
roadmap narration on targetSdk and the restated mechanism on the keep rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
targetSdk 28 was chosen because anything below 29 keeps the legacy storage the File-based crawl needs. That reasoning missed the other line 28 crosses: at API 26 a NotificationChannel becomes mandatory, and this app creates none, so the system would drop every notification with nothing but a log warning. Four call sites, "mirror finished" among them, and no build or lint check sees it. 28 buys this phase nothing that 25 does not, and only 25 leaves runtime behaviour untouched. Channels belong with the targetSdk 35 move, where the emulator job can prove they work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
The assemble job went red with wget exit 4 after stalling two minutes on ftp.gnu.org. Both jobs that need libiconv fetch it from that one host on every run, with no retry and no timeout, so whenever it is slow or down the build fails for reasons that have nothing to do with the change under test. Try the GNU redirector first, then ftp.gnu.org, then the kernel.org mirror, and let wget retry with a timeout. All three were checked to serve a tarball whose sha256 matches the pinned value; that existing gate is what makes reading from a mirror safe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The app half of the modernization. The native layer was already done; this makes Gradle and the Java side build under a current toolchain, with no runtime change at all.
AGP moves 2.3.2 to 8.6.0, Gradle 3.3 to 8.7, compileSdk 11 to 35, minSdk 9 to 24, and support-v4 to androidx.core/androidx.fragment. The dead jcenter() gives way to google()/mavenCentral() in settings.gradle. AGP 8.6 is the floor for compileSdk 35, and that pins Gradle 8.7.
targetSdk deliberately stays at master's 25, because 25 is the only value that changes nothing at runtime. I had it at 28 first, on the reasoning that anything below 29 keeps the legacy storage the File-based crawl needs. That reasoning was incomplete: 28 also crosses API 26, where a NotificationChannel becomes mandatory, and this app creates none. Every notification would have been dropped by the system with only a log warning, across four call sites including "mirror finished". Nothing user-visible, nothing a build catches. Notification channels belong with the targetSdk 35 move next phase, where an emulator can verify them.
./gradlewcould not run on a clean checkout at all before this..gitignorehad/gradle, a typo for the/.gradlealready on the line above, and it matched the wrapper directory, sogradle-wrapper.jarwas never committed. The jar is now tracked, and the distribution is pinned by sha256.Three things only broke once the toolchain was current.
ButtonStylesetparent="@android:attr/buttonStyle", which aapt2 rejects outright because an attr is not a style, so the module cannot build under AGP 8 until it goes. aapt1 did accept it and did resolve the parent, but the only thing it inherited was the attr bag's reserved type key, which no widget ever queries, so it contributed nothing. Buttons already inherit the theme'sbuttonStyleas their default style, so dropping the parent leaves the appearance exactly as it has always been.OptionsActivitybuilds its tabs from@Fields({R.id.x})annotations, which need R fields to be constants, henceandroid.nonFinalResIds=false. Andunserialize(getParcelableExtra(...))turned ambiguous, because Java 8+ can inferTasFile & Parcelableand match theunserialize(File)overload too.One deviation from the plan worth checking in review: it said to rename the FileProvider
FILE_PROVIDER_PATHSmeta-data to an androidx key, butandroidx.core.content.FileProviderstill readsandroid.support.FILE_PROVIDER_PATHS. I checked that against the compiled 1.13.1 artifact. Renaming it would have broken sharing at runtime with nothing in the build to catch it, so the key is unchanged../gradlew clean assembleDebug assembleRelease lintis green against a local SDK 35 / NDK r26d install, and both APKs carry arm64-v8a and x86_64 only, targetSdkVersion 25, versionCode 63. AGP 8's packaging flip is fine here: the.soentries are stored uncompressed withextractNativeLibs=false, which needs API 23+ and minSdk is 24, and they come out 16 KB page aligned. Depending on modern androidx.core also pulls profileinstaller in, so the merged manifest gainsandroidx.startup.InitializationProviderand aProfileInstallReceivergated behind the DUMP permission; worth an ack rather than a surprise.Lint is non-fatal (
abortOnError false) and reports 13 pre-existing errors, mostly theOptionsActivity$*Tabmanifest entries that are registered as activities but extendTab; that wants its own PR. Nothing was installed on a device: there is no KVM access on this machine, so the emulator smoke job still waits for Phase 4.