Skip to content

Commit adee0d6

Browse files
committed
fix(tooling-api): fetch variant-specific application IDs in application projects (AndroidIDEOfficial#1387)
1 parent 348dfee commit adee0d6

27 files changed

Lines changed: 998 additions & 313 deletions

File tree

.idea/compiler.xml

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ plugins {
2828
alias(libs.plugins.android.application) apply false
2929
alias(libs.plugins.android.library) apply false
3030
alias(libs.plugins.kotlin.android) apply false
31+
alias(libs.plugins.kotlin.jvm) apply false
3132
alias(libs.plugins.maven.publish) apply false
3233
alias(libs.plugins.gradle.publish) apply false
3334
}

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,6 @@ maven-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version
148148
android-application = { id = "com.android.application", version.ref = "agp" }
149149
android-library = { id = "com.android.library", version.ref = "agp" }
150150
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
151+
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
151152
maven-publish = { id = "com.vanniktech.maven.publish.base", version.ref = "maven-publish-plugin" }
152153
gradle-publish = { id = "com.gradle.plugin-publish", version = "1.2.1" }

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ include(
112112
":termux:termux-shared",
113113
":termux:termux-view",
114114
":testing:android",
115+
":testing:common",
115116
":testing:lsp",
116117
":testing:tooling",
117118
":testing:unit",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* This file is part of AndroidIDE.
3+
*
4+
* AndroidIDE is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* AndroidIDE is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.itsaky.androidide.utils
19+
20+
import java.util.Locale
21+
22+
/**
23+
* Capitalizes the string.
24+
*/
25+
@JvmOverloads
26+
fun String.capitalizeString(locale: Locale = Locale.getDefault()): String {
27+
return replaceFirstChar { char ->
28+
if (char.isLowerCase()) char.titlecase(locale) else char.toString()
29+
}
30+
}

subprojects/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,11 @@ import com.itsaky.androidide.projects.IProjectManager
2626
import com.itsaky.androidide.projects.ProjectManagerImpl
2727
import com.itsaky.androidide.projects.builder.BuildService
2828
import com.itsaky.androidide.tooling.api.IAndroidProject
29-
import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams
30-
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
29+
import com.itsaky.androidide.testing.tooling.ToolingApiTestLauncher
3130
import com.itsaky.androidide.utils.FileProvider
32-
import com.itsaky.androidide.utils.SourceClassTrie.SourceNode
3331
import kotlinx.coroutines.runBlocking
3432
import java.io.File
3533
import java.nio.file.Files
36-
import kotlin.io.path.pathString
3734
import kotlin.io.path.writeText
3835
import org.junit.Test
3936
import org.junit.runner.RunWith

subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/AndroidProjectImpl.kt

Lines changed: 97 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717

1818
package com.itsaky.androidide.tooling.impl.internal
1919

20+
import com.android.builder.model.v2.dsl.BuildType
2021
import com.android.builder.model.v2.ide.AndroidArtifact
2122
import com.android.builder.model.v2.ide.GraphItem
2223
import com.android.builder.model.v2.ide.Library
24+
import com.android.builder.model.v2.ide.ProjectType
2325
import com.android.builder.model.v2.ide.Variant
26+
import com.android.builder.model.v2.models.AndroidDsl
2427
import com.android.builder.model.v2.models.AndroidProject
2528
import com.android.builder.model.v2.models.BasicAndroidProject
2629
import com.android.builder.model.v2.models.VariantDependencies
@@ -40,9 +43,9 @@ import com.itsaky.androidide.tooling.api.models.ProjectMetadata
4043
import com.itsaky.androidide.tooling.api.models.params.StringParameter
4144
import com.itsaky.androidide.tooling.api.util.AndroidModulePropertyCopier
4245
import com.itsaky.androidide.tooling.api.util.AndroidModulePropertyCopier.copy
43-
import com.itsaky.androidide.tooling.api.util.compareSemanticVersions
4446
import com.itsaky.androidide.tooling.api.util.extractPackageName
4547
import com.itsaky.androidide.utils.AndroidPluginVersion
48+
import com.itsaky.androidide.utils.capitalizeString
4649
import org.gradle.tooling.model.GradleProject
4750
import java.io.File
4851
import java.io.Serializable
@@ -57,7 +60,8 @@ internal class AndroidProjectImpl(
5760
private val basicAndroidProject: BasicAndroidProject,
5861
private val androidProject: AndroidProject,
5962
private val variantDependencies: VariantDependencies,
60-
private val versions: Versions
63+
private val versions: Versions,
64+
private val androidDsl: AndroidDsl,
6165
) : GradleProjectImpl(gradleProject), IAndroidProject, Serializable {
6266

6367
private val serialVersionUID = 1L
@@ -84,19 +88,17 @@ internal class AndroidProjectImpl(
8488
}
8589
}
8690

87-
private fun AndroidArtifact.toMetadata(name: String): AndroidArtifactMetadata {
88-
return AndroidArtifactMetadata(
89-
name = name,
90-
resGenTaskName = resGenTaskName,
91+
private fun AndroidArtifact.toMetadata(variantName: String): AndroidArtifactMetadata {
92+
return AndroidArtifactMetadata(name = variantName,
93+
applicationId = computeApplicationId(variantName), resGenTaskName = resGenTaskName,
9194
assembleTaskOutputListingFile = assembleTaskOutputListingFile,
9295
generatedResourceFolders = generatedResourceFolders,
9396
generatedSourceFolders = generatedSourceFolders, maxSdkVersion = maxSdkVersion,
9497
minSdkVersion = minSdkVersion.apiLevel, signingConfigName = signingConfigName,
9598
sourceGenTaskName = sourceGenTaskName, assembleTaskName = assembleTaskName,
9699
classJars = classesFolders.filter { it.name.endsWith(".jar") },
97100
compileTaskName = compileTaskName,
98-
targetSdkVersionOverride = targetSdkVersionOverride?.apiLevel ?: -1
99-
)
101+
targetSdkVersionOverride = targetSdkVersionOverride?.apiLevel ?: -1)
100102
}
101103

102104
override fun getVariant(param: StringParameter): CompletableFuture<AndroidVariantMetadata?> {
@@ -106,11 +108,8 @@ internal class AndroidProjectImpl(
106108
}
107109

108110
private fun Variant.toMetadata(): AndroidVariantMetadata {
109-
return AndroidVariantMetadata(
110-
name = name,
111-
mainArtifact = mainArtifact.toMetadata(name),
112-
otherArtifacts = mutableMapOf()
113-
)
111+
return AndroidVariantMetadata(name = name, mainArtifact = mainArtifact.toMetadata(name),
112+
otherArtifacts = mutableMapOf())
114113
}
115114

116115
override fun getBootClasspaths(): CompletableFuture<Collection<File>> {
@@ -131,11 +130,8 @@ internal class AndroidProjectImpl(
131130
}
132131
}
133132

134-
private fun fillLibrary(
135-
item: GraphItem,
136-
libraries: Map<String, Library>,
137-
seen: HashMap<String, DefaultLibrary>
138-
): DefaultLibrary? {
133+
private fun fillLibrary(item: GraphItem, libraries: Map<String, Library>,
134+
seen: HashMap<String, DefaultLibrary>): DefaultLibrary? {
139135

140136
val lib = libraries[item.key] ?: return null
141137
val library = copy(lib)
@@ -164,8 +160,9 @@ internal class AndroidProjectImpl(
164160
return CompletableFuture.supplyAsync {
165161

166162
// model sync files available only in v7.3.0 and later
167-
return@supplyAsync if (AndroidPluginVersion.parse(versions.agp) >= AndroidPluginVersion(7, 3, 0))
168-
copy(androidProject.modelSyncFiles)
163+
return@supplyAsync if (AndroidPluginVersion.parse(versions.agp) >= AndroidPluginVersion(7, 3,
164+
0)
165+
) copy(androidProject.modelSyncFiles)
169166
else emptyList()
170167
}
171168
}
@@ -191,22 +188,13 @@ internal class AndroidProjectImpl(
191188
val gradleMetadata = super.getMetadata().get()
192189

193190
val viewBindingOptions = androidProject.viewBindingOptions?.let(
194-
AndroidModulePropertyCopier::copy)
195-
?: DefaultViewBindingOptions()
191+
AndroidModulePropertyCopier::copy) ?: DefaultViewBindingOptions()
196192

197-
return@supplyAsync AndroidProjectMetadata(
198-
gradleMetadata,
199-
packageName,
200-
basicAndroidProject.projectType,
201-
copy(androidProject.flags),
202-
copy(androidProject.javaCompileOptions),
203-
viewBindingOptions,
204-
androidProject.resourcePrefix,
205-
androidProject.namespace,
206-
androidProject.androidTestNamespace,
207-
androidProject.testFixturesNamespace,
208-
getClassesJar()
209-
)
193+
return@supplyAsync AndroidProjectMetadata(gradleMetadata, packageName,
194+
basicAndroidProject.projectType, copy(androidProject.flags),
195+
copy(androidProject.javaCompileOptions), viewBindingOptions, androidProject.resourcePrefix,
196+
androidProject.namespace, androidProject.androidTestNamespace,
197+
androidProject.testFixturesNamespace, getClassesJar())
210198
}
211199
}
212200

@@ -234,4 +222,77 @@ internal class AndroidProjectImpl(
234222
this.packageName = extractPackageName(manifestFile) ?: UNKNOWN_PACKAGE
235223
this.shouldLookupPackage = false
236224
}
237-
}
225+
226+
private fun AndroidArtifact.computeApplicationId(variantName: String): String? {
227+
val minAgpForAppId = AndroidPluginVersion(7, 4, 0)
228+
return if (minAgpForAppId <= AndroidPluginVersion.parse(versions.agp)) {
229+
applicationId
230+
} else {
231+
computeApplicationIdLegacy(variantName)
232+
}
233+
}
234+
235+
// Adapted from the following :
236+
// https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/core/dsl/impl/ComponentDslInfoImpl.kt;drc=6a5551bdea55c0c991f1ccf1e3f8f6f3d2cd2cb7;l=107
237+
// https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/core/dsl/impl/VariantDslInfoImpl.kt;drc=d44f5b98cd5530eceb230e0d151ad96c4277f78d;l=109
238+
239+
protected fun computeApplicationIdLegacy(variantName: String): String {
240+
val basicVariant = basicAndroidProject.variants.firstOrNull { it.name == variantName }
241+
val buildType = basicVariant?.buildType?.let { buildTypeName ->
242+
androidDsl.buildTypes.find { buildType -> buildType.name == buildTypeName }
243+
}!!
244+
245+
val appIdFromFlavor = if (basicAndroidProject.projectType == ProjectType.APPLICATION) {
246+
androidDsl.productFlavors.find { flavor ->
247+
"${flavor.name}${buildType.name.capitalizeString()}" == variantName
248+
}?.applicationId
249+
} else {
250+
androidDsl.defaultConfig.applicationId
251+
}
252+
253+
return if (appIdFromFlavor == null) {
254+
// No appId value set from DSL; use the namespace value from the DSL.
255+
"${androidProject.namespace}${computeApplicationIdSuffix(variantName, buildType)}"
256+
} else {
257+
// use value from flavors/defaultConfig
258+
// needed to make nullability work in kotlinc
259+
val finalAppIdFromFlavors: String = appIdFromFlavor
260+
"$finalAppIdFromFlavors${computeApplicationIdSuffix(variantName, buildType)}"
261+
}
262+
}
263+
264+
/**
265+
* Combines all the appId suffixes into a single one.
266+
*
267+
* The suffixes are separated by '.' whether their first char is a '.' or not.
268+
*/
269+
protected fun computeApplicationIdSuffix(variantName: String, buildType: BuildType): String {
270+
// for the suffix we combine the suffix from all the flavors. However, we're going to
271+
// want the higher priority one to be last.
272+
val suffixes = mutableListOf<String>()
273+
androidDsl.defaultConfig.applicationIdSuffix?.let {
274+
suffixes.add(it)
275+
}
276+
277+
if (basicAndroidProject.projectType == ProjectType.APPLICATION) {
278+
279+
val flavorSuffix = androidDsl.productFlavors.find { flavor ->
280+
"${flavor.name}${buildType.name.capitalizeString()}" == variantName
281+
}?.applicationIdSuffix
282+
283+
flavorSuffix?.also { suffixes.add(flavorSuffix) }
284+
285+
// then we add the build type after.
286+
buildType.applicationIdSuffix?.also {
287+
suffixes.add(it)
288+
}
289+
}
290+
291+
val nonEmptySuffixes = suffixes.filter { it.isNotEmpty() }
292+
return if (nonEmptySuffixes.isNotEmpty()) {
293+
".${nonEmptySuffixes.joinToString(separator = ".", transform = { it.removePrefix(".") })}"
294+
} else {
295+
""
296+
}
297+
}
298+
}

subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AndroidProjectModelBuilder.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package com.itsaky.androidide.tooling.impl.sync
1818

19+
import com.android.builder.model.v2.models.AndroidDsl
1920
import com.android.builder.model.v2.models.AndroidProject
2021
import com.android.builder.model.v2.models.BasicAndroidProject
2122
import com.android.builder.model.v2.models.ModelBuilderParameter
@@ -40,6 +41,7 @@ class AndroidProjectModelBuilder(initializationParams: InitializeProjectParams)
4041
val projectPath = module.gradleProject.path
4142
val basicModel = controller.getModelAndLog(module, BasicAndroidProject::class.java)
4243
val androidModel = controller.getModelAndLog(module, AndroidProject::class.java)
44+
val androidDsl = controller.getModelAndLog(module, AndroidDsl::class.java)
4345

4446
val variantNames = basicModel.variants.map { it.name }
4547
log(
@@ -80,7 +82,8 @@ class AndroidProjectModelBuilder(initializationParams: InitializeProjectParams)
8082
basicModel,
8183
androidModel,
8284
variantDependencies,
83-
versions
85+
versions,
86+
androidDsl
8487
)
8588
}
8689
}

0 commit comments

Comments
 (0)