Skip to content

Commit f7e31b8

Browse files
trexemDagger Team
authored andcommitted
Replace Hilt's "android-classes" transform with attribute rules.
This change removes the CopyTransform from "android-classes" to "jar-for-dagger" and instead uses Gradle's attribute compatibility and disambiguation rules. The new rules make "android-classes" and "android-classes-jar" compatible with requests for "jar-for-dagger" and "aggregated-jar-for-hilt", and prioritize these artifact types when multiple candidates are available. FIXES #5116 FIXES #5099 RELNOTES=Replaced Hilt's "android-classes" transform with attribute rules PiperOrigin-RevId: 869250235
1 parent 5909005 commit f7e31b8

File tree

3 files changed

+117
-15
lines changed

3 files changed

+117
-15
lines changed

java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import com.android.build.api.variant.ScopedArtifacts
3030
import com.android.build.api.variant.TestAndroidComponentsExtension
3131
import com.android.build.gradle.api.AndroidBasePlugin
3232
import com.android.build.gradle.tasks.JdkImageInput
33+
import dagger.hilt.android.plugin.rules.HiltArtifactCompatibilityRule
34+
import dagger.hilt.android.plugin.rules.HiltArtifactDisambiguationRule
3335
import dagger.hilt.android.plugin.task.AggregateDepsTask
3436
import dagger.hilt.android.plugin.task.HiltSyncTask
3537
import dagger.hilt.android.plugin.transform.AggregatedPackagesTransform
@@ -102,6 +104,7 @@ class HiltGradlePlugin @Inject constructor(private val providers: ProviderFactor
102104
val hiltExtension =
103105
project.extensions.create(HiltExtension::class.java, "hilt", HiltExtensionImpl::class.java)
104106
HiltPluginEnvironment(project, hiltExtension).apply {
107+
configureCompatibilityRules()
105108
configureDependencyTransforms()
106109
configureCompileClasspath()
107110
configureBytecodeTransformASM()
@@ -113,20 +116,36 @@ class HiltGradlePlugin @Inject constructor(private val providers: ProviderFactor
113116
// Configures Gradle dependency transforms.
114117
private fun HiltPluginEnvironment.configureDependencyTransforms() =
115118
project.dependencies.apply {
119+
/**
120+
* NOTE: 'CopyTransform' is intentionally used for directories to create 'Path Asymmetry.'
121+
*
122+
* In AGP 9.0+, if we use a 'Pure Rules' approach (making both android-classes and directories
123+
* compatible via AttributeCompatibilityRule), Gradle discovers two competing transformation
124+
* chains for every JAR artifact:
125+
* 1. jar -> IdentityTransform -> android-classes-jar -> [Rule] hilt-all-classes
126+
* 2. jar -> UnzipTransform -> directory -> [Rule] hilt-all-classes
127+
*
128+
* Both chains have the same length, thus giving Multiple transformation chains error. And
129+
* disambiguation rules don't help choosing one over the other.
130+
*
131+
* The solution is to make the 'directory' chain one step more complex than the
132+
* 'android-classes' chain.
133+
*
134+
* By using a 'CopyTransform' for directories and EXCLUDING 'directory' from the
135+
* CompatibilityRules, we ensure that the 'Unzip' path is one step more complex than the
136+
* android-classes path. This asymmetry allows Gradle to prioritize the Identity/Rule path for
137+
* JARs and avoids the ambiguity error, while still providing a dedicated gateway for local
138+
* project directories. The chains would look like:
139+
* 1. jar -> IdentityTransform -> android-classes-jar -> [Rule] hilt-all-classes (preferred)
140+
* 2. jar -> UnzipTransform -> directory -> CopyTransform -> hilt-all-classes
141+
*/
116142
registerTransform(CopyTransform::class.java) { spec ->
117-
// AGP has transforms from jar to android-classes for Java/Kotlin/Android libraries.
118-
spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "android-classes")
119-
spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE)
120-
}
121-
registerTransform(CopyTransform::class.java) { spec ->
122-
// File Collection dependencies might be an artifact of type 'directory', e.g. when
123-
// adding as a dep the destination directory of the JavaCompile task.
124143
spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "directory")
125-
spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE)
144+
spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, HILT_ALL_CLASSES_ARTIFACT_TYPE_VALUE)
126145
}
127146
registerTransform(AggregatedPackagesTransform::class.java) { spec ->
128-
spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE)
129-
spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, AGGREGATED_HILT_ARTIFACT_TYPE_VALUE)
147+
spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, HILT_ALL_CLASSES_ARTIFACT_TYPE_VALUE)
148+
spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, HILT_METADATA_CLASSES_ARTIFACT_TYPE_VALUE)
130149
}
131150
}
132151

@@ -149,7 +168,7 @@ class HiltGradlePlugin @Inject constructor(private val providers: ProviderFactor
149168
// runtime classpath has the tested dependencies removed in these cases.
150169
val artifactView =
151170
(testedVariant ?: variant).runtimeConfiguration.incoming.artifactView { view ->
152-
view.attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE)
171+
view.attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, HILT_ALL_CLASSES_ARTIFACT_TYPE_VALUE)
153172
view.componentFilter { identifier ->
154173
// Filter out the project's classes from the aggregated view since this can cause
155174
// issues with Kotlin internal members visibility. b/178230629
@@ -208,7 +227,10 @@ class HiltGradlePlugin @Inject constructor(private val providers: ProviderFactor
208227
testedVariant = testedVariant,
209228
classpath =
210229
project.files(
211-
getHiltTransformedDependencies(configurations, AGGREGATED_HILT_ARTIFACT_TYPE_VALUE)
230+
getHiltTransformedDependencies(
231+
configurations,
232+
HILT_METADATA_CLASSES_ARTIFACT_TYPE_VALUE,
233+
)
212234
),
213235
)
214236

@@ -219,7 +241,7 @@ class HiltGradlePlugin @Inject constructor(private val providers: ProviderFactor
219241
sources = project.files(aggregatingTask.map { it.outputDir }),
220242
classpath =
221243
project.files(
222-
getHiltTransformedDependencies(configurations, DAGGER_ARTIFACT_TYPE_VALUE)
244+
getHiltTransformedDependencies(configurations, HILT_ALL_CLASSES_ARTIFACT_TYPE_VALUE)
223245
),
224246
)
225247

@@ -398,6 +420,15 @@ class HiltGradlePlugin @Inject constructor(private val providers: ProviderFactor
398420
}
399421
}
400422

423+
private fun HiltPluginEnvironment.configureCompatibilityRules() {
424+
project.dependencies.attributesSchema { schema ->
425+
schema.attribute(ARTIFACT_TYPE_ATTRIBUTE) { attribute ->
426+
attribute.compatibilityRules.add(HiltArtifactCompatibilityRule::class.java)
427+
attribute.disambiguationRules.add(HiltArtifactDisambiguationRule::class.java)
428+
}
429+
}
430+
}
431+
401432
private fun verifyDependencies(project: Project) {
402433
// If project is already failing, skip verification since dependencies might not be resolved.
403434
if (project.state.failure != null) {
@@ -430,8 +461,10 @@ class HiltGradlePlugin @Inject constructor(private val providers: ProviderFactor
430461

431462
companion object {
432463
private val ARTIFACT_TYPE_ATTRIBUTE = Attribute.of("artifactType", String::class.java)
433-
const val DAGGER_ARTIFACT_TYPE_VALUE = "jar-for-dagger"
434-
const val AGGREGATED_HILT_ARTIFACT_TYPE_VALUE = "aggregated-jar-for-hilt"
464+
/** Includes all classes from `android-classes` and `directory` artifacts. */
465+
const val HILT_ALL_CLASSES_ARTIFACT_TYPE_VALUE = "hilt-all-classes"
466+
/** A filtered view of hilt-all-classes that only includes the Hilt metadata. */
467+
const val HILT_METADATA_CLASSES_ARTIFACT_TYPE_VALUE = "hilt-metadata-classes"
435468

436469
const val LIBRARY_GROUP = "com.google.dagger"
437470

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (C) 2026 The Dagger Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dagger.hilt.android.plugin.rules
18+
19+
import org.gradle.api.attributes.AttributeCompatibilityRule
20+
import org.gradle.api.attributes.CompatibilityCheckDetails
21+
22+
/**
23+
* A compatibility rule for Hilt artifact types. This rule is used to allow the "hilt-all-classes"
24+
* artifact type to be compatible with "android-classes" and "android-classes-jar".
25+
*/
26+
abstract class HiltArtifactCompatibilityRule : AttributeCompatibilityRule<String> {
27+
override fun execute(details: CompatibilityCheckDetails<String>) {
28+
if (
29+
details.consumerValue == "hilt-all-classes" && details.producerValue == "android-classes-jar"
30+
) {
31+
details.compatible()
32+
}
33+
}
34+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (C) 2026 The Dagger Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dagger.hilt.android.plugin.rules
18+
19+
import org.gradle.api.attributes.AttributeDisambiguationRule
20+
import org.gradle.api.attributes.MultipleCandidatesDetails
21+
22+
/**
23+
* A disambiguation rule for Hilt artifact types. This rule is used to disambiguate between
24+
* "android-classes" when the consumer value is "hilt-all-classes".
25+
*/
26+
abstract class HiltArtifactDisambiguationRule : AttributeDisambiguationRule<String> {
27+
override fun execute(details: MultipleCandidatesDetails<String>) {
28+
if (
29+
details.consumerValue == "hilt-all-classes" &&
30+
details.candidateValues.contains("android-classes-jar")
31+
) {
32+
details.closestMatch("android-classes-jar")
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)