build: reorganize and reconfigure modules to resolve dependency resolution errors

Gradle, for some reason, does not the path of modules to identify them uniquely. Instead, the 'build id' is used to do so, which consists of the maven group and artifact ID. So, even if two modules are located in separate directories, but have the name group and artifact ID, including both of them in other modules may result in dependency resolution errors.
This commit is contained in:
Akash Yadav
2024-05-29 13:11:44 +05:30
parent c0c185cfb7
commit 1efd6a04d5
2457 changed files with 1069 additions and 709 deletions
@@ -78,7 +78,10 @@ class AndroidIDEAssetsPlugin : Plugin<Project> {
// Tooling API JAR copier
val copyToolingApiJar = tasks.register("copy${variantNameCapitalized}ToolingApiJar",
AddFileToAssetsTask::class.java) {
val toolingApi = rootProject.findProject(":subprojects:tooling-api-impl")!!
val implPath = ":tooling:impl"
val toolingApi = checkNotNull(rootProject.findProject(implPath)) {
"Cannot find the Tooling Impl module with project path: '$implPath'"
}
dependsOn(toolingApi.tasks.getByName("copyJar"))
val toolingApiJar = toolingApi.layout.buildDirectory.file("libs/tooling-api-all.jar")
@@ -0,0 +1,30 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* Marker plugin that is applied to the core application module project.
*
* @author Akash Yadav
*/
class AndroidIDECoreAppPlugin : Plugin<Project> {
override fun apply(target: Project) {}
}
@@ -30,43 +30,41 @@ import org.gradle.api.Project
*/
class AndroidIDEPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.run {
if (project.path == rootProject.path) {
throw GradleException("Cannot apply ${AndroidIDEPlugin::class.simpleName} to root project")
}
override fun apply(target: Project) = target.run {
if (project.path == rootProject.path) {
throw GradleException("Cannot apply ${AndroidIDEPlugin::class.simpleName} to root project")
}
if (!project.buildFile.exists() || !project.buildFile.isFile) {
return@run
}
if (!project.buildFile.exists() || !project.buildFile.isFile) {
return@run
}
val isAndroidModule = plugins.hasPlugin("com.android.application") ||
plugins.hasPlugin("com.android.library")
val isAndroidModule = plugins.hasPlugin("com.android.application") ||
plugins.hasPlugin("com.android.library")
if (isAndroidModule && !isFDroidBuild) {
// setup signing configuration
plugins.apply(SigningConfigPlugin::class.java)
}
if (isAndroidModule && !isFDroidBuild) {
// setup signing configuration
plugins.apply(SigningConfigPlugin::class.java)
}
if (isFDroidBuild && project.path == ":app") {
val baseExtension = extensions.getByType(BaseExtension::class.java)
logger.warn("Building for F-Droid with configuration:")
logger.warn("applicationId = ${baseExtension.defaultConfig.applicationId}")
logger.warn("versionName = ${baseExtension.defaultConfig.versionName}")
logger.warn("versionCode = ${baseExtension.defaultConfig.versionCode}")
logger.warn("--- x --- x ---")
}
if (isFDroidBuild && project.plugins.hasPlugin(AndroidIDECoreAppPlugin::class.java)) {
val baseExtension = extensions.getByType(BaseExtension::class.java)
logger.warn("Building for F-Droid with configuration:")
logger.warn("applicationId = ${baseExtension.defaultConfig.applicationId}")
logger.warn("versionName = ${baseExtension.defaultConfig.versionName}")
logger.warn("versionCode = ${baseExtension.defaultConfig.versionCode}")
logger.warn("--- x --- x ---")
}
val taskName = when {
isAndroidModule -> "testDebugUnitTest"
else -> "test"
}
val taskName = when {
isAndroidModule -> "testDebugUnitTest"
else -> "test"
}
logger.info("${project.path} will run task '$taskName' for tests in CI")
logger.info("${project.path} will run task '$taskName' for tests in CI")
project.tasks.create("runTestsInCI") {
dependsOn(taskName)
}
project.tasks.create("runTestsInCI") {
dependsOn(taskName)
}
}
}
@@ -0,0 +1,42 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.plugins
import com.itsaky.androidide.build.config.BuildConfig
import com.itsaky.androidide.plugins.exts.GroupConfigPluginExtension.Companion.groupConfigExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* @author Akash Yadav
*/
class GroupConfigPlugin : Plugin<Project> {
override fun apply(target: Project) = target.run {
val extension = groupConfigExtension()
extension.groupIdSuffix.convention("")
afterEvaluate {
val suffix = extension.groupIdSuffix.get().let {
if (it.isNotEmpty()) ".${it}" else ""
}
project.group = "${BuildConfig.packageName}${suffix}"
logger.info("Maven group ID set to : ${project.group}")
}
}
}
@@ -0,0 +1,30 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* Marker plugin to indicate that the desugaring plugin should not be applied to the project.
*
* @author Akash Yadav
*/
class NoDesugarPlugin : Plugin<Project> {
override fun apply(target: Project) {}
}
@@ -27,6 +27,8 @@ import com.itsaky.androidide.build.config.BuildConfig
import com.itsaky.androidide.build.config.FDroidConfig
import com.itsaky.androidide.build.config.isFDroidBuild
import com.itsaky.androidide.build.config.projectVersionCode
import com.itsaky.androidide.plugins.AndroidIDECoreAppPlugin
import com.itsaky.androidide.plugins.NoDesugarPlugin
import com.itsaky.androidide.plugins.util.SdkUtils.getAndroidJar
import org.gradle.api.Project
import org.gradle.api.artifacts.MinimalExternalModuleDependency
@@ -42,19 +44,20 @@ import org.gradle.api.provider.Provider
* flavor, the version code will be `100 * 270 + 1` i.e. `27001`
*/
internal val flavorsAbis = mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86_64" to 3)
private val disableCoreLibDesugaringForModules = arrayOf(":logsender", ":logger")
fun Project.configureAndroidModule(
coreLibDesugDep: Provider<MinimalExternalModuleDependency>
) {
val isAppModule = plugins.hasPlugin("com.android.application")
assert(
plugins.hasPlugin("com.android.application") || plugins.hasPlugin("com.android.library")) {
isAppModule || plugins.hasPlugin("com.android.library")
) {
"${javaClass.simpleName} can only be applied to Android projects"
}
val androidJar = extensions.getByType(AndroidComponentsExtension::class.java)
.getAndroidJar(assertExists = true)
val frameworkStubsJar = findProject(":subprojects:framework-stubs")!!.file("libs/android.jar")
val frameworkStubsJar = findProject(":utilities:framework-stubs")!!.file("libs/android.jar")
.also { it.parentFile.mkdirs() }
if (!(frameworkStubsJar.exists() && frameworkStubsJar.isFile)) {
@@ -116,20 +119,13 @@ fun Project.configureAndroidModule(
configureCoreLibDesugaring(this, coreLibDesugDep)
if (":app" == project.path) {
if (project.plugins.hasPlugin(AndroidIDECoreAppPlugin::class.java)) {
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
}
flavorsAbis.forEach { (abi, _) ->
// the common defaultConfig, not the flavor-specific
defaultConfig.buildConfigField("String",
"ABI_${abi.replace('-', '_').uppercase()}",
"\"${abi}\"")
}
splits {
abi {
reset()
@@ -149,7 +145,8 @@ fun Project.configureAndroidModule(
// version code increment
val verCodeIncr = flavorsAbis[output.getFilter(
FilterConfiguration.FilterType.ABI)?.identifier]
FilterConfiguration.FilterType.ABI
)?.identifier]
?: throw UnsupportedOperationException("Universal APKs are not supported!")
output.versionCode.set(100 * projectVersionCode + verCodeIncr)
@@ -167,7 +164,11 @@ fun Project.configureAndroidModule(
buildTypes.getByName("debug") { isMinifyEnabled = false }
buildTypes.getByName("release") {
isMinifyEnabled = true
// from AGP 8.4.0 onwards, there are some behavioral changes in R8
// enabling R8 on library projects results in missing class errors
// see https://issuetracker.google.com/issues/338411137#comment11
isMinifyEnabled = isAppModule
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
@@ -177,7 +178,6 @@ fun Project.configureAndroidModule(
// the build are faster for this build type as compared to 'release'
buildTypes.register("dev") {
initWith(buildTypes.getByName("release"))
isMinifyEnabled = false
}
@@ -188,9 +188,11 @@ fun Project.configureAndroidModule(
}
}
private fun Project.configureCoreLibDesugaring(baseExtension: BaseExtension,
coreLibDesugDep: Provider<MinimalExternalModuleDependency>) {
val coreLibDesugaringEnabled = project.path !in disableCoreLibDesugaringForModules
private fun Project.configureCoreLibDesugaring(
baseExtension: BaseExtension,
coreLibDesugDep: Provider<MinimalExternalModuleDependency>
) {
val coreLibDesugaringEnabled = !project.plugins.hasPlugin(NoDesugarPlugin::class.java)
baseExtension.compileOptions.isCoreLibraryDesugaringEnabled = coreLibDesugaringEnabled
@@ -35,7 +35,7 @@ import org.gradle.kotlin.dsl.withType
import com.itsaky.androidide.build.config.publishingVersion
import java.io.File
private val projectsRequiringMavenLocalForTests = arrayOf(":gradle-plugin")
private val projectsRequiringMavenLocalForTests = arrayOf(":tooling:plugin")
private val mavenLocalRepos = hashMapOf<String, String>()
@Suppress("UnstableApiUsage")
@@ -45,6 +45,11 @@ fun Project.configureMavenPublish() {
}
gradle.projectsEvaluated {
for (path in projectsRequiringMavenLocalForTests) {
checkNotNull(rootProject.findProject(path)) {
"Unable to configure maven local for project '$path' (project cannot be found)."
}
}
rootProject.subprojects {
if (project.path in projectsRequiringMavenLocalForTests) {
tasks.withType<Test> {
@@ -0,0 +1,47 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.plugins.exts
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.kotlin.dsl.create
/**
* @author Akash Yadav
*/
abstract class GroupConfigPluginExtension {
/**
* The suffix for the maven group ID for the module.
*
* Example:
* If the [groupIdSuffix] is "core", then the maven group ID will be
* `${BuildConfig.packageName}.core`.
*/
abstract val groupIdSuffix: Property<String>
companion object {
/**
* Create the [GroupConfigPluginExtension] for the project.
*/
fun Project.groupConfigExtension(): GroupConfigPluginExtension {
return extensions.create<GroupConfigPluginExtension>("groupConfig")
}
}
}