Last active
October 5, 2024 03:24
-
-
Save eygraber/482e9942d5812e9efa5ace016aac4197 to your computer and use it in GitHub Desktop.
Initial exploration of a configuration cache compatible way of collecting dependencies in Gradle (Heavily borrowed from https://github.com/google/play-services-plugins/blob/master/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/LicensesTask.groovy)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.gradle.internal.component.AmbiguousVariantSelectionException | |
tasks.register<GatherDependenciesTask>("gatherDependencies") { | |
output.set( | |
layout.buildDirectory.file( | |
"generated/gathered-dependencies/dependencies.txt" | |
) | |
) | |
ids.set( | |
project | |
.configurations | |
.filterNot { configuration -> | |
configuration.shouldSkip() | |
} | |
.flatMap { configuration -> | |
val visitedDependencyNames = mutableSetOf<String>() | |
configuration | |
.resolvedConfiguration | |
.lenientConfiguration | |
.firstLevelModuleDependencies | |
.getResolvedArtifacts(visitedDependencyNames) | |
.toModuleComponentIdentifierWrapper() | |
} | |
.toSet() | |
.sortedBy { "${it.group}:${it.name}:${it.version}" } | |
) | |
} | |
} | |
fun Set<ResolvedDependency>.getResolvedArtifacts( | |
visitedDependencyNames: MutableSet<String> | |
): Set<ResolvedArtifact> { | |
val resolvedArtifacts = mutableSetOf<ResolvedArtifact>() | |
for(resolvedDependency in this) { | |
val name = resolvedDependency.name | |
if(name !in visitedDependencyNames) { | |
visitedDependencyNames += name | |
try { | |
resolvedArtifacts += when(resolvedDependency.moduleVersion) { | |
"unspecified" -> | |
resolvedDependency.children.getResolvedArtifacts( | |
visitedDependencyNames = visitedDependencyNames | |
) | |
else -> resolvedDependency.allModuleArtifacts | |
} | |
} | |
catch(e: AmbiguousVariantSelectionException) { | |
} | |
} | |
} | |
return resolvedArtifacts | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.gradle.api.DefaultTask | |
import org.gradle.api.Project | |
import org.gradle.api.artifacts.Configuration | |
import org.gradle.api.artifacts.ConfigurationContainer | |
import org.gradle.api.artifacts.Dependency | |
import org.gradle.api.artifacts.ExternalModuleDependency | |
import org.gradle.api.artifacts.ProjectDependency | |
import org.gradle.api.artifacts.dsl.DependencyHandler | |
import org.gradle.api.artifacts.result.ResolvedArtifactResult | |
import org.gradle.api.artifacts.result.UnresolvedArtifactResult | |
import org.gradle.api.file.RegularFileProperty | |
import org.gradle.api.internal.artifacts.DefaultModuleIdentifier | |
import org.gradle.api.tasks.CacheableTask | |
import org.gradle.api.tasks.Input | |
import org.gradle.api.tasks.Nested | |
import org.gradle.api.tasks.OutputFile | |
import org.gradle.api.tasks.TaskAction | |
import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier | |
import org.gradle.kotlin.dsl.listProperty | |
import org.gradle.maven.MavenModule | |
import org.gradle.maven.MavenPomArtifact | |
import org.w3c.dom.Element | |
import javax.inject.Inject | |
import javax.xml.parsers.DocumentBuilderFactory | |
/** | |
* Heavily borrowed from | |
* https://github.com/google/play-services-plugins/blob/master/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/LicensesTask.groovy | |
*/ | |
@CacheableTask | |
abstract class GatherDependenciesTask : DefaultTask() { | |
@get:Inject abstract val dependencyHandler: DependencyHandler | |
@get:Nested | |
val ids = project.objects.listProperty<ModuleComponentIdentifierWrapper>() | |
@Input | |
fun getDirectDependencies() = project.configurations.collectDependencies(mutableSetOf(project)) | |
@get:OutputFile | |
abstract val output: RegularFileProperty | |
@TaskAction | |
fun run() { | |
val data = | |
ids | |
.get() | |
.mapNotNull { (group, name, version) -> | |
val id = DefaultModuleComponentIdentifier(DefaultModuleIdentifier.newId(group, name), version) | |
val components = | |
dependencyHandler | |
.createArtifactResolutionQuery() | |
.forComponents(id) | |
.withArtifacts(MavenModule::class.java, MavenPomArtifact::class.java) | |
.execute() | |
components | |
.resolvedComponents | |
.firstOrNull() | |
.let { resolvedComponent -> | |
if(resolvedComponent == null) { | |
logger.warn("No resolved components for $id") | |
} | |
resolvedComponent | |
} | |
?.getArtifacts(MavenPomArtifact::class.java) | |
?.firstOrNull() | |
.let { pomArtifact -> | |
when(pomArtifact) { | |
is ResolvedArtifactResult -> pomArtifact.file | |
is UnresolvedArtifactResult -> { | |
logger.warn("Failed to resolve $id", pomArtifact.failure) | |
null | |
} | |
else -> { | |
logger.warn("Couldn't find a MavenPomArtifact for $id") | |
null | |
} | |
} | |
} | |
?.let { pomFile -> | |
val factory = DocumentBuilderFactory.newInstance() | |
val doc = factory.newDocumentBuilder().parse(pomFile) | |
val pom = doc.getElementsByTagName("project").item(0) | |
// do something | |
} | |
} | |
} | |
} | |
private fun ConfigurationContainer.collectDependencies( | |
visitedProjects: MutableSet<Project> | |
): List<String> { | |
val directDependencies = mutableSetOf<String>() | |
val libraryProjects = mutableSetOf<Project>() | |
for(configuration in this) { | |
if(configuration.shouldSkip()) { | |
continue | |
} | |
for(dependency in configuration.allDependencies) { | |
if(dependency is ProjectDependency) { | |
libraryProjects.add(dependency.dependencyProject) | |
} | |
else if(dependency is ExternalModuleDependency) { | |
directDependencies.add(dependency.toMavenId()) | |
} | |
} | |
} | |
for(libraryProject in libraryProjects) { | |
if(libraryProject in visitedProjects) { | |
continue | |
} | |
visitedProjects += libraryProject | |
directDependencies.addAll( | |
libraryProject.configurations.collectDependencies( | |
visitedProjects | |
) | |
) | |
} | |
return directDependencies.sorted() | |
} | |
private fun Dependency.toMavenId() = | |
"$group:$name:$version" | |
internal fun Configuration.shouldSkip() = | |
!isCanBeResolved || | |
isTest || | |
!isPackagedDependency | |
private val testCompile = setOf("testCompile", "androidTestCompile") | |
internal val Configuration.isTest | |
get() = | |
name.contains("test", ignoreCase = true) || | |
name.contains("androidTest", ignoreCase = true) || | |
hierarchy.any { configurationHierarchy -> | |
testCompile.any { configurationHierarchy.name.contains(it, ignoreCase = true) } | |
} | |
private val packagedDependencyPrefixes = setOf("compile", "implementation", "api") | |
internal val Configuration.isPackagedDependency | |
get() = | |
packagedDependencyPrefixes.any { name.contains(it, ignoreCase = true) } || | |
hierarchy.any { configurationHierarchy -> | |
packagedDependencyPrefixes.any { configurationHierarchy.name.contains(it, ignoreCase = true) } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.gradle.api.artifacts.ResolvedArtifact | |
import org.gradle.api.tasks.Input | |
data class ModuleComponentIdentifierWrapper( | |
@get:Input val group: String, | |
@get:Input val name: String, | |
@get:Input val version: String | |
) | |
internal fun Set<ResolvedArtifact>.toModuleComponentIdentifierWrapper() = | |
map { artifact -> | |
val group = artifact.moduleVersion.id.group.trim() | |
val name = artifact.name.trim() | |
val version = artifact.moduleVersion.id.version.trim() | |
ModuleComponentIdentifierWrapper(group, name, version) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment