Skip to content
Merged
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
8 changes: 6 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,12 @@ jobs:
echo "${{ secrets.KEYSTORE_B64 }}" | base64 -d > release.keystore
fi
KEYSTORE_PASS="${{ secrets.KEYSTORE_PASS }}" ALIAS_NAME="${{ secrets.ALIAS_NAME }}" ALIAS_PASS="${{ secrets.ALIAS_PASS }}" ./gradlew app:assembleOssRelease
APK=$(find app/build/outputs/apk -name '*arm64-v8a*.apk')
APK=$(dirname $APK)
APK=$(find app/build/outputs/renamed_apks -name '*arm64-v8a*.apk' | head -n 1)
if [ -z "$APK" ]; then
echo "Error: no arm64-v8a APK found under app/build/outputs/renamed_apks" >&2
exit 1
fi
APK=$(dirname "$APK")
echo "APK=$APK" >> $GITHUB_ENV
- name: Upload Artifacts
uses: namespace-actions/upload-artifact@v1
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,12 @@ jobs:
export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}"
./run init action gradle
./gradlew app:assembleOssDebug
APK=$(find app/build/outputs/apk -name '*arm64-v8a*.apk')
APK=$(dirname $APK)
APK=$(find app/build/outputs/renamed_apks -name '*arm64-v8a*.apk' | head -n 1)
if [ -z "$APK" ]; then
echo "Error: no arm64-v8a APK found under app/build/outputs/renamed_apks" >&2
exit 1
fi
APK=$(dirname "$APK")
echo "APK=$APK" >> $GITHUB_ENV
- name: Upload Artifacts
uses: namespace-actions/upload-artifact@v1
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,12 @@ jobs:
export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}"
./run init action gradle
./gradlew app:assemblePreviewRelease
APK=$(find app/build/outputs/apk -name '*arm64-v8a*.apk')
APK=$(dirname $APK)
APK=$(find app/build/outputs/renamed_apks -name '*arm64-v8a*.apk' | head -n 1)
if [ -z "$APK" ]; then
echo "Error: no arm64-v8a APK found under app/build/outputs/renamed_apks" >&2
exit 1
fi
APK=$(dirname "$APK")
echo "APK=$APK" >> $GITHUB_ENV
- uses: namespace-actions/upload-artifact@v1
with:
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,12 @@ jobs:
echo "${{ secrets.KEYSTORE_B64 }}" | base64 -d > release.keystore
fi
KEYSTORE_PASS="${{ secrets.KEYSTORE_PASS }}" ALIAS_NAME="${{ secrets.ALIAS_NAME }}" ALIAS_PASS="${{ secrets.ALIAS_PASS }}" ./gradlew app:assembleOssRelease
APK=$(find app/build/outputs/apk -name '*arm64-v8a*.apk')
APK=$(dirname $APK)
APK=$(find app/build/outputs/renamed_apks -name '*arm64-v8a*.apk' | head -n 1)
if [ -z "$APK" ]; then
echo "Error: no arm64-v8a APK found under app/build/outputs/renamed_apks" >&2
exit 1
fi
APK=$(dirname "$APK")
echo "APK=$APK" >> $GITHUB_ENV
- uses: namespace-actions/upload-artifact@v1
with:
Expand Down
4 changes: 3 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

plugins {
id("com.android.application")
id("kotlin-android")
// kotlin-android is no longer applied: AGP 9.0+ provides built-in Kotlin support
// (android.builtInKotlin defaults to true), so the org.jetbrains.kotlin.android plugin
// would clash ("extension already registered with name 'kotlin'").
id("com.google.devtools.ksp")
id("kotlin-parcelize")
}
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ allprojects {
}

tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
delete(rootProject.layout.buildDirectory)
}

plugins {
Expand Down
5 changes: 4 additions & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ apply(from = "../repositories.gradle.kts")

dependencies {
// Gradle Plugins
implementation("com.android.tools.build:gradle:8.13.2")
// AGP 9.2.1 has a runtime dependency on KGP and bundles Kotlin Gradle Plugin 2.3.10,
// which matches the version we were pinning explicitly. Keep KGP pinned for clarity so
// the buildSrc classpath is unambiguous.
implementation("com.android.tools.build:gradle:9.2.1")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.10")
}
85 changes: 41 additions & 44 deletions buildSrc/src/main/kotlin/Helpers.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.gradle.AbstractAppExtension
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByName
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
Expand All @@ -12,6 +13,8 @@ import java.util.Properties
import kotlin.system.exitProcess

private val Project.android get() = extensions.getByName<ApplicationExtension>("android")
private val Project.androidComponents
get() = extensions.getByName<ApplicationAndroidComponentsExtension>("androidComponents")

private lateinit var metadata: Properties
private lateinit var localProperties: Properties
Expand Down Expand Up @@ -82,28 +85,22 @@ fun Project.setupCommon() {
)
)
}
(this as? AbstractAppExtension)?.apply {
buildTypes {
getByName("release") {
isShrinkResources = true
if (System.getenv("nkmr_minify") == "0") {
isShrinkResources = false
isMinifyEnabled = false
}
}
getByName("debug") {
applicationIdSuffix = "debug"
debuggable(true)
jniDebuggable(true)
// Under AGP 9's new DSL the build types below are configured directly on
// ApplicationExtension (no AbstractAppExtension cast needed). APK output renaming is no
// longer done here via the removed applicationVariants/BaseVariantOutputImpl API; it is
// handled by the RenameApkTask wired in setupApp() through androidComponents.onVariants.
buildTypes {
getByName("release") {
isShrinkResources = true
if (System.getenv("nkmr_minify") == "0") {
isShrinkResources = false
isMinifyEnabled = false
}
}
applicationVariants.forEach { variant ->
variant.outputs.forEach {
it as BaseVariantOutputImpl
it.outputFileName = it.outputFileName.replace(
"app", "${project.name}-" + variant.versionName
).replace("-release", "").replace("-oss", "")
}
getByName("debug") {
applicationIdSuffix = "debug"
isDebuggable = true
isJniDebuggable = true
}
}
}
Expand Down Expand Up @@ -173,8 +170,6 @@ fun Project.setupApp() {
setupAppCommon()

android.apply {
this as AbstractAppExtension

buildTypes {
getByName("release") {
proguardFiles(
Expand Down Expand Up @@ -208,31 +203,33 @@ fun Project.setupApp() {
}
}

applicationVariants.all {
outputs.all {
this as BaseVariantOutputImpl
val isPreview = outputFileName.contains("-preview")
outputFileName = if (isPreview) {
outputFileName.replace(
project.name,
"NekoBox-" + requireMetadata().getProperty("PRE_VERSION_NAME")
).replace("-preview", "")
} else {
outputFileName.replace(project.name, "NekoBox-$versionName")
.replace("-release", "")
.replace("-oss", "")
}
}
}

for (abi in listOf("Arm64", "Arm", "X64", "X86")) {
tasks.create("assemble" + abi + "FdroidRelease") {
tasks.register("assemble" + abi + "FdroidRelease") {
dependsOn("assembleFdroidRelease")
}
}

sourceSets.getByName("main").apply {
jniLibs.srcDir("executableSo")
jniLibs.directories.add("executableSo")
}
}
}

// APK output renaming. The legacy applicationVariants/BaseVariantOutputImpl API was removed in
// AGP 9 (new DSL). Instead we react to artifact creation and copy the produced APKs into
// build/outputs/renamed_apks/<variant> with friendly NekoBox-<version>[-<abi>].apk names.
// The preview flavor uses PRE_VERSION_NAME to match the previous behaviour.
val previewVersionName = requireMetadata().getProperty("PRE_VERSION_NAME")
androidComponents.onVariants { variant ->
val isPreview = variant.flavorName == "preview" ||
variant.productFlavors.any { (_, flavor) -> flavor == "preview" }
val version = if (isPreview) previewVersionName else verName
val renameTask = tasks.register<RenameApkTask>("renameApksFor${variant.name.replaceFirstChar { it.uppercase() }}") {
output.set(layout.buildDirectory.dir("outputs/renamed_apks/${variant.name}"))
builtArtifactsLoader.set(variant.artifacts.getBuiltArtifactsLoader())
baseName.set("NekoBox-$version")
}
variant.artifacts.use(renameTask)
.wiredWith { it.input }
.toListenTo(SingleArtifact.APK)
}
}
98 changes: 98 additions & 0 deletions buildSrc/src/main/kotlin/RenameApkTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import com.android.build.api.variant.BuiltArtifactsLoader
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption

/**
* Copies the APKs produced for a single variant into a dedicated `renamed_apks/<variant>`
* directory, giving them friendly `NekoBox-<versionName>[-<abi>].apk` filenames.
*
* AGP 9 (new DSL) removed the legacy `applicationVariants` / `BaseVariantOutputImpl` API that
* previously let us rewrite `outputFileName` in place. The supported replacement is to react to
* artifact creation via `androidComponents.onVariants { ... }` and wire a task that *copies*
* the produced APKs, using a [BuiltArtifactsLoader] to enumerate every split (one APK per ABI)
* along with its metadata. The original APKs in `outputs/apk/...` are left untouched.
*
* Only public AGP/Gradle APIs are used here (no `com.android.build.gradle.internal.*` and no
* `org.gradle.internal.impldep.*`).
*/
abstract class RenameApkTask : DefaultTask() {

/** The directory of APKs produced by AGP for this variant (wired automatically by AGP). */
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val input: DirectoryProperty

/** Destination directory: `build/outputs/renamed_apks/<variant>`. */
@get:OutputDirectory
abstract val output: DirectoryProperty

/** Loader that enumerates the built APKs (one per ABI split) and their metadata. */
@get:Internal
abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>

/**
* Friendly base name to use instead of the default project/module name, e.g.
* `NekoBox-1.4.2-mod-12` or `NekoBox-pre-1.4.2-20260214-1` for the preview flavor.
* Declared as @Input so a version bump invalidates the task and the APKs are re-copied
* with the new name rather than leaving stale files in the output directory.
*/
@get:Input
abstract val baseName: Property<String>

@TaskAction
fun taskAction() {
val outputDir = output.get()
val outputFile = outputDir.asFile

// This task does not run incrementally; start from a clean output directory.
outputFile.deleteRecursively()
outputFile.mkdirs()

val builtArtifacts = builtArtifactsLoader.get().load(input.get())
?: throw RuntimeException("Cannot load APKs from ${input.get().asFile}")

val base = baseName.get()

builtArtifacts.elements.forEach { artifact ->
val source = File(artifact.outputFile)
// Preserve the ABI substring from the original split filename (e.g. "arm64-v8a")
// so downstream tooling (CI globs `*arm64-v8a*.apk`) keeps matching.
val abi = ABIS.firstOrNull { source.name.contains(it) }

val targetName = buildString {
append(base)
if (abi != null) append("-").append(abi)
append(".apk")
}

Files.copy(
source.toPath(),
outputDir.file(targetName).asFile.toPath(),
StandardCopyOption.REPLACE_EXISTING,
)
}

// NOTE: we intentionally do NOT call builtArtifacts.save(outputDir). That would write an
// output-metadata.json whose `outputFile` entries still point at the original (un-renamed)
// APK paths in outputs/apk/<variant>/, not at the NekoBox-* copies next to it - which would
// mislead any BuiltArtifactsLoader consumer back to the originals. Nothing downstream
// consumes this metadata (CI just globs the *.apk files), so we omit it rather than emit a
// misleading file.
}

private companion object {
// Order matters: check the more specific names before substrings would collide.
val ABIS = listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
}
}
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
distributionSha256Sum=6f74b601422d6d6fc4e1f9a1ab6522f642c2fdcbc15ae33ebd30ba3d7198e854
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
Loading