Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
java-version: 21

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6

- name: Configure Gradle properties
run: |
Expand All @@ -67,7 +67,7 @@ jobs:
${{ runner.os }}-ccache-

- name: Setup Android SDK
uses: android-actions/setup-android@v3
uses: android-actions/setup-android@v4

- name: Remove Android's cmake
shell: bash
Expand All @@ -87,27 +87,27 @@ jobs:
unzip zygisk/release/Vector-v*-Debug.zip -d Vector-Debug

- name: Upload zygisk release
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ${{ steps.prepareArtifact.outputs.zygiskReleaseName }}
path: "./Vector-Release/*"

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

- name: Upload mappings
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: mappings
path: |
zygisk/build/outputs/mapping
app/build/outputs/mapping

- name: Upload symbols
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: symbols
path: build/symbols
Expand Down
82 changes: 43 additions & 39 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import java.time.Instant
plugins {
alias(libs.plugins.agp.app)
alias(libs.plugins.nav.safeargs)
alias(libs.plugins.autoresconfig)
alias(libs.plugins.materialthemebuilder)
alias(libs.plugins.lsplugin.apksign)
}

Expand Down Expand Up @@ -72,48 +70,54 @@ android {
namespace = defaultManagerPackageName
}

autoResConfig {
generateClass = true
generateRes = false
generatedClassFullName = "org.lsposed.manager.util.LangList"
generatedArrayFirstItem = "SYSTEM"
}
// Generate the translated-locale list and the Material accent-color theme overlays at build time
// from the inputs below. Task implementations live in buildSrc.
androidComponents {
onVariants { variant ->
val capped = variant.name.replaceFirstChar { it.uppercase() }

val langList =
tasks.register<GenerateLangListTask>("generate${capped}LangList") {
resDirs.from("src/main/res")
packageName = "org.lsposed.manager.util"
className = "LangList"
firstItem = "SYSTEM"
}
variant.sources.java?.addGeneratedSourceDirectory(langList, GenerateLangListTask::outputDir)

materialThemeBuilder {
themes {
for ((name, color) in
listOf(
"Red" to "F44336",
"Pink" to "E91E63",
"Purple" to "9C27B0",
"DeepPurple" to "673AB7",
"Indigo" to "3F51B5",
"Blue" to "2196F3",
"LightBlue" to "03A9F4",
"Cyan" to "00BCD4",
"Teal" to "009688",
"Green" to "4FAF50",
"LightGreen" to "8BC3A4",
"Lime" to "CDDC39",
"Yellow" to "FFEB3B",
"Amber" to "FFC107",
"Orange" to "FF9800",
"DeepOrange" to "FF5722",
"Brown" to "795548",
"BlueGrey" to "607D8F",
"Sakura" to "FF9CA8",
)) {
create("Material$name") {
val materialTheme =
tasks.register<GenerateMaterialThemeTask>("generate${capped}MaterialTheme") {
generatePalette = true
lightThemeFormat = "ThemeOverlay.Light.%s"
darkThemeFormat = "ThemeOverlay.Dark.%s"
primaryColor = "#$color"
seedColors =
mapOf(
"Red" to "F44336",
"Pink" to "E91E63",
"Purple" to "9C27B0",
"DeepPurple" to "673AB7",
"Indigo" to "3F51B5",
"Blue" to "2196F3",
"LightBlue" to "03A9F4",
"Cyan" to "00BCD4",
"Teal" to "009688",
"Green" to "4FAF50",
"LightGreen" to "8BC3A4",
"Lime" to "CDDC39",
"Yellow" to "FFEB3B",
"Amber" to "FFC107",
"Orange" to "FF9800",
"DeepOrange" to "FF5722",
"Brown" to "795548",
"BlueGrey" to "607D8F",
"Sakura" to "FF9CA8",
)
}
}
variant.sources.res?.addGeneratedSourceDirectory(
materialTheme,
GenerateMaterialThemeTask::outputDir,
)
}
// Add Material Design 3 color tokens (such as palettePrimary100) in generated theme
// rikka.material:material >= 2.0.0 provides such attributes
// Enable this if your are using rikka.material:material
generatePalette = true
}

dependencies {
Expand Down
66 changes: 33 additions & 33 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import org.gradle.process.ExecOperations
plugins {
alias(libs.plugins.agp.lib) apply false
alias(libs.plugins.agp.app) apply false
alias(libs.plugins.kotlin) apply false
alias(libs.plugins.ktfmt)
}

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

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

override fun obtain(): String {
val output = ByteArrayOutputStream()
val result =
execOperations.exec {
commandLine("git", "tag", "--list", "--sort=-v:refname")
standardOutput = output
isIgnoreExitValue = true
}
val result = execOperations.exec {
commandLine("git", "tag", "--list", "--sort=-v:refname")
standardOutput = output
isIgnoreExitValue = true
}
// If successful, parse the first line. Provide a default if no tags are found.
return if (result.exitValue == 0 && output.toString().isNotBlank()) {
output.toString().lineSequence().first().removePrefix("v")
Expand All @@ -65,10 +62,10 @@ val injectedPackageName by extra("com.android.shell")
val injectedPackageUid by extra(2000)
val defaultManagerPackageName by extra("org.lsposed.manager")

val androidTargetSdkVersion by extra(36)
val androidTargetSdkVersion by extra(37)
val androidMinSdkVersion by extra(27)
val androidBuildToolsVersion by extra("36.0.0")
val androidCompileSdkVersion by extra(36)
val androidBuildToolsVersion by extra("37.0.0")
val androidCompileSdkVersion by extra(37)
val androidCompileNdkVersion by extra("29.0.13113456")
val androidSourceCompatibility by extra(JavaVersion.VERSION_21)
val androidTargetCompatibility by extra(JavaVersion.VERSION_21)
Expand All @@ -80,15 +77,13 @@ subprojects {
ndkVersion = androidCompileNdkVersion
buildToolsVersion = androidBuildToolsVersion

buildFeatures { buildConfig = true }
externalNativeBuild {
cmake {
version = "3.29.8+"
buildStagingDirectory = layout.buildDirectory.get().asFile
}
buildFeatures.buildConfig = true
externalNativeBuild.cmake {
version = "3.29.8+"
buildStagingDirectory = layout.buildDirectory.get().asFile
}

defaultConfig {
defaultConfig.apply {
minSdk = androidMinSdkVersion
ndk { abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64")) }

Expand Down Expand Up @@ -123,26 +118,24 @@ subprojects {
}
}

buildTypes {
getByName("release") {
externalNativeBuild {
cmake {
arguments.add(
"-DDEBUG_SYMBOLS_PATH=${
buildTypes.getByName("release").apply {
externalNativeBuild {
cmake {
arguments.add(
"-DDEBUG_SYMBOLS_PATH=${
layout.buildDirectory.dir("symbols").get().asFile.absolutePath
}"
)
}
)
}
}
}

lint {
lint.apply {
abortOnError = true
checkReleaseBuilds = false
}

compileOptions {
compileOptions.apply {
sourceCompatibility = androidSourceCompatibility
targetCompatibility = androidTargetCompatibility
}
Expand All @@ -163,7 +156,14 @@ tasks.register<KtfmtFormatTask>("format") {
"*/build.gradle.kts",
"hiddenapi/*/build.gradle.kts",
"services/*-service/build.gradle.kts",
"buildSrc/src/main/kotlin/**/*.kt",
)
// The daemon subproject is stuck on ktfmt's default (Meta) style instead of the
// kotlinLangStyle() applied everywhere else — the wrong style was set for it, but a
// bulk reformat would wreck git blame across the module, so it is kept as-is. Exclude
// its build script here so this task's kotlinLangStyle sweep does not fight
// :daemon:ktfmtFormat, which formats the daemon (scripts included) in Meta style.
exclude("daemon/**")
dependsOn(":daemon:ktfmtFormat")
dependsOn(":xposed:ktfmtFormat")
dependsOn(":zygisk:ktfmtFormat")
Expand Down
57 changes: 57 additions & 0 deletions buildSrc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Vector Build Logic

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.

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.

## Directory Structure

```text
buildSrc/
├── build.gradle.kts # kotlin-dsl module; declares the color-generation library
└── src/main/kotlin/
├── GenerateLangListTask.kt # translated-locale enumeration
└── GenerateMaterialThemeTask.kt # accent-color theme overlays
```

## Integration

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:

```kotlin
androidComponents {
onVariants { variant ->
val langList = tasks.register<GenerateLangListTask>("generate${cap}LangList") { … }
variant.sources.java?.addGeneratedSourceDirectory(langList, GenerateLangListTask::outputDir)

val theme = tasks.register<GenerateMaterialThemeTask>("generate${cap}MaterialTheme") { … }
variant.sources.res?.addGeneratedSourceDirectory(theme, GenerateMaterialThemeTask::outputDir)
}
}
```

`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.

## Locale List Generation

`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).

Inputs are the resource directories to scan (`resDirs`), the target `packageName` and `className`, and the leading `firstItem` (`SYSTEM`). The task:

* 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`.
* 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`).
* Adds `en` for the default `values/` resources, sorts the tags, and prepends `firstItem`.
* 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`.

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.

## Material Theme Generation

`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.

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:

* The resolved color roles for the light and dark schemes (`colorPrimary`, `colorOnPrimary`, `colorPrimaryContainer`, and the remaining container, surface, and outline roles).
* 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.

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.
13 changes: 13 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins { `kotlin-dsl` }

repositories {
google()
mavenCentral()
}

dependencies {
// Supplies the Material color science (HCT, tonal palettes) and XML generators used by
// GenerateMaterialThemeTask. Only the library classes are referenced; the plugin is not
// applied.
implementation("dev.rikka.tools.materialthemebuilder:gradle-plugin:1.5.1")
}
Loading
Loading