Skip to content

Commit 7fd88c9

Browse files
authored
Migrate build to Gradle 9.6.1 / AGP 9 and update dependencies (#792)
Gradle 9.6 removed the internal API that AGP 8.13 relied on, forcing the AGP 9 upgrade and the surrounding build-script changes. Toolchain: - Gradle wrapper 9.3.1 -> 9.6.1; AGP 8.13.1 -> 9.3.1; apksig follows AGP. - Adopt AGP 9 built-in Kotlin: remove org.jetbrains.kotlin.android from all Android modules; Kotlin stdlib 2.3.10 -> 2.4.10. - compileSdk/targetSdk 36 -> 37, build-tools 37.0.0. Build-script migration: - Root build configures the shared CommonExtension through its getters, since AGP 9 dropped the action-DSL methods on that type. - daemon generates SignInfo through androidComponents.onVariants and a typed task; android.applicationVariants was removed. Resource generators (replace the rikka autoResConfig / materialthemebuilder plugins, whose entry points use removed AGP variant APIs): - buildSrc/GenerateLangListTask scans the translated locales. - buildSrc/GenerateMaterialThemeTask computes the accent-color theme overlays, reusing the materialthemebuilder color library without applying its plugin. Drop android.nonFinalResIds=false: AGP 9 enables optimized resource shrinking by default, and that shrinker requires non-final resource IDs. With isShrinkResources=true the manager's release build now fails :app:minifyReleaseWithR8 with "Optimized resource shrinking requires non-final IDs". AGP offers two remedies: make the IDs non-final, or opt out of optimized shrinking (r8.optimizedResourceShrinking= false). We take the former, because the false setting turns out to be dead weight: - It was added in 348f049 (Aug 2023), an unrelated "show packagename" feature commit, as a one-line drop-in beside the now-removed experimental flags enableAppCompileTimeRClass / enableNewResourceShrinker.preciseShrinking. Those siblings were cleaned up later; this line was simply missed. - Final IDs are only actually required to use R.* as Java switch/case labels. There are zero `case R.*` occurrences in the tree at 348f049 and at every commit since, so the flag never protected anything here. - Non-final IDs are the modern AGP default, so removing the line (rather than writing =true) expresses the intent with no config at all. It also builds smaller: the optimized shrinker trims the release APK from ~3.45 MB to ~3.13 MB (~9%). Verified end to end -- assembleRelease plus a zygisk installKsuAndReboot run that loads the module and starts lspd on device. Formatting task: - Add buildSrc/src/main/kotlin to the format task so the generator sources are formatted with the rest of the Kotlin build logic. - Exclude daemon/**, which is intentionally kept on ktfmt's default (Meta) style; formatting it here fought :daemon:ktfmtFormat and flipped the style back and forth. Dependencies: - AGP/apksig 9.3.1, Kotlin 2.4.10, androidx.core 1.19.0 (dependabot maven group). - coroutines 1.11.0, okhttp 5.4.0, gson 2.14.0, nav 2.9.8, glide 5.0.9, androidx activity/browser/annotation, ktfmt 0.26.0. - Material kept at 1.12.0; 1.13+ removes the colorPrimary/colorError attrs the manager references. - Submodules fmt and commons-lang bumped; CI action versions bumped (actions/checkout 6 -> 7, actions/cache 5 -> 6).
1 parent 9350c7c commit 7fd88c9

18 files changed

Lines changed: 412 additions & 172 deletions

.github/workflows/core.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919

2020
steps:
2121
- name: Checkout
22-
uses: actions/checkout@v6
22+
uses: actions/checkout@v7
2323
with:
2424
submodules: recursive
2525
fetch-depth: 0
@@ -42,7 +42,7 @@ jobs:
4242
java-version: 21
4343

4444
- name: Setup Gradle
45-
uses: gradle/actions/setup-gradle@v5
45+
uses: gradle/actions/setup-gradle@v6
4646

4747
- name: Configure Gradle properties
4848
run: |
@@ -57,7 +57,7 @@ jobs:
5757
version: 1.12.1
5858

5959
- name: Setup ccache
60-
uses: actions/cache@v5
60+
uses: actions/cache@v6
6161
with:
6262
path: |
6363
~/.ccache
@@ -67,7 +67,7 @@ jobs:
6767
${{ runner.os }}-ccache-
6868
6969
- name: Setup Android SDK
70-
uses: android-actions/setup-android@v3
70+
uses: android-actions/setup-android@v4
7171

7272
- name: Remove Android's cmake
7373
shell: bash
@@ -87,27 +87,27 @@ jobs:
8787
unzip zygisk/release/Vector-v*-Debug.zip -d Vector-Debug
8888
8989
- name: Upload zygisk release
90-
uses: actions/upload-artifact@v6
90+
uses: actions/upload-artifact@v7
9191
with:
9292
name: ${{ steps.prepareArtifact.outputs.zygiskReleaseName }}
9393
path: "./Vector-Release/*"
9494

9595
- name: Upload zygisk debug
96-
uses: actions/upload-artifact@v6
96+
uses: actions/upload-artifact@v7
9797
with:
9898
name: ${{ steps.prepareArtifact.outputs.zygiskDebugName }}
9999
path: "./Vector-Debug/*"
100100

101101
- name: Upload mappings
102-
uses: actions/upload-artifact@v6
102+
uses: actions/upload-artifact@v7
103103
with:
104104
name: mappings
105105
path: |
106106
zygisk/build/outputs/mapping
107107
app/build/outputs/mapping
108108
109109
- name: Upload symbols
110-
uses: actions/upload-artifact@v6
110+
uses: actions/upload-artifact@v7
111111
with:
112112
name: symbols
113113
path: build/symbols

app/build.gradle.kts

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ import java.time.Instant
2222
plugins {
2323
alias(libs.plugins.agp.app)
2424
alias(libs.plugins.nav.safeargs)
25-
alias(libs.plugins.autoresconfig)
26-
alias(libs.plugins.materialthemebuilder)
2725
alias(libs.plugins.lsplugin.apksign)
2826
}
2927

@@ -72,48 +70,54 @@ android {
7270
namespace = defaultManagerPackageName
7371
}
7472

75-
autoResConfig {
76-
generateClass = true
77-
generateRes = false
78-
generatedClassFullName = "org.lsposed.manager.util.LangList"
79-
generatedArrayFirstItem = "SYSTEM"
80-
}
73+
// Generate the translated-locale list and the Material accent-color theme overlays at build time
74+
// from the inputs below. Task implementations live in buildSrc.
75+
androidComponents {
76+
onVariants { variant ->
77+
val capped = variant.name.replaceFirstChar { it.uppercase() }
78+
79+
val langList =
80+
tasks.register<GenerateLangListTask>("generate${capped}LangList") {
81+
resDirs.from("src/main/res")
82+
packageName = "org.lsposed.manager.util"
83+
className = "LangList"
84+
firstItem = "SYSTEM"
85+
}
86+
variant.sources.java?.addGeneratedSourceDirectory(langList, GenerateLangListTask::outputDir)
8187

82-
materialThemeBuilder {
83-
themes {
84-
for ((name, color) in
85-
listOf(
86-
"Red" to "F44336",
87-
"Pink" to "E91E63",
88-
"Purple" to "9C27B0",
89-
"DeepPurple" to "673AB7",
90-
"Indigo" to "3F51B5",
91-
"Blue" to "2196F3",
92-
"LightBlue" to "03A9F4",
93-
"Cyan" to "00BCD4",
94-
"Teal" to "009688",
95-
"Green" to "4FAF50",
96-
"LightGreen" to "8BC3A4",
97-
"Lime" to "CDDC39",
98-
"Yellow" to "FFEB3B",
99-
"Amber" to "FFC107",
100-
"Orange" to "FF9800",
101-
"DeepOrange" to "FF5722",
102-
"Brown" to "795548",
103-
"BlueGrey" to "607D8F",
104-
"Sakura" to "FF9CA8",
105-
)) {
106-
create("Material$name") {
88+
val materialTheme =
89+
tasks.register<GenerateMaterialThemeTask>("generate${capped}MaterialTheme") {
90+
generatePalette = true
10791
lightThemeFormat = "ThemeOverlay.Light.%s"
10892
darkThemeFormat = "ThemeOverlay.Dark.%s"
109-
primaryColor = "#$color"
93+
seedColors =
94+
mapOf(
95+
"Red" to "F44336",
96+
"Pink" to "E91E63",
97+
"Purple" to "9C27B0",
98+
"DeepPurple" to "673AB7",
99+
"Indigo" to "3F51B5",
100+
"Blue" to "2196F3",
101+
"LightBlue" to "03A9F4",
102+
"Cyan" to "00BCD4",
103+
"Teal" to "009688",
104+
"Green" to "4FAF50",
105+
"LightGreen" to "8BC3A4",
106+
"Lime" to "CDDC39",
107+
"Yellow" to "FFEB3B",
108+
"Amber" to "FFC107",
109+
"Orange" to "FF9800",
110+
"DeepOrange" to "FF5722",
111+
"Brown" to "795548",
112+
"BlueGrey" to "607D8F",
113+
"Sakura" to "FF9CA8",
114+
)
110115
}
111-
}
116+
variant.sources.res?.addGeneratedSourceDirectory(
117+
materialTheme,
118+
GenerateMaterialThemeTask::outputDir,
119+
)
112120
}
113-
// Add Material Design 3 color tokens (such as palettePrimary100) in generated theme
114-
// rikka.material:material >= 2.0.0 provides such attributes
115-
// Enable this if your are using rikka.material:material
116-
generatePalette = true
117121
}
118122

119123
dependencies {

build.gradle.kts

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import org.gradle.process.ExecOperations
1111
plugins {
1212
alias(libs.plugins.agp.lib) apply false
1313
alias(libs.plugins.agp.app) apply false
14-
alias(libs.plugins.kotlin) apply false
1514
alias(libs.plugins.ktfmt)
1615
}
1716

@@ -21,12 +20,11 @@ abstract class GitCommitCountValueSource : ValueSource<String, ValueSourceParame
2120

2221
override fun obtain(): String {
2322
val output = ByteArrayOutputStream()
24-
val result =
25-
execOperations.exec {
26-
commandLine("git", "rev-list", "--count", "refs/remotes/origin/master")
27-
standardOutput = output
28-
isIgnoreExitValue = true
29-
}
23+
val result = execOperations.exec {
24+
commandLine("git", "rev-list", "--count", "refs/remotes/origin/master")
25+
standardOutput = output
26+
isIgnoreExitValue = true
27+
}
3028
// Return the count if successful, otherwise a default of "1".
3129
return if (result.exitValue == 0 && output.toString().isNotBlank()) {
3230
output.toString().trim()
@@ -42,12 +40,11 @@ abstract class GitLatestTagValueSource : ValueSource<String, ValueSourceParamete
4240

4341
override fun obtain(): String {
4442
val output = ByteArrayOutputStream()
45-
val result =
46-
execOperations.exec {
47-
commandLine("git", "tag", "--list", "--sort=-v:refname")
48-
standardOutput = output
49-
isIgnoreExitValue = true
50-
}
43+
val result = execOperations.exec {
44+
commandLine("git", "tag", "--list", "--sort=-v:refname")
45+
standardOutput = output
46+
isIgnoreExitValue = true
47+
}
5148
// If successful, parse the first line. Provide a default if no tags are found.
5249
return if (result.exitValue == 0 && output.toString().isNotBlank()) {
5350
output.toString().lineSequence().first().removePrefix("v")
@@ -65,10 +62,10 @@ val injectedPackageName by extra("com.android.shell")
6562
val injectedPackageUid by extra(2000)
6663
val defaultManagerPackageName by extra("org.lsposed.manager")
6764

68-
val androidTargetSdkVersion by extra(36)
65+
val androidTargetSdkVersion by extra(37)
6966
val androidMinSdkVersion by extra(27)
70-
val androidBuildToolsVersion by extra("36.0.0")
71-
val androidCompileSdkVersion by extra(36)
67+
val androidBuildToolsVersion by extra("37.0.0")
68+
val androidCompileSdkVersion by extra(37)
7269
val androidCompileNdkVersion by extra("29.0.13113456")
7370
val androidSourceCompatibility by extra(JavaVersion.VERSION_21)
7471
val androidTargetCompatibility by extra(JavaVersion.VERSION_21)
@@ -80,15 +77,13 @@ subprojects {
8077
ndkVersion = androidCompileNdkVersion
8178
buildToolsVersion = androidBuildToolsVersion
8279

83-
buildFeatures { buildConfig = true }
84-
externalNativeBuild {
85-
cmake {
86-
version = "3.29.8+"
87-
buildStagingDirectory = layout.buildDirectory.get().asFile
88-
}
80+
buildFeatures.buildConfig = true
81+
externalNativeBuild.cmake {
82+
version = "3.29.8+"
83+
buildStagingDirectory = layout.buildDirectory.get().asFile
8984
}
9085

91-
defaultConfig {
86+
defaultConfig.apply {
9287
minSdk = androidMinSdkVersion
9388
ndk { abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64")) }
9489

@@ -123,26 +118,24 @@ subprojects {
123118
}
124119
}
125120

126-
buildTypes {
127-
getByName("release") {
128-
externalNativeBuild {
129-
cmake {
130-
arguments.add(
131-
"-DDEBUG_SYMBOLS_PATH=${
121+
buildTypes.getByName("release").apply {
122+
externalNativeBuild {
123+
cmake {
124+
arguments.add(
125+
"-DDEBUG_SYMBOLS_PATH=${
132126
layout.buildDirectory.dir("symbols").get().asFile.absolutePath
133127
}"
134-
)
135-
}
128+
)
136129
}
137130
}
138131
}
139132

140-
lint {
133+
lint.apply {
141134
abortOnError = true
142135
checkReleaseBuilds = false
143136
}
144137

145-
compileOptions {
138+
compileOptions.apply {
146139
sourceCompatibility = androidSourceCompatibility
147140
targetCompatibility = androidTargetCompatibility
148141
}
@@ -163,7 +156,14 @@ tasks.register<KtfmtFormatTask>("format") {
163156
"*/build.gradle.kts",
164157
"hiddenapi/*/build.gradle.kts",
165158
"services/*-service/build.gradle.kts",
159+
"buildSrc/src/main/kotlin/**/*.kt",
166160
)
161+
// The daemon subproject is stuck on ktfmt's default (Meta) style instead of the
162+
// kotlinLangStyle() applied everywhere else — the wrong style was set for it, but a
163+
// bulk reformat would wreck git blame across the module, so it is kept as-is. Exclude
164+
// its build script here so this task's kotlinLangStyle sweep does not fight
165+
// :daemon:ktfmtFormat, which formats the daemon (scripts included) in Meta style.
166+
exclude("daemon/**")
167167
dependsOn(":daemon:ktfmtFormat")
168168
dependsOn(":xposed:ktfmtFormat")
169169
dependsOn(":zygisk:ktfmtFormat")

buildSrc/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Vector Build Logic
2+
3+
The `buildSrc` project holds build logic that Gradle compiles before configuring the main build. It currently contains two source generators consumed by the `:app` (manager) module. Both produce data that the manager references at compile time, so they participate in every application build rather than running as a manual pre-step.
4+
5+
The manager exposes two user-facing settings that require precomputed, per-build data: an in-app language selector and an accent-color selector. The language selector needs the concrete set of locales the application ships translations for, and the color selector needs a full Material 3 palette for each preset seed color. These generators derive that data from the resource tree and a fixed color table, so the settings stay consistent with the translations and palette definitions actually present in the source.
6+
7+
## Directory Structure
8+
9+
```text
10+
buildSrc/
11+
├── build.gradle.kts # kotlin-dsl module; declares the color-generation library
12+
└── src/main/kotlin/
13+
├── GenerateLangListTask.kt # translated-locale enumeration
14+
└── GenerateMaterialThemeTask.kt # accent-color theme overlays
15+
```
16+
17+
## Integration
18+
19+
The task classes live in the default package and are therefore visible to the module build scripts without an import. The `:app` build registers them per variant through the AGP variant API:
20+
21+
```kotlin
22+
androidComponents {
23+
onVariants { variant ->
24+
val langList = tasks.register<GenerateLangListTask>("generate${cap}LangList") { … }
25+
variant.sources.java?.addGeneratedSourceDirectory(langList, GenerateLangListTask::outputDir)
26+
27+
val theme = tasks.register<GenerateMaterialThemeTask>("generate${cap}MaterialTheme") { … }
28+
variant.sources.res?.addGeneratedSourceDirectory(theme, GenerateMaterialThemeTask::outputDir)
29+
}
30+
}
31+
```
32+
33+
`addGeneratedSourceDirectory` binds each task's `outputDir` into the variant's Java or resource source set. AGP owns the output location, wires the task into the source-merge graph, and establishes the task dependency automatically, so the generated Java is compiled and the generated resources are merged like any hand-written source.
34+
35+
## Locale List Generation
36+
37+
`GenerateLangListTask` emits the `org.lsposed.manager.util.LangList` class, which the settings screen reads to populate the language dropdown (`LangList.LOCALES` for entry values, `LangList.DISPLAY_LOCALES` for the script-qualified display keys).
38+
39+
Inputs are the resource directories to scan (`resDirs`), the target `packageName` and `className`, and the leading `firstItem` (`SYSTEM`). The task:
40+
41+
* Collects every `values-<qualifier>` folder that contains a `strings.xml`, treating the qualifier as a translated locale and ignoring configuration-only qualifiers such as `values-night` or `values-v31`.
42+
* Converts each Android qualifier to a BCP-47 tag: a `b+`-prefixed qualifier has its `+` separators replaced with `-`, and a trailing `-r<REGION>` segment drops its `r` marker (`zh-rCN` becomes `zh-CN`, `pt-rBR` becomes `pt-BR`).
43+
* Adds `en` for the default `values/` resources, sorts the tags, and prepends `firstItem`.
44+
* Produces `DISPLAY_LOCALES` as a parallel array in which the Simplified and Traditional Chinese tags are rewritten to their script forms (`zh-CN` to `zh-Hans`, `zh-TW` to `zh-Hant`); all other tags are identical to `LOCALES`.
45+
46+
Adding a new translation is therefore a matter of dropping a `values-<locale>/strings.xml` folder into the module; the list updates on the next build with no manual edits.
47+
48+
## Material Theme Generation
49+
50+
`GenerateMaterialThemeTask` emits a resource `values.xml` containing the color roles and theme-overlay styles that back the accent-color options. The manager maps each preset to a generated style through `ThemeUtil` (for example `MATERIAL_RED` to `R.style.ThemeOverlay_MaterialRed`) and applies it to recolor the interface.
51+
52+
Inputs are the seed color table (`seedColors`, mapping a theme name to a hex value), the `generatePalette` flag, and the `lightThemeFormat` / `darkThemeFormat` style-name templates. For each seed the task computes a Material 3 tonal palette (primary, secondary, tertiary, neutral, neutral-variant, and error ramps across the standard tone stops) and writes:
53+
54+
* The resolved color roles for the light and dark schemes (`colorPrimary`, `colorOnPrimary`, `colorPrimaryContainer`, and the remaining container, surface, and outline roles).
55+
* A `ThemeOverlay.Light.Material<name>` and `ThemeOverlay.Dark.Material<name>` style that binds those roles, plus the discrete palette attributes (`palettePrimary0` through `palettePrimary100`, and the equivalent ramps for the other tonal palettes) when `generatePalette` is set.
56+
57+
The color science and XML serialization are provided by the `dev.rikka.tools.materialthemebuilder` artifact declared in `build.gradle.kts`. The task drives it directly: it instantiates a `MaterialThemeBuilderExtension` through the `ObjectFactory`, populates the theme container from `seedColors`, and invokes `ValuesAllGenerator`. Only the library classes are used; the artifact's Gradle plugin is not applied. Themes are keyed in a `NamedDomainObjectContainer`, so the output is ordered by theme name and is deterministic for a given seed table.

buildSrc/build.gradle.kts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
plugins { `kotlin-dsl` }
2+
3+
repositories {
4+
google()
5+
mavenCentral()
6+
}
7+
8+
dependencies {
9+
// Supplies the Material color science (HCT, tonal palettes) and XML generators used by
10+
// GenerateMaterialThemeTask. Only the library classes are referenced; the plugin is not
11+
// applied.
12+
implementation("dev.rikka.tools.materialthemebuilder:gradle-plugin:1.5.1")
13+
}

0 commit comments

Comments
 (0)