diff --git a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/ArtifactFiles.groovy b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/ArtifactFiles.groovy index bfba7194..3208aa93 100644 --- a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/ArtifactFiles.groovy +++ b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/ArtifactFiles.groovy @@ -16,13 +16,24 @@ package com.google.android.gms.oss.licenses.plugin +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import java.io.Serializable /** * Data class to hold the resolved physical files for a single dependency. */ class ArtifactFiles implements Serializable { + @InputFile + @PathSensitive(PathSensitivity.NONE) + @Optional File pomFile + + @InputFile + @PathSensitive(PathSensitivity.NONE) + @Optional File libraryFile ArtifactFiles(File pomFile, File libraryFile) { diff --git a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/DependencyTask.groovy b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/DependencyTask.groovy index e62df28f..493a2a8a 100644 --- a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/DependencyTask.groovy +++ b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/DependencyTask.groovy @@ -21,8 +21,12 @@ import groovy.json.JsonBuilder import groovy.json.JsonGenerator import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.slf4j.LoggerFactory @@ -37,6 +41,7 @@ import static com.android.tools.build.libraries.metadata.Library.LibraryOneofCas * If the protobuf is not present (e.g. debug variants) it writes a single * dependency on the {@link DependencyUtil#ABSENT_ARTIFACT}. */ +@CacheableTask abstract class DependencyTask extends DefaultTask { private static final logger = LoggerFactory.getLogger(DependencyTask.class) @@ -44,7 +49,8 @@ abstract class DependencyTask extends DefaultTask { abstract RegularFileProperty getDependenciesJson() @InputFile - @org.gradle.api.tasks.Optional + @PathSensitive(PathSensitivity.NONE) + @Optional abstract RegularFileProperty getLibraryDependenciesReport() @TaskAction diff --git a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/DependencyUtil.groovy b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/DependencyUtil.groovy index fb7006dc..0808b0b8 100644 --- a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/DependencyUtil.groovy +++ b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/DependencyUtil.groovy @@ -20,6 +20,7 @@ import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.component.ModuleComponentIdentifier import org.gradle.api.artifacts.result.ResolvedArtifactResult +import org.gradle.api.provider.Provider import org.gradle.maven.MavenModule import org.gradle.maven.MavenPomArtifact @@ -45,52 +46,54 @@ class DependencyUtil { * * @param project The Gradle project used to create the resolution query. * @param runtimeConfiguration The configuration whose dependencies should be resolved. - * @return A map of GAV coordinates to their resolved ArtifactFiles. + * @return A provider for a map of GAV coordinates to their resolved ArtifactFiles. */ - static Map resolveArtifacts(Project project, Configuration runtimeConfiguration) { - // We create an ArtifactView to gather the component identifiers and library files. - // We specifically target external Maven dependencies (ModuleComponentIdentifiers). - def runtimeArtifactView = runtimeConfiguration.incoming.artifactView { - it.componentFilter { id -> id instanceof ModuleComponentIdentifier } - } - - def artifactsMap = [:] - - // 1. Gather library files directly from the view - runtimeArtifactView.artifacts.each { artifact -> - def id = artifact.id.componentIdentifier - if (id instanceof ModuleComponentIdentifier) { - String key = "${id.group}:${id.module}:${id.version}".toString() - artifactsMap[key] = new ArtifactFiles(null, artifact.file) + static Provider> resolveArtifacts(Project project, Configuration runtimeConfiguration) { + return project.provider { + // We create an ArtifactView to gather the component identifiers and library files. + // We specifically target external Maven dependencies (ModuleComponentIdentifiers). + def runtimeArtifactView = runtimeConfiguration.incoming.artifactView { + it.componentFilter { id -> id instanceof ModuleComponentIdentifier } + } + + def artifactsMap = [:] + + // 1. Gather library files directly from the view + runtimeArtifactView.artifacts.each { artifact -> + def id = artifact.id.componentIdentifier + if (id instanceof ModuleComponentIdentifier) { + String key = "${id.group}:${id.module}:${id.version}".toString() + artifactsMap[key] = new ArtifactFiles(null, artifact.file) + } } - } - // 2. Fetch corresponding POM files using ArtifactResolutionQuery - def componentIds = runtimeArtifactView.artifacts.collect { it.id.componentIdentifier } - - if (!componentIds.isEmpty()) { - def result = project.dependencies.createArtifactResolutionQuery() - .forComponents(componentIds) - .withArtifacts(MavenModule, MavenPomArtifact) - .execute() + // 2. Fetch corresponding POM files using ArtifactResolutionQuery + def componentIds = runtimeArtifactView.artifacts.collect { it.id.componentIdentifier } + + if (!componentIds.isEmpty()) { + def result = project.dependencies.createArtifactResolutionQuery() + .forComponents(componentIds) + .withArtifacts(MavenModule, MavenPomArtifact) + .execute() - result.resolvedComponents.each { component -> - component.getArtifacts(MavenPomArtifact).each { artifact -> - if (artifact instanceof ResolvedArtifactResult) { - def id = component.id - String key = "${id.group}:${id.module}:${id.version}".toString() - - // Update the existing entry with the POM file - if (artifactsMap.containsKey(key)) { - artifactsMap[key].pomFile = artifact.file - } else { - artifactsMap[key] = new ArtifactFiles(artifact.file, null) + result.resolvedComponents.each { component -> + component.getArtifacts(MavenPomArtifact).each { artifact -> + if (artifact instanceof ResolvedArtifactResult) { + def id = component.id + String key = "${id.group}:${id.module}:${id.version}".toString() + + // Update the existing entry with the POM file + if (artifactsMap.containsKey(key)) { + artifactsMap[key].pomFile = artifact.file + } else { + artifactsMap[key] = new ArtifactFiles(artifact.file, null) + } } } } } + + return artifactsMap } - - return artifactsMap } } diff --git a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/LicensesTask.groovy b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/LicensesTask.groovy index e79f0da7..6b243167 100644 --- a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/LicensesTask.groovy +++ b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/LicensesTask.groovy @@ -21,10 +21,14 @@ import groovy.xml.XmlSlurper import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.slf4j.LoggerFactory @@ -38,6 +42,7 @@ import java.util.zip.ZipFile * mappings (POMs and Library artifacts) are provided as lazy input properties, * making the task a pure function of its inputs. */ +@CacheableTask abstract class LicensesTask extends DefaultTask { private static final String UTF_8 = "UTF-8" private static final byte[] LINE_SEPARATOR = System @@ -66,10 +71,11 @@ abstract class LicensesTask extends DefaultTask { * A map of GAV coordinates (group:name:version) to their resolved POM and Library files. * Populated by OssLicensesPlugin during configuration. */ - @org.gradle.api.tasks.Input + @Nested abstract org.gradle.api.provider.MapProperty getArtifactFiles() @InputFile + @PathSensitive(PathSensitivity.NONE) abstract RegularFileProperty getDependenciesJson() @OutputDirectory diff --git a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/OssLicensesPlugin.groovy b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/OssLicensesPlugin.groovy index df70fe90..985c6fb1 100644 --- a/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/OssLicensesPlugin.groovy +++ b/oss-licenses-plugin/src/main/groovy/com/google/android/gms/oss/licenses/plugin/OssLicensesPlugin.groovy @@ -58,11 +58,10 @@ class OssLicensesPlugin implements Plugin { // Task 1: Dependency Identification // This task reads the AGP METADATA_LIBRARY_DEPENDENCIES_REPORT protobuf. - def dependenciesJson = baseDir.map { it.file("dependencies.json") } TaskProvider dependencyTask = project.tasks.register( "${variant.name}OssDependencyTask", DependencyTask.class) { - it.dependenciesJson.set(dependenciesJson) + it.dependenciesJson.set(baseDir.map { it.file("dependencies.json") }) it.libraryDependenciesReport.set(variant.artifacts.get(SingleArtifact.METADATA_LIBRARY_DEPENDENCIES_REPORT.INSTANCE)) } project.logger.debug("Registered task ${dependencyTask.name}") @@ -73,10 +72,7 @@ class OssLicensesPlugin implements Plugin { "${variant.name}OssLicensesTask", LicensesTask.class) { it.dependenciesJson.set(dependencyTask.flatMap { it.dependenciesJson }) - - it.artifactFiles.set(project.provider { - DependencyUtil.resolveArtifacts(project, variant.runtimeConfiguration) - }) + it.artifactFiles.set(DependencyUtil.resolveArtifacts(project, variant.runtimeConfiguration)) } project.logger.debug("Registered task ${licenseTask.name}") diff --git a/oss-licenses-plugin/src/test/java/com/google/android/gms/oss/licenses/plugin/DependencyResolutionTest.java b/oss-licenses-plugin/src/test/java/com/google/android/gms/oss/licenses/plugin/DependencyResolutionTest.java index 220eaab6..d79def40 100644 --- a/oss-licenses-plugin/src/test/java/com/google/android/gms/oss/licenses/plugin/DependencyResolutionTest.java +++ b/oss-licenses-plugin/src/test/java/com/google/android/gms/oss/licenses/plugin/DependencyResolutionTest.java @@ -92,7 +92,7 @@ public void testComplexDependencyGraphResolution() throws IOException { runtimeClasspath.resolve(); // Execute resolution logic - Map artifactFiles = DependencyUtil.resolveArtifacts(appProject, runtimeClasspath); + Map artifactFiles = DependencyUtil.resolveArtifacts(appProject, runtimeClasspath).get(); // Assertions // - Guava resolved to the higher version @@ -121,7 +121,7 @@ public void testPomResolution() throws IOException { Configuration runtimeClasspath = appProject.getConfigurations().getByName("runtimeClasspath"); runtimeClasspath.resolve(); - Map artifactFiles = DependencyUtil.resolveArtifacts(appProject, runtimeClasspath); + Map artifactFiles = DependencyUtil.resolveArtifacts(appProject, runtimeClasspath).get(); assertThat(artifactFiles).containsKey("com.google.guava:guava:33.0.0-jre"); assertThat(artifactFiles.get("com.google.guava:guava:33.0.0-jre").getPomFile()).isNotNull();