diff --git a/composite-builds/build-logic/common/src/main/java/com/itsaky/androidide/build/config/ShellUtils.kt b/composite-builds/build-logic/common/src/main/java/com/itsaky/androidide/build/config/ShellUtils.kt new file mode 100644 index 0000000000..42600c56f6 --- /dev/null +++ b/composite-builds/build-logic/common/src/main/java/com/itsaky/androidide/build/config/ShellUtils.kt @@ -0,0 +1,38 @@ +package com.itsaky.androidide.build.config + +import java.io.File + +/** + * Utilities for running shell commands. + * + * @author Akash Yadav + */ +object ShellUtils { + fun which(cmd: String): String? = shC("which '$cmd'") + + fun shC( + cmd: String, + workDir: File? = null, + redirectErrorStream: Boolean = true, + ): String? { + val proc = + ProcessBuilder("sh", "-c", cmd).run { + if (workDir != null) { + directory(workDir) + } + + redirectErrorStream(redirectErrorStream) + start() + } + + val exitCode = proc.waitFor() + if (exitCode != 0) { + return null + } + + return proc.inputStream + .bufferedReader() + .readText() + .trim() + } +} diff --git a/composite-builds/build-logic/plugins/build.gradle.kts b/composite-builds/build-logic/plugins/build.gradle.kts index 885fba3bcb..409aac8403 100644 --- a/composite-builds/build-logic/plugins/build.gradle.kts +++ b/composite-builds/build-logic/plugins/build.gradle.kts @@ -18,77 +18,82 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform plugins { - `kotlin-dsl` + `kotlin-dsl` } repositories { - google() - gradlePluginPortal() - mavenCentral() + google() + gradlePluginPortal() + mavenCentral() } dependencies { - implementation(libs.composite.constants) - implementation(projects.buildLogic.common) - implementation(projects.buildLogic.desugaring) - implementation(projects.buildLogic.propertiesParser) + implementation(libs.composite.constants) + implementation(projects.buildLogic.common) + implementation(projects.buildLogic.desugaring) + implementation(projects.buildLogic.propertiesParser) - implementation("com.android.tools.build:gradle:${libs.versions.agp.asProvider().get()}") - implementation( - "org.jetbrains.kotlin:kotlin-gradle-plugin:${ - libs.versions.kotlin.asProvider().get() - }" - ) - implementation(libs.maven.publish) + implementation("com.android.tools.build:gradle:${libs.versions.agp.asProvider().get()}") + implementation( + "org.jetbrains.kotlin:kotlin-gradle-plugin:${ + libs.versions.kotlin.asProvider().get() + }", + ) + implementation(libs.maven.publish) - implementation(libs.common.jkotlin) - implementation(libs.common.antlr4) - implementation(libs.google.gson) - implementation(libs.google.java.format) + implementation(libs.common.jkotlin) + implementation(libs.common.antlr4) + implementation(libs.google.gson) + implementation(libs.google.java.format) + implementation(libs.google.protobuf.gradle) - val arch = DefaultNativePlatform.getCurrentArchitecture() - val brotli4jNatives = DefaultNativePlatform.getCurrentOperatingSystem().let { os -> - when { - os.isMacOsX -> when { - arch.isArm64 -> libs.brotli4j.osx.aarch64 - arch.isAmd64 -> libs.brotli4j.osx.x64 - else -> throw IllegalStateException("Unsupported OSX architecture: $arch") - } - os.isWindows -> when { - arch.isArm64 -> libs.brotli4j.windows.aarch64 - arch.isAmd64 -> libs.brotli4j.windows.x64 - else -> throw IllegalStateException("Unsupported Windows architecture: $arch") - } - os.isLinux -> when { - arch.isArm64 -> libs.brotli4j.linux.aarch64 - arch.isAmd64 -> libs.brotli4j.linux.x64 - else -> throw IllegalStateException("Unsupported Linux architecture: $arch") - } - else -> throw IllegalStateException("Unsupported OS: $os") - } - } + val arch = DefaultNativePlatform.getCurrentArchitecture() + val brotli4jNatives = + DefaultNativePlatform.getCurrentOperatingSystem().let { os -> + when { + os.isMacOsX -> + when { + arch.isArm64 -> libs.brotli4j.osx.aarch64 + arch.isAmd64 -> libs.brotli4j.osx.x64 + else -> throw IllegalStateException("Unsupported OSX architecture: $arch") + } + os.isWindows -> + when { + arch.isArm64 -> libs.brotli4j.windows.aarch64 + arch.isAmd64 -> libs.brotli4j.windows.x64 + else -> throw IllegalStateException("Unsupported Windows architecture: $arch") + } + os.isLinux -> + when { + arch.isArm64 -> libs.brotli4j.linux.aarch64 + arch.isAmd64 -> libs.brotli4j.linux.x64 + else -> throw IllegalStateException("Unsupported Linux architecture: $arch") + } + else -> throw IllegalStateException("Unsupported OS: $os") + } + } - implementation(libs.brotli4j) - runtimeOnly(brotli4jNatives) + implementation(libs.brotli4j) + runtimeOnly(brotli4jNatives) } gradlePlugin { - plugins { - create("com.itsaky.androidide.build") { - id = "com.itsaky.androidide.build" - implementationClass = "com.itsaky.androidide.plugins.AndroidIDEPlugin" - } - create("com.itsaky.androidide.build.propsparser") { - id = "com.itsaky.androidide.build.propsparser" - implementationClass = "com.itsaky.androidide.plugins.PropertiesParserPlugin" - } - create("com.itsaky.androidide.build.lexergenerator") { - id = "com.itsaky.androidide.build.lexergenerator" - implementationClass = "com.itsaky.androidide.plugins.LexerGeneratorPlugin" - } - create("com.itsaky.androidide.build.external-assets") { - id = "com.itsaky.androidide.build.external-assets" - implementationClass = "com.itsaky.androidide.plugins.ExternalAssetsPlugin" - } - } + plugins { + create("com.itsaky.androidide.build") { + id = "com.itsaky.androidide.build" + implementationClass = "com.itsaky.androidide.plugins.AndroidIDEPlugin" + } + create("com.itsaky.androidide.build.propsparser") { + id = "com.itsaky.androidide.build.propsparser" + implementationClass = "com.itsaky.androidide.plugins.PropertiesParserPlugin" + } + create("com.itsaky.androidide.build.lexergenerator") { + id = "com.itsaky.androidide.build.lexergenerator" + implementationClass = "com.itsaky.androidide.plugins.LexerGeneratorPlugin" + } + create("com.itsaky.androidide.build.external-assets") { + id = "com.itsaky.androidide.build.external-assets" + implementationClass = "com.itsaky.androidide.plugins.ExternalAssetsPlugin" + } + } } diff --git a/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/SigningConfigPlugin.kt b/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/SigningConfigPlugin.kt index 97a6db533d..922d10e07a 100644 --- a/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/SigningConfigPlugin.kt +++ b/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/SigningConfigPlugin.kt @@ -17,16 +17,16 @@ package com.itsaky.androidide.plugins +import com.android.build.gradle.BaseExtension import com.itsaky.androidide.build.config.KEY_ALIAS import com.itsaky.androidide.build.config.KEY_PASS import com.itsaky.androidide.build.config.KEY_STORE_PASS -import com.android.build.gradle.BaseExtension +import com.itsaky.androidide.build.config.isFDroidBuild +import com.itsaky.androidide.build.config.signingKey import com.itsaky.androidide.plugins.util.SigningKeyUtils.downloadSigningKey import com.itsaky.androidide.plugins.util.SigningKeyUtils.getEnvOrProp -import com.itsaky.androidide.build.config.isFDroidBuild import org.gradle.api.Plugin import org.gradle.api.Project -import com.itsaky.androidide.build.config.signingKey /** * Configures the signing keys to application modules. @@ -34,46 +34,53 @@ import com.itsaky.androidide.build.config.signingKey * @author Akash Yadav */ class SigningConfigPlugin : Plugin { + companion object { + private var warned = false + } - override fun apply(target: Project) { - target.run { - - if (isFDroidBuild) { - logger.warn("!!! Do not apply ${javaClass.simpleName} when building or F-Droid.") - return - } + override fun apply(target: Project) { + target.run { + if (isFDroidBuild) { + logger.warn("!!! Do not apply ${javaClass.simpleName} when building or F-Droid.") + return + } - // Download the signing key - downloadSigningKey() + // Download the signing key + downloadSigningKey() - // Create and apply the signing config - extensions.getByType(BaseExtension::class.java).let { extension -> - // Keystore credentials - val alias = getEnvOrProp(KEY_ALIAS) - val storePass = getEnvOrProp(KEY_STORE_PASS) - val keyPass = getEnvOrProp(KEY_PASS) + // Create and apply the signing config + extensions.getByType(BaseExtension::class.java).let { extension -> + // Keystore credentials + val alias = getEnvOrProp(KEY_ALIAS) + val storePass = getEnvOrProp(KEY_STORE_PASS) + val keyPass = getEnvOrProp(KEY_PASS) - val signingKey = signingKey.get().asFile + val signingKey = signingKey.get().asFile - if (alias != null && storePass != null && keyPass != null && signingKey.exists()) { - val config = extension.signingConfigs.create("common") { - storeFile = signingKey - keyAlias = alias - storePassword = storePass - keyPassword = keyPass - } + if (alias != null && storePass != null && keyPass != null && signingKey.exists()) { + val config = + extension.signingConfigs.create("common") { + storeFile = signingKey + keyAlias = alias + storePassword = storePass + keyPassword = keyPass + } - extension.buildTypes.forEach { buildType -> - buildType.signingConfig = config - } - } else { - logger.warn( - "Signing info not configured. keystoreFile=$signingKey[exists=${signingKey.exists()}]" - ) + extension.buildTypes.forEach { buildType -> + buildType.signingConfig = config + } + } else { + if (!warned) { + warned = true + logger.warn( + "Signing info not configured. " + + "keystoreFile=$signingKey[exists=${signingKey.exists()}]", + ) + } - null - } - } - } - } -} \ No newline at end of file + null + } + } + } + } +} diff --git a/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/conf/ProtocConf.kt b/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/conf/ProtocConf.kt new file mode 100644 index 0000000000..8f63feed51 --- /dev/null +++ b/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/conf/ProtocConf.kt @@ -0,0 +1,77 @@ +package com.itsaky.androidide.plugins.conf + +import com.google.protobuf.gradle.ProtobufExtension +import com.itsaky.androidide.build.config.BuildConfig +import com.itsaky.androidide.build.config.ShellUtils +import org.gradle.api.Project +import org.gradle.api.provider.Provider +import java.io.File + +fun Project.configureProtoc( + protobuf: ProtobufExtension, + protocVersion: Provider, +) { + if (configureOnDeviceProtoc(protobuf)) return + + configureProtocArtifact(protobuf, protocVersion) +} + +private fun Project.configureOnDeviceProtoc(protobuf: ProtobufExtension): Boolean { + if (isTermuxAppPackageNameSet() || isTermuxJdk()) { + // this is an on-device build + // find path to the protoc binary + + val protocPath = ShellUtils.which("protoc") + if (protocPath == null) { + logger.warn( + "Unable to get path to protoc binary for on-device build." + + " Falling back to using maven artifact, which is likely to fail.", + ) + return false + } + + val protoc = File(protocPath) + if (!protoc.exists()) { + logger.warn( + "protoc path $protocPath does not exist." + + " Falling back to using maven artifact", + ) + return false + } + + if (!protoc.canExecute()) { + logger.warn( + "protoc path $protocPath is not executable." + + " Falling back to using maven artifact", + ) + return false + } + + logger.lifecycle("Using protoc from $protocPath") + protobuf.protoc { + path = protoc.absolutePath + } + + return true + } + + return false +} + +fun Project.configureProtocArtifact( + protobuf: ProtobufExtension, + protocVersion: Provider, +) { + val protocModule = "com.google.protobuf:protoc:${protocVersion.get()}" + logger.lifecycle("Using protoc module: $protocModule") + + protobuf.protoc { + artifact = protocModule + } +} + +fun isTermuxAppPackageNameSet() = System.getenv("TERMUX_APP__PACKAGE_NAME") == BuildConfig.PACKAGE_NAME + +fun isTermuxJdk() = + System.getProperty("java.vendor") == "Termux" || + System.getProperty("java.vm.vendor") == "Termux" diff --git a/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/util/SigningKeyUtils.kt b/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/util/SigningKeyUtils.kt index 615a208472..f9d44f16e6 100644 --- a/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/util/SigningKeyUtils.kt +++ b/composite-builds/build-logic/plugins/src/main/java/com/itsaky/androidide/plugins/util/SigningKeyUtils.kt @@ -21,8 +21,8 @@ import com.itsaky.androidide.build.config.AUTH_PASS import com.itsaky.androidide.build.config.AUTH_USER import com.itsaky.androidide.build.config.KEY_BIN import com.itsaky.androidide.build.config.KEY_URL -import org.gradle.api.Project import com.itsaky.androidide.build.config.signingKey +import org.gradle.api.Project import org.gradle.api.invocation.Gradle import java.util.Base64 @@ -32,59 +32,68 @@ import java.util.Base64 * @author Akash Yadav */ object SigningKeyUtils { - - private val _warned = mutableMapOf() - - @JvmStatic - fun Project.downloadSigningKey() { - val signingKey = signingKey.get().asFile - if (signingKey.exists()) { - logger.info("Skipping download as ${signingKey.name} file already exists.") - return - } + private val warned = mutableMapOf() - signingKey.parentFile.mkdirs() + @JvmStatic + fun Project.downloadSigningKey() { + val signingKey = signingKey.get().asFile + if (signingKey.exists()) { + logger.info("Skipping download as ${signingKey.name} file already exists.") + return + } - getEnvOrProp(key = KEY_BIN, warn = false)?.also { bin -> - val contents = Base64.getDecoder().decode(bin) - signingKey.writeBytes(contents) - return - } + signingKey.parentFile.mkdirs() - // URL to download the signing key - val url = getEnvOrProp(KEY_URL) ?: return + getEnvOrProp(key = KEY_BIN, warn = false)?.also { bin -> + val contents = Base64.getDecoder().decode(bin) + signingKey.writeBytes(contents) + return + } - // Username and password required to download the keystore - val user = getEnvOrProp(AUTH_USER) ?: return - val pass = getEnvOrProp(AUTH_PASS) ?: return + // URL to download the signing key + val url = getEnvOrProp(KEY_URL) ?: return - logger.info("Downloading signing key...") - val result = exec { - var rootGradle: Gradle? = gradle - while (rootGradle?.parent != null) { - rootGradle = rootGradle.parent - } + // Username and password required to download the keystore + val user = getEnvOrProp(AUTH_USER) ?: return + val pass = getEnvOrProp(AUTH_PASS) ?: return - workingDir(rootGradle!!.rootProject.projectDir) - commandLine("bash", "./scripts/download_key.sh", signingKey.absolutePath, url, user, pass) - } + logger.info("Downloading signing key...") + val result = + exec { + var rootGradle: Gradle? = gradle + while (rootGradle?.parent != null) { + rootGradle = rootGradle.parent + } - result.assertNormalExitValue() - } + workingDir(rootGradle!!.rootProject.projectDir) + commandLine( + "bash", + "./scripts/download_key.sh", + signingKey.absolutePath, + url, + user, + pass, + ) + } - internal fun Project.getEnvOrProp(key: String, warn: Boolean = true): String? { - var value: String? = System.getenv(key) - if (value.isNullOrBlank()) { - value = project.properties[key] as? String? - } + result.assertNormalExitValue() + } - if (value.isNullOrBlank()) { - if (warn && _warned.putIfAbsent(key, true) != true) { - logger.warn("$key is not set. Debug key will be used to sign the APK") - } - return null - } - return value - } + internal fun Project.getEnvOrProp( + key: String, + warn: Boolean = true, + ): String? { + var value: String? = System.getenv(key) + if (value.isNullOrBlank()) { + value = project.properties[key] as? String? + } -} \ No newline at end of file + if (value.isNullOrBlank()) { + if (warn && warned.putIfAbsent(key, true) != true) { + logger.warn("$key is not set. Debug key will be used to sign the APK") + } + return null + } + return value + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6880805b62..6ec710e12e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -241,6 +241,7 @@ google-auto-service-annotations = { module = "com.google.auto.service:auto-servi google-auto-service = { module = "com.google.auto.service:auto-service", version = "1.1.1" } google-protobuf-java = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" } google-protobuf-kotlin = { module = "com.google.protobuf:protobuf-kotlin-lite", version.ref = "protobuf" } +google-protobuf-gradle = { module = "com.google.protobuf:protobuf-gradle-plugin", version.ref = "protobuf-plugin" } google-java-format = { module = "com.google.googlejavaformat:google-java-format", version = "1.20.0" } google-flexbox = { module = "com.google.android.flexbox:flexbox", version = "3.0.0" } diff --git a/subprojects/aapt2-proto/build.gradle.kts b/subprojects/aapt2-proto/build.gradle.kts index c68109cb5f..43220b8708 100644 --- a/subprojects/aapt2-proto/build.gradle.kts +++ b/subprojects/aapt2-proto/build.gradle.kts @@ -1,12 +1,13 @@ +import com.itsaky.androidide.plugins.conf.configureProtoc + plugins { id("java-library") alias(libs.plugins.google.protobuf) } +configureProtoc(protobuf = protobuf, protocVersion = libs.versions.protobuf.asProvider()) + protobuf { - protoc { - artifact = "com.google.protobuf:protoc:${libs.versions.protobuf.asProvider().get()}" - } generateProtoTasks { all().forEach { task -> task.builtins { diff --git a/subprojects/project-models/build.gradle.kts b/subprojects/project-models/build.gradle.kts index ec7600e9b8..2d7ea2bc5a 100644 --- a/subprojects/project-models/build.gradle.kts +++ b/subprojects/project-models/build.gradle.kts @@ -1,4 +1,5 @@ import com.google.protobuf.gradle.id +import com.itsaky.androidide.plugins.conf.configureProtoc plugins { id("java-library") @@ -6,10 +7,9 @@ plugins { alias(libs.plugins.google.protobuf) } +configureProtoc(protobuf = protobuf, protocVersion = libs.versions.protobuf.asProvider()) + protobuf { - protoc { - artifact = "com.google.protobuf:protoc:${libs.versions.protobuf.asProvider().get()}" - } plugins { id("kotlin-ext") { artifact = "dev.hsbrysk:protoc-gen-kotlin-ext:${libs.versions.protoc.gen.kotlin.ext.get()}:jdk8@jar"