22
33import groovy.lang.Closure
44import groovy.util.Node
5- import org.gradle.api.NamedDomainObjectCollection
65import org.gradle.api.Project
76import org.gradle.api.artifacts.Dependency
87import org.gradle.api.artifacts.ProjectDependency
98import org.gradle.api.plugins.ExtensionAware
109import org.gradle.api.plugins.ExtraPropertiesExtension
1110import org.gradle.api.publish.PublishingExtension
1211import org.gradle.api.publish.internal.PublicationInternal
12+ import org.gradle.api.publish.internal.metadata.ModuleMetadataSpec
1313import org.gradle.api.publish.maven.MavenArtifact
1414import org.gradle.api.publish.maven.MavenPublication
15- import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication
1615import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven
16+ import org.gradle.api.publish.tasks.GenerateModuleMetadata
1717import org.gradle.api.tasks.SourceSet
1818import org.gradle.api.tasks.SourceSetContainer
19+ import org.gradle.internal.Try
1920import org.gradle.jvm.tasks.Jar
2021import org.gradle.kotlin.dsl.closureOf
2122import org.gradle.kotlin.dsl.maybeCreate
@@ -25,7 +26,8 @@ import org.gradle.kotlin.dsl.withGroovyBuilder
2526import org.gradle.kotlin.dsl.withType
2627import org.gradle.plugins.signing.Sign
2728import org.gradle.plugins.signing.SigningExtension
28- import java.io.File
29+ import java.lang.reflect.Field
30+ import java.lang.reflect.Method
2931
3032/* *
3133 * Configure deployment tasks and properties for a project using the provided [deployConfig].
@@ -146,9 +148,25 @@ private fun Project.configureAndroidDeployment(
146148 }
147149
148150 // Disable main publication
149- tasks.withType<AbstractPublishToMaven >().configureEach {
151+ tasks.withType<AbstractPublishToMaven > {
150152 isEnabled = " Main" !in name
151153 }
154+
155+ // Hook into Gradle module metadata generation
156+ // and replace project references (e.g. "core") with the correct
157+ // Maven dependency coordinates ("android-test-core")
158+ tasks.withType<GenerateModuleMetadata > {
159+ val junit = SupportedJUnit .values()
160+ .firstOrNull { name.contains(it.variant, ignoreCase = true ) }
161+
162+ if (junit != null ) {
163+ val modifier = ReflectiveModuleMetadataModifier (this , junit)
164+
165+ doFirst {
166+ modifier.run ()
167+ }
168+ }
169+ }
152170}
153171
154172private fun Project.configurePluginDeployment (
@@ -465,3 +483,68 @@ private fun Project.centralPublishing(
465483 }
466484 }
467485}
486+
487+ /* *
488+ * Gradle module metadata modifier, replacing references to instrumentation libraries
489+ * with the correct artifact ID in each module's JSON file (build/publications/.../module.json).
490+ */
491+ private class ReflectiveModuleMetadataModifier (
492+ private val task : GenerateModuleMetadata ,
493+ private val junit : SupportedJUnit
494+ ) {
495+ @Suppress(" UNCHECKED_CAST" )
496+ private companion object {
497+ private val reflectiveMethodCache = mutableMapOf<Class <* >, MutableMap <String , Method >>()
498+ private val reflectiveFieldCache = mutableMapOf<Class <* >, MutableMap <String , Field >>()
499+
500+ fun <R : Any > method (cls : Class <* >, named : String , receiver : Any , vararg args : Any ): R {
501+ val methods = reflectiveMethodCache.getOrPut(cls, ::mutableMapOf)
502+ val method = methods.getOrPut(named) {
503+ cls.getDeclaredMethod(named).also { it.isAccessible = true }
504+ }
505+ return method.invoke(receiver, * args) as R
506+ }
507+
508+ fun field (cls : Class <* >, named : String ): Field {
509+ val fields = reflectiveFieldCache.getOrPut(cls, ::mutableMapOf)
510+ val field = fields.getOrPut(named) {
511+ cls.getDeclaredField(named).also { it.isAccessible = true }
512+ }
513+ return field
514+ }
515+
516+ fun <R : Any > Any.field (named : String ): R = field(this .javaClass, named).get(this ) as R
517+ }
518+
519+ fun run () {
520+ // Access the inputs of the task, then crawl through to the dependencies
521+ // of each variant inside its module metadata. Rewrite the coordinates of each
522+ // instrumentation lib found inside there.
523+ val inputState = method<Any >(GenerateModuleMetadata ::class .java, " inputState" , task)
524+ val metadataSpecTry = inputState.field<Try <ModuleMetadataSpec >>(" moduleMetadataSpec" )
525+ val metadataSpec = metadataSpecTry.get()
526+ val variants = metadataSpec.field<List <Any >>(" variants" )
527+
528+ for (variant in variants) {
529+ val variantDependencies =
530+ runCatching { variant.field<List <Any >>(" dependencies" ) }
531+ .getOrNull()
532+ ? : continue
533+
534+ for (variantDependency in variantDependencies) {
535+ val depCoordinates = variantDependency.field<Any >(" coordinates" )
536+ val depGroup = depCoordinates.field<String >(" group" )
537+ val depName = depCoordinates.field<String >(" name" )
538+
539+ if (depGroup == Artifacts .Instrumentation .groupId) {
540+ Artifacts .from(depName)?.let { replacement ->
541+ field(depCoordinates.javaClass, " name" ).set(
542+ depCoordinates,
543+ suffixedArtifactId(replacement.artifactId, junit)
544+ )
545+ }
546+ }
547+ }
548+ }
549+ }
550+ }
0 commit comments