@@ -30,6 +30,8 @@ import com.android.build.api.variant.ScopedArtifacts
3030import com.android.build.api.variant.TestAndroidComponentsExtension
3131import com.android.build.gradle.api.AndroidBasePlugin
3232import com.android.build.gradle.tasks.JdkImageInput
33+ import dagger.hilt.android.plugin.rules.HiltArtifactCompatibilityRule
34+ import dagger.hilt.android.plugin.rules.HiltArtifactDisambiguationRule
3335import dagger.hilt.android.plugin.task.AggregateDepsTask
3436import dagger.hilt.android.plugin.task.HiltSyncTask
3537import 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
0 commit comments