diff --git a/build.gradle.kts b/build.gradle.kts index 2f9d520..0b1bd71 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,22 +1,31 @@ import com.android.build.gradle.LibraryExtension import java.io.ByteArrayOutputStream +import javax.inject.Inject +import org.gradle.process.ExecOperations plugins { alias(libs.plugins.agp.lib) apply false } -fun String.execute(currentWorkingDir: File = file("./")): String { - val byteOut = ByteArrayOutputStream() - project.exec { - workingDir = currentWorkingDir - commandLine = split("\\s".toRegex()) - standardOutput = byteOut +// Helper class to get access to the ExecOperations service +abstract class GitExecutor @Inject constructor(private val execOperations: ExecOperations) { + fun execute(command: String, currentWorkingDir: File): String { + val byteOut = ByteArrayOutputStream() + execOperations.exec { + workingDir = currentWorkingDir + commandLine = command.split("\\s".toRegex()) + standardOutput = byteOut + } + return String(byteOut.toByteArray()).trim() } - return String(byteOut.toByteArray()).trim() } -val gitCommitCount = "git rev-list HEAD --count".execute().toInt() -val gitCommitHash = "git rev-parse --verify --short HEAD".execute() +// Instantiate the helper class using Gradle's object factory +val gitExecutor = objects.newInstance(GitExecutor::class.java) + +// Use the helper to execute the git commands +val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt() +val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir) val moduleId by extra("zygisksu") val moduleName by extra("NeoZygisk") @@ -42,7 +51,7 @@ val androidSourceCompatibility by extra(JavaVersion.VERSION_21) val androidTargetCompatibility by extra(JavaVersion.VERSION_21) tasks.register("Delete", Delete::class) { - delete(rootProject.buildDir) + delete(rootProject.layout.buildDirectory) } fun Project.configureBaseExtension() { @@ -54,10 +63,10 @@ fun Project.configureBaseExtension() { defaultConfig { minSdk = androidMinSdkVersion - targetSdk = androidTargetSdkVersion } lint { + targetSdk = androidTargetSdkVersion abortOnError = true } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bfd3549..f4c12fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -agp = "8.11.1" -kotlin = "2.2.0" +agp = "8.13.0" +kotlin = "2.2.20" [plugins] agp-lib = { id = "com.android.library", version.ref = "agp" } diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts index 4e3da5d..061b6dd 100644 --- a/loader/build.gradle.kts +++ b/loader/build.gradle.kts @@ -42,11 +42,14 @@ val releaseFlags = arrayOf( android { buildFeatures { - androidResources = false buildConfig = false prefab = true } + androidResources { + enable = false + } + externalNativeBuild.cmake { path("src/CMakeLists.txt") buildStagingDirectory = layout.buildDirectory.get().asFile diff --git a/module/build.gradle.kts b/module/build.gradle.kts index 03c30e9..e8faec3 100644 --- a/module/build.gradle.kts +++ b/module/build.gradle.kts @@ -1,25 +1,16 @@ -import android.databinding.tool.ext.capitalizeUS -import java.security.MessageDigest -import org.apache.tools.ant.filters.ReplaceTokens - +import com.android.build.api.variant.LibraryVariant import org.apache.tools.ant.filters.FixCrLfFilter - +import org.apache.tools.ant.filters.ReplaceTokens import org.apache.commons.codec.binary.Hex -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.security.KeyFactory -import java.security.KeyPairGenerator -import java.security.Signature -import java.security.interfaces.EdECPrivateKey -import java.security.interfaces.EdECPublicKey -import java.security.spec.EdECPrivateKeySpec -import java.security.spec.NamedParameterSpec -import java.util.TreeSet +import java.security.MessageDigest +import org.gradle.process.ExecOperations +import javax.inject.Inject plugins { alias(libs.plugins.agp.lib) } +// region Extra Properties val moduleId: String by rootProject.extra val moduleName: String by rootProject.extra val verCode: Int by rootProject.extra @@ -32,63 +23,112 @@ val minMagiskVersion: Int by rootProject.extra val workDirectory: String by rootProject.extra val commitHash: String by rootProject.extra val updateJson: String by rootProject.extra +// endregion + +android { + androidResources.enable = false + buildFeatures.buildConfig = false +} -android.buildFeatures { - androidResources = false - buildConfig = false +interface ExecOperationsService { + @get:Inject + val execOperations: ExecOperations +} + +fun Project.registerShellInstallTask( + variant: LibraryVariant, + toolName: String, + pushTaskProvider: TaskProvider, + zipFileNameProvider: Provider, + installCommand: String +): TaskProvider { + val variantCapped = variant.name.replaceFirstChar { it.titlecase() } + return tasks.register("install${toolName}${variantCapped}") { + group = "module" + description = "Pushes the module zip and installs it using $toolName." + dependsOn(pushTaskProvider) + val serviceHolder = project.objects.newInstance() + doLast { + val execOps = serviceHolder.execOperations + val zipFileName = zipFileNameProvider.get() + execOps.exec { + commandLine( + "adb", "shell", "echo", + "'$installCommand /data/local/tmp/$zipFileName'", + "> /data/local/tmp/install.sh" + ) + } + execOps.exec { + commandLine("adb", "shell", "chmod", "755", "/data/local/tmp/install.sh") + } + execOps.exec { + commandLine("adb", "shell", "su", "-c", "/data/local/tmp/install.sh") + } + } + } +} + +fun Project.registerRebootTask( + variant: LibraryVariant, + toolName: String, + installTaskProvider: TaskProvider +): TaskProvider { + val variantCapped = variant.name.replaceFirstChar { it.titlecase() } + return tasks.register("install${toolName}AndReboot${variantCapped}") { + group = "module" + description = "Installs the module using $toolName and reboots the device." + dependsOn(installTaskProvider) + commandLine("adb", "reboot") + } } androidComponents.onVariants { variant -> + val variantCapped = variant.name.replaceFirstChar { it.titlecase() } val variantLowered = variant.name.lowercase() - val variantCapped = variant.name.capitalizeUS() - val buildTypeLowered = variant.buildType?.lowercase() + val buildTypeLowered = variant.buildType?.lowercase() ?: "" val moduleDir = layout.buildDirectory.dir("outputs/module/$variantLowered") - val zipFileName = "$moduleName-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-') + val zipFileNameProvider = provider { "$moduleName-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-') } - val prepareModuleFilesTask = task("prepareModuleFiles$variantCapped") { + val prepareModuleFilesTask = tasks.register("prepareModuleFiles$variantCapped") { group = "module" - dependsOn( - ":loader:assemble$variantCapped", - ":zygiskd:buildAndStrip", - ) + dependsOn(":loader:assemble$variantCapped", ":zygiskd:buildAndStrip") into(moduleDir) - from("${rootProject.projectDir}/README.md") - from("$projectDir/src") { + from(rootProject.projectDir.resolve("README.md")) + from(projectDir.resolve("src")) { exclude("module.prop", "action.sh", "customize.sh", "post-fs-data.sh", "service.sh", "uninstall.sh", "zygisk-ctl.sh") - filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) + filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf"))) } - from("$projectDir/src") { + from(projectDir.resolve("src")) { include("module.prop") expand( "moduleId" to moduleId, "moduleName" to moduleName, "versionName" to "$verName ($verCode-$commitHash-$variantLowered)", - "versionCode" to verCode, + "versionCode" to verCode.toString(), "updateJson" to updateJson ) } - from("$projectDir/src") { + from(projectDir.resolve("src")) { include("action.sh", "customize.sh", "post-fs-data.sh", "service.sh", "uninstall.sh", "zygisk-ctl.sh") val tokens = mapOf( - "DEBUG" to if (buildTypeLowered == "debug") "true" else "false", - "MIN_APATCH_VERSION" to "$minAPatchVersion", - "MIN_KSU_VERSION" to "$minKsuVersion", - "MIN_KSUD_VERSION" to "$minKsudVersion", - "MAX_KSU_VERSION" to "$maxKsuVersion", - "MIN_MAGISK_VERSION" to "$minMagiskVersion", - "WORK_DIRECTORY" to "$workDirectory", + "DEBUG" to (buildTypeLowered == "debug").toString(), + "MIN_APATCH_VERSION" to minAPatchVersion.toString(), + "MIN_KSU_VERSION" to minKsuVersion.toString(), + "MIN_KSUD_VERSION" to minKsudVersion.toString(), + "MAX_KSU_VERSION" to maxKsuVersion.toString(), + "MIN_MAGISK_VERSION" to minMagiskVersion.toString(), + "WORK_DIRECTORY" to workDirectory ) - filter("tokens" to tokens) - filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) + filter(mapOf("tokens" to tokens)) + filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf"))) } into("bin") { - from(project(":zygiskd").layout.buildDirectory.file("rustJniLibs/android")) - include("**/zygiskd") - } - into("lib") { - from(project(":loader").layout.buildDirectory.file("intermediates/stripped_native_libs/$variantLowered/strip${variantCapped}DebugSymbols/out/lib")) + from(project(":zygiskd").layout.buildDirectory.dir("rustJniLibs/android")) { + include("**/zygiskd") + } } + into("lib") { from(project(":loader").layout.buildDirectory.file("intermediates/stripped_native_libs/$variantLowered/strip${variantCapped}DebugSymbols/out/lib")) } doLast { fileTree(moduleDir).visit { @@ -97,78 +137,47 @@ androidComponents.onVariants { variant -> file.forEachBlock(4096) { bytes, size -> md.update(bytes, 0, size) } - file(file.path + ".sha256").writeText(Hex.encodeHexString(md.digest())) + // Use project.file() for robust path resolution. + project.file(file.path + ".sha256").writeText(Hex.encodeHexString(md.digest())) } } } - val zipTask = task("zip$variantCapped") { + val zipTask = tasks.register("zip$variantCapped") { group = "module" dependsOn(prepareModuleFilesTask) - archiveFileName.set(zipFileName) - destinationDirectory.set(layout.buildDirectory.file("outputs/release").get().asFile) + archiveFileName.set(zipFileNameProvider) + destinationDirectory.set(layout.buildDirectory.dir("outputs/release")) from(moduleDir) } - val pushTask = task("push$variantCapped") { + val pushTask = tasks.register("push$variantCapped") { group = "module" dependsOn(zipTask) - commandLine("adb", "push", zipTask.outputs.files.singleFile.path, "/data/local/tmp") - } - - val installAPatchTask = task("installAPatch$variantCapped") { - group = "module" - dependsOn(pushTask) - doLast { - exec { - commandLine( - "adb", "shell", "echo", - "/data/adb/apd module install /data/local/tmp/$zipFileName", - "> /data/local/tmp/install.sh" - ) - } - exec { commandLine("adb", "shell", "chmod", "755", "/data/local/tmp/install.sh") } - exec { commandLine("adb", "shell", "su", "-c", "/data/local/tmp/install.sh") } + doFirst { + commandLine( + "adb", + "push", + zipTask.get().archiveFile.get().asFile.absolutePath, + "/data/local/tmp" + ) } } - val installKsuTask = task("installKsu$variantCapped") { - group = "module" - dependsOn(pushTask) - doLast { - exec { - commandLine( - "adb", "shell", "echo", - "/data/adb/ksud module install /data/local/tmp/$zipFileName", - "> /data/local/tmp/install.sh" - ) - } - exec { commandLine("adb", "shell", "chmod", "755", "/data/local/tmp/install.sh") } - exec { commandLine("adb", "shell", "su", "-c", "/data/local/tmp/install.sh") } - } - } + val installAPatchTask = registerShellInstallTask(variant, "APatch", pushTask, zipFileNameProvider, "/data/adb/apd module install") + val installKsuTask = registerShellInstallTask(variant, "Ksu", pushTask, zipFileNameProvider, "/data/adb/ksud module install") - val installMagiskTask = task("installMagisk$variantCapped") { + val installMagiskTask = tasks.register("installMagisk$variantCapped") { group = "module" dependsOn(pushTask) - commandLine("adb", "shell", "su", "-M", "-c", "magisk --install-module /data/local/tmp/$zipFileName") - } - - task("installAPatchAndReboot$variantCapped") { - group = "module" - dependsOn(installAPatchTask) - commandLine("adb", "reboot") - } - - task("installKsuAndReboot$variantCapped") { - group = "module" - dependsOn(installKsuTask) - commandLine("adb", "reboot") + doFirst { + val zipFileName = zipFileNameProvider.get() + val installCommand = "su -c 'magisk --install-module /data/local/tmp/$zipFileName'" + commandLine("adb", "shell", installCommand) + } } - task("installMagiskAndReboot$variantCapped") { - group = "module" - dependsOn(installMagiskTask) - commandLine("adb", "reboot") - } + registerRebootTask(variant, "APatch", installAPatchTask) + registerRebootTask(variant, "Ksu", installKsuTask) + registerRebootTask(variant, "Magisk", installMagiskTask) } diff --git a/zygiskd/build.gradle.kts b/zygiskd/build.gradle.kts index f0c209b..bb61146 100644 --- a/zygiskd/build.gradle.kts +++ b/zygiskd/build.gradle.kts @@ -1,3 +1,6 @@ +import org.gradle.process.ExecOperations +import javax.inject.Inject + plugins { alias(libs.plugins.agp.lib) alias(libs.plugins.rust.android) @@ -11,9 +14,9 @@ val verCode: Int by rootProject.extra val verName: String by rootProject.extra val commitHash: String by rootProject.extra -android.buildFeatures { - androidResources = false - buildConfig = false +android { + androidResources.enable = false + buildFeatures.buildConfig = false } cargo { @@ -22,7 +25,7 @@ cargo { targetIncludes = arrayOf("zygiskd") targets = listOf("arm64", "arm", "x86", "x86_64") targetDirectory = "build/intermediates/rust" - val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") } + val isDebug = gradle.startParameter.taskNames.any { it.lowercase().contains("debug") } profile = if (isDebug) "debug" else "release" exec = { spec, _ -> spec.environment("ANDROID_NDK_HOME", android.ndkDirectory.path) @@ -34,35 +37,50 @@ cargo { } } -afterEvaluate { - task("buildAndStrip") { - dependsOn(":zygiskd:cargoBuild") - val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") } - doLast { - val dir = File(buildDir, "rustJniLibs/android") - val prebuilt = File(android.ndkDirectory, "toolchains/llvm/prebuilt").listFiles()!!.first() - val binDir = File(prebuilt, "bin") - val symbolDir = File(buildDir, "symbols/${if (isDebug) "debug" else "release"}") - symbolDir.mkdirs() - val suffix = if (prebuilt.name.contains("windows")) ".exe" else "" - val strip = File(binDir, "llvm-strip$suffix") - val objcopy = File(binDir, "llvm-objcopy$suffix") - dir.listFiles()!!.forEach { - if (!it.isDirectory) return@forEach - val symbolPath = File(symbolDir, "${it.name}/zygiskd.debug") - symbolPath.parentFile.mkdirs() - exec { - workingDir = it - commandLine(objcopy, "--only-keep-debug", "zygiskd", symbolPath) - } - exec { - workingDir = it - commandLine(strip, "--strip-all", "zygiskd") - } - exec { - workingDir = it - commandLine(objcopy, "--add-gnu-debuglink", symbolPath, "zygiskd") - } +// An interface to safely inject the ExecOperations service for use in doLast {}. +interface ExecOperationsService { + @get:Inject + val execOperations: ExecOperations +} + +tasks.register("buildAndStrip") { + dependsOn(":zygiskd:cargoBuild") + + // Create a holder for the exec service during the configuration phase. + val serviceHolder = project.objects.newInstance() + + doLast { + val isDebug = gradle.startParameter.taskNames.any { it.lowercase().contains("debug") } + + val buildDir = layout.buildDirectory.get().asFile + val dir = buildDir.resolve("rustJniLibs/android") + val prebuilt = android.ndkDirectory.resolve("toolchains/llvm/prebuilt").listFiles()!!.first() + val binDir = prebuilt.resolve("bin") + val symbolDir = buildDir.resolve("symbols/${if (isDebug) "debug" else "release"}") + symbolDir.mkdirs() + val isWindows = System.getProperty("os.name").lowercase().contains("win") + val suffix = if (isWindows) ".exe" else "" + val strip = binDir.resolve("llvm-strip$suffix") + val objcopy = binDir.resolve("llvm-objcopy$suffix") + + // Get the exec service from the holder during the execution phase. + val execOps = serviceHolder.execOperations + + dir.listFiles()!!.forEach { + if (!it.isDirectory) return@forEach + val symbolPath = symbolDir.resolve("${it.name}/zygiskd.debug") + symbolPath.parentFile.mkdirs() + execOps.exec { + workingDir = it + commandLine(objcopy, "--only-keep-debug", "zygiskd", symbolPath) + } + execOps.exec { + workingDir = it + commandLine(strip, "--strip-all", "zygiskd") + } + execOps.exec { + workingDir = it + commandLine(objcopy, "--add-gnu-debuglink", symbolPath, "zygiskd") } } }