Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,17 @@ import static com.android.tools.build.libraries.metadata.Library.LibraryOneofCas
* Plugin into a JSON format that will be consumed by the {@link LicensesTask}.
*
* If the protobuf is not present (e.g. debug variants) it writes a single
* dependency on the {@link DependencyUtil#ABSENT_ARTIFACT}.
* dependency on the {@link #ABSENT_ARTIFACT}.
*/
@CacheableTask
abstract class DependencyTask extends DefaultTask {
private static final logger = LoggerFactory.getLogger(DependencyTask.class)

// Sentinel written to the JSON when AGP does not provide a dependency report (e.g. debug
// variants). LicensesTask detects this and renders a placeholder message instead of licenses.
protected static final ArtifactInfo ABSENT_ARTIFACT =
new ArtifactInfo("absent", "absent", "absent")

@OutputFile
abstract RegularFileProperty getDependenciesJson()

Expand Down Expand Up @@ -75,7 +80,7 @@ abstract class DependencyTask extends DefaultTask {
private Set<ArtifactInfo> loadArtifactInfo() {
if (!libraryDependenciesReport.isPresent()) {
logger.info("$name not provided with AppDependencies proto file.")
return [DependencyUtil.ABSENT_ARTIFACT]
return [ABSENT_ARTIFACT]
}

AppDependencies appDependencies = loadDependenciesFile()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ 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.provider.MapProperty
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
Expand All @@ -37,9 +36,9 @@ import java.util.zip.ZipFile

/**
* Task to extract and bundle license information from application dependencies.
*
* This task is compatible with Gradle's Configuration Cache. All necessary file
* mappings (POMs and Library artifacts) are provided as lazy input properties,
*
* This task is compatible with Gradle's Configuration Cache. All necessary file
* mappings (POMs and Library artifacts) are provided as lazy input properties,
* making the task a pure function of its inputs.
*/
@CacheableTask
Expand Down Expand Up @@ -67,12 +66,24 @@ abstract class LicensesTask extends DefaultTask {
"(e.g. release) where the Android Gradle Plugin " +
"generates an app dependency list.")

/**
* A map of GAV coordinates (group:name:version) to their resolved POM and Library files.
* Populated by OssLicensesPlugin during configuration.
*/
@Nested
abstract org.gradle.api.provider.MapProperty<String, ArtifactFiles> getArtifactFiles()
// Library JARs/AARs keyed by "group:name:version", used to extract bundled license data
// from Google Play Services / Firebase artifacts.
//
// Why @Internal instead of @InputFiles?
// Gradle uses task input annotations to compute a cache key for up-to-date checks and build
// cache lookups. If these maps were @InputFiles, Gradle would hash every JAR/AAR and POM,
// which is expensive and redundant. The dependenciesJson file (which IS @InputFile) already
// captures the full dependency set as a stable JSON list. Since Maven Central artifacts are
// immutable per GAV coordinate (you can't re-publish the same version), the physical files
// can only change when the dependency list itself changes — which dependenciesJson already
// tracks. Using @Internal avoids the redundant hashing while maintaining correctness.
@Internal
abstract MapProperty<String, File> getLibraryFilesByGav()

// POM files keyed by "group:name:version", for reading <licenses> URLs from Maven metadata.
// @Internal for the same reason as libraryFilesByGav above.
@Internal
abstract MapProperty<String, File> getPomFilesByGav()

@InputFile
@PathSensitive(PathSensitivity.NONE)
Expand All @@ -81,20 +92,23 @@ abstract class LicensesTask extends DefaultTask {
@OutputDirectory
abstract DirectoryProperty getGeneratedDirectory()

@Internal // represented by getGeneratedDirectory()
@Internal // output file within getGeneratedDirectory(); tracked via that @OutputDirectory
File licenses

@Internal // represented by getGeneratedDirectory()
@Internal // output file within getGeneratedDirectory(); tracked via that @OutputDirectory
File licensesMetadata

@TaskAction
void action() {
initOutputDir()

Map<String, File> libraryMap = libraryFilesByGav.getOrElse([:])
Map<String, File> pomMap = pomFilesByGav.getOrElse([:])

File dependenciesJsonFile = dependenciesJson.asFile.get()
Set<ArtifactInfo> artifactInfoSet = loadDependenciesJson(dependenciesJsonFile)

if (DependencyUtil.ABSENT_ARTIFACT in artifactInfoSet) {
if (DependencyTask.ABSENT_ARTIFACT in artifactInfoSet) {
if (artifactInfoSet.size() > 1) {
throw new IllegalStateException("artifactInfoSet that contains EMPTY_ARTIFACT should not contain other artifacts.")
}
Expand All @@ -104,18 +118,18 @@ abstract class LicensesTask extends DefaultTask {
if (isGoogleServices(artifactInfo.group)) {
// Add license info for google-play-services itself
if (!artifactInfo.name.endsWith(LICENSE_ARTIFACT_SUFFIX)) {
addLicensesFromPom(artifactInfo)
addLicensesFromPom(pomMap, artifactInfo)
}
// Add transitive licenses info for google-play-services. For
// post-granular versions, this is located in the artifact
// itself, whereas for pre-granular versions, this information
// is located at the complementary license artifact as a runtime
// dependency.
if (isGranularVersion(artifactInfo.version) || artifactInfo.name.endsWith(LICENSE_ARTIFACT_SUFFIX)) {
addGooglePlayServiceLicenses(artifactInfo)
addGooglePlayServiceLicenses(libraryMap, artifactInfo)
}
} else {
addLicensesFromPom(artifactInfo)
addLicensesFromPom(pomMap, artifactInfo)
}
}
}
Expand All @@ -125,7 +139,8 @@ abstract class LicensesTask extends DefaultTask {

private static Set<ArtifactInfo> loadDependenciesJson(File jsonFile) {
def allDependencies = new JsonSlurper().parse(jsonFile)
def artifactInfoSet = new LinkedHashSet<ArtifactInfo>() // use LinkedHashSet to ensure stable output order
def artifactInfoSet = new LinkedHashSet<ArtifactInfo>()
// use LinkedHashSet to ensure stable output order
for (entry in allDependencies) {
ArtifactInfo artifactInfo = artifactInfoFromEntry(entry)
artifactInfoSet.add(artifactInfo)
Expand Down Expand Up @@ -166,14 +181,13 @@ abstract class LicensesTask extends DefaultTask {
&& Integer.valueOf(versions[0]) >= GRANULAR_BASE_VERSION)
}

protected void addGooglePlayServiceLicenses(ArtifactInfo artifactInfo) {
// We look up the artifact file using the pre-resolved map provided during configuration.
ArtifactFiles files = getArtifactFiles().get().get(artifactInfo.toString())
if (files == null || files.libraryFile == null || !files.libraryFile.exists()) {
protected void addGooglePlayServiceLicenses(Map<String, File> libraryMap, ArtifactInfo artifactInfo) {
File libraryFile = libraryMap.get(artifactInfo.toString())
if (libraryFile == null || !libraryFile.exists()) {
logger.warn("Unable to find Google Play Services Artifact for $artifactInfo")
return
}
addGooglePlayServiceLicenses(files.libraryFile)
addGooglePlayServiceLicenses(libraryFile)
}

protected void addGooglePlayServiceLicenses(File artifactFile) {
Expand Down Expand Up @@ -242,15 +256,14 @@ abstract class LicensesTask extends DefaultTask {
}
}

protected void addLicensesFromPom(ArtifactInfo artifactInfo) {
// We look up the POM file using the pre-resolved map provided during configuration.
ArtifactFiles files = getArtifactFiles().get().get(artifactInfo.toString())
addLicensesFromPom(files?.pomFile, artifactInfo.group, artifactInfo.name)
protected void addLicensesFromPom(Map<String, File> pomMap, ArtifactInfo artifactInfo) {
File pomFile = pomMap.get(artifactInfo.toString())
addLicensesFromPom(pomFile, artifactInfo.group, artifactInfo.name)
}

protected void addLicensesFromPom(File pomFile, String group, String name) {
if (pomFile == null || !pomFile.exists()) {
logger.error("POM file $pomFile for $group:$name does not exist.")
logger.info("POM file $pomFile for $group:$name does not exist. This is expected for some libraries from androidx and org.jetbrains")
return
}

Expand Down
Loading
Loading