Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.gradle.api.tasks.TaskAction
import org.slf4j.LoggerFactory

import java.util.zip.ZipEntry
import java.util.zip.ZipException
import java.util.zip.ZipFile

/**
Expand All @@ -46,18 +47,13 @@ abstract class LicensesTask extends DefaultTask {
private static final String UTF_8 = "UTF-8"
private static final byte[] LINE_SEPARATOR = System
.getProperty("line.separator").getBytes(UTF_8)
private static final int GRANULAR_BASE_VERSION = 14
private static final String GOOGLE_PLAY_SERVICES_GROUP =
"com.google.android.gms"
private static final String LICENSE_ARTIFACT_SUFFIX = "-license"
private static final String FIREBASE_GROUP = "com.google.firebase"
private static final String FAIL_READING_LICENSES_ERROR =
"Failed to read license text."

private static final logger = LoggerFactory.getLogger(LicensesTask.class)

protected int start = 0
protected Set<String> googleServiceLicenses = []
protected Set<String> embeddedLicenses = []
protected Map<String, String> licensesMap = [:]
protected Map<String, String> licenseOffsets = [:]
protected static final String ABSENT_DEPENDENCY_KEY = "Debug License Info"
Expand Down Expand Up @@ -126,22 +122,18 @@ abstract class LicensesTask extends DefaultTask {
addDebugLicense()
} else {
for (artifactInfo in artifactInfoSet) {
if (isGoogleServices(artifactInfo.group)) {
// Add license info for google-play-services itself
if (!artifactInfo.name.endsWith(LICENSE_ARTIFACT_SUFFIX)) {
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(libraryMap, artifactInfo)
}
} else {
// 1. Extract licenses from POM for all artifacts, except for side-car license artifacts.
// Artifacts named "*-license" are containers for license data and shouldn't have
// their own entry in the attribution list.
if (!artifactInfo.name.endsWith("-license")) {
addLicensesFromPom(pomMap, artifactInfo)
}

// 2. For any artifact, try to extract embedded licenses if they exist.
File libraryFile = libraryMap.get(artifactInfo.toString())
if (libraryFile != null && libraryFile.exists()) {
addEmbeddedLicenses(libraryFile)
}
}
}

Expand Down Expand Up @@ -181,59 +173,46 @@ abstract class LicensesTask extends DefaultTask {
}
}

protected static boolean isGoogleServices(String group) {
return (GOOGLE_PLAY_SERVICES_GROUP.equalsIgnoreCase(group)
|| FIREBASE_GROUP.equalsIgnoreCase(group))
}

protected static boolean isGranularVersion(String version) {
String[] versions = version.split("\\.")
return (versions.length > 0
&& Integer.valueOf(versions[0]) >= GRANULAR_BASE_VERSION)
}

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(libraryFile)
}

protected void addGooglePlayServiceLicenses(File artifactFile) {
ZipFile licensesZip = new ZipFile(artifactFile)

ZipEntry jsonFile = licensesZip.getEntry("third_party_licenses.json")
ZipEntry txtFile = licensesZip.getEntry("third_party_licenses.txt")
protected void addEmbeddedLicenses(File artifactFile) {
try {
new ZipFile(artifactFile).withCloseable { licensesZip ->
ZipEntry jsonFile = licensesZip.getEntry("third_party_licenses.json")
ZipEntry txtFile = licensesZip.getEntry("third_party_licenses.txt")

if (!jsonFile || !txtFile) {
return
}
if (!jsonFile || !txtFile) {
return
}

JsonSlurper jsonSlurper = new JsonSlurper()
Object licensesObj = licensesZip.getInputStream(jsonFile).withCloseable {
jsonSlurper.parse(it)
}
if (licensesObj == null) {
return
}
JsonSlurper jsonSlurper = new JsonSlurper()
Object licensesObj = licensesZip.getInputStream(jsonFile).withCloseable {
jsonSlurper.parse(it)
}
if (licensesObj == null) {
return
}

for (entry in licensesObj) {
String key = entry.key
int startValue = entry.value.start
int lengthValue = entry.value.length

if (!googleServiceLicenses.contains(key)) {
licensesZip.getInputStream(txtFile).withCloseable {
byte[] content = getBytesFromInputStream(
it,
startValue,
lengthValue)
googleServiceLicenses.add(key)
appendDependency(key, content)
for (entry in licensesObj) {
String key = entry.key
int startValue = entry.value.start
int lengthValue = entry.value.length

if (!embeddedLicenses.contains(key)) {
licensesZip.getInputStream(txtFile).withCloseable {
byte[] content = getBytesFromInputStream(
it,
startValue,
lengthValue)
embeddedLicenses.add(key)
appendDependency(key, content)
}
}
}
}
} catch (ZipException e) {
// Not a zip file, or malformed. Skip.
logger.debug("Failed to open $artifactFile as a zip file: ${e.message}")
} catch (IOException e) {
logger.warn("Failed to read embedded licenses from $artifactFile: ${e.message}")
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,6 @@ public void testInitOutputDir() throws IOException {
assertEquals(0, Files.size(licensesMetadata.toPath()));
}

@Test
public void testIsGranularVersion_True() {
String versionTrue = "14.6.0";
assertTrue(LicensesTask.isGranularVersion(versionTrue));
}

@Test
public void testIsGranularVersion_False() {
String versionFalse = "11.4.0";
assertFalse(LicensesTask.isGranularVersion(versionFalse));
}

@Test
public void testAddLicensesFromPom() throws IOException {
File deps1 = getResourceFile("dependencies/groupA/deps1.pom");
Expand Down Expand Up @@ -222,28 +210,29 @@ public void testGetBytesFromInputStream_specialCharacters() {
}

@Test
public void testAddGooglePlayServiceLicenses() throws IOException {
public void testAddEmbeddedLicenses() throws IOException {
File tempOutput = temporaryFolder.newFolder();
tempOutput.mkdirs();
createLicenseZip(tempOutput.getPath() + "play-services-foo-license.aar");
File artifact = new File(tempOutput.getPath() + "play-services-foo-license.aar");
String artifactName = "test-artifact.aar";
createLicenseZip(tempOutput.getPath() + "/" + artifactName);
File artifact = new File(tempOutput.getPath() + "/" + artifactName);

licensesTask.initOutputDir();
licensesTask.addGooglePlayServiceLicenses(artifact);
licensesTask.addEmbeddedLicenses(artifact);

String content = new String(Files.readAllBytes(licensesTask.getLicenses().toPath()), UTF_8);
String expected = "safeparcel" + LINE_BREAK + "JSR 305" + LINE_BREAK;
assertEquals(expected, content);
assertThat(licensesTask.googleServiceLicenses.size(), is(2));
assertTrue(licensesTask.googleServiceLicenses.contains("safeparcel"));
assertTrue(licensesTask.googleServiceLicenses.contains("JSR 305"));
assertThat(licensesTask.embeddedLicenses.size(), is(2));
assertTrue(licensesTask.embeddedLicenses.contains("safeparcel"));
assertTrue(licensesTask.embeddedLicenses.contains("JSR 305"));
assertThat(licensesTask.licensesMap.size(), is(2));
assertTrue(licensesTask.licensesMap.containsKey("safeparcel"));
assertTrue(licensesTask.licensesMap.containsKey("JSR 305"));
}

@Test
public void testAddGooglePlayServiceLicenses_withoutDuplicate() throws IOException {
public void testAddEmbeddedLicenses_withoutDuplicate() throws IOException {
File groupC = temporaryFolder.newFolder();
groupC.mkdirs();
createLicenseZip(groupC.getPath() + "/play-services-foo-license.aar");
Expand All @@ -255,20 +244,106 @@ public void testAddGooglePlayServiceLicenses_withoutDuplicate() throws IOExcepti
File artifactBar = new File(groupD.getPath() + "/play-services-bar-license.aar");

licensesTask.initOutputDir();
licensesTask.addGooglePlayServiceLicenses(artifactFoo);
licensesTask.addGooglePlayServiceLicenses(artifactBar);
licensesTask.addEmbeddedLicenses(artifactFoo);
licensesTask.addEmbeddedLicenses(artifactBar);

String content = new String(Files.readAllBytes(licensesTask.getLicenses().toPath()), UTF_8);
String expected = "safeparcel" + LINE_BREAK + "JSR 305" + LINE_BREAK;
assertEquals(expected, content);
assertThat(licensesTask.googleServiceLicenses.size(), is(2));
assertTrue(licensesTask.googleServiceLicenses.contains("safeparcel"));
assertTrue(licensesTask.googleServiceLicenses.contains("JSR 305"));
assertThat(licensesTask.embeddedLicenses.size(), is(2));
assertTrue(licensesTask.embeddedLicenses.contains("safeparcel"));
assertTrue(licensesTask.embeddedLicenses.contains("JSR 305"));
assertThat(licensesTask.licensesMap.size(), is(2));
assertTrue(licensesTask.licensesMap.containsKey("safeparcel"));
assertTrue(licensesTask.licensesMap.containsKey("JSR 305"));
}

@Test
public void action_nonGmsGranularLibrary_extractsTransitiveLicenses() throws Exception {
// 1. Setup a non-GMS granular artifact
File artifactDir = temporaryFolder.newFolder("artifacts");
String gav = "com.example:granular-lib:1.0.0";
String artifactName = "granular-lib-1.0.0.aar";
File artifactFile = new File(artifactDir, artifactName);
createLicenseZip(artifactFile.getAbsolutePath());

// 2. Setup POM file (empty or with its own license)
File pomDir = temporaryFolder.newFolder("poms");
File pomFile = new File(pomDir, "granular-lib-1.0.0.pom");
try (FileWriter writer = new FileWriter(pomFile)) {
writer.write("<project><name>Granular Lib</name><licenses><license><name>Apache 2.0</name><url>http://www.apache.org/licenses/LICENSE-2.0</url></license></licenses></project>");
}

// 3. Setup dependencies.json
File dependenciesJson = temporaryFolder.newFile("dependencies.json");
ArtifactInfo[] artifactInfoArray = new ArtifactInfo[] {
new ArtifactInfo("com.example", "granular-lib", "1.0.0")
};
Gson gson = new Gson();
try (FileWriter writer = new FileWriter(dependenciesJson)) {
gson.toJson(artifactInfoArray, writer);
}

// 4. Configure task inputs
licensesTask.getDependenciesJson().set(dependenciesJson);
licensesTask.getLibraryFilesByGav().put(gav, artifactFile);
licensesTask.getPomFilesByGav().put(gav, pomFile);

// 5. Execute action
licensesTask.action();

// 6. Verify results
String content = new String(Files.readAllBytes(licensesTask.getLicenses().toPath()), UTF_8);

// It should contain BOTH the POM license and the granular transitive licenses
assertTrue("Should contain POM license", content.contains("http://www.apache.org/licenses/LICENSE-2.0"));
assertTrue("Should contain Granular license 'safeparcel'", content.contains("safeparcel"));
assertTrue("Should contain Granular license 'JSR 305'", content.contains("JSR 305"));
}

@Test
public void action_licenseSidecarArtifact_skipsPomButExtractsEmbedded() throws Exception {
// 1. Setup a side-car license artifact
File artifactDir = temporaryFolder.newFolder("sidecar_artifacts");
String gav = "com.example:sidecar-license:1.0.0";
String artifactName = "sidecar-license-1.0.0.aar";
File artifactFile = new File(artifactDir, artifactName);
createLicenseZip(artifactFile.getAbsolutePath());

// 2. Setup POM file with a license URL (which should be ignored)
File pomDir = temporaryFolder.newFolder("sidecar_poms");
File pomFile = new File(pomDir, "sidecar-license-1.0.0.pom");
try (FileWriter writer = new FileWriter(pomFile)) {
writer.write("<project><name>Sidecar License</name><licenses><license><name>Ignored POM License</name><url>http://ignored-pom-url.com</url></license></licenses></project>");
}

// 3. Setup dependencies.json
File dependenciesJson = temporaryFolder.newFile("sidecar_dependencies.json");
ArtifactInfo[] artifactInfoArray = new ArtifactInfo[] {
new ArtifactInfo("com.example", "sidecar-license", "1.0.0")
};
Gson gson = new Gson();
try (FileWriter writer = new FileWriter(dependenciesJson)) {
gson.toJson(artifactInfoArray, writer);
}

// 4. Configure task inputs
licensesTask.getDependenciesJson().set(dependenciesJson);
licensesTask.getLibraryFilesByGav().put(gav, artifactFile);
licensesTask.getPomFilesByGav().put(gav, pomFile);

// 5. Execute action
licensesTask.action();

// 6. Verify results
String content = new String(Files.readAllBytes(licensesTask.getLicenses().toPath()), UTF_8);

// It should NOT contain the POM license but SHOULD contain the granular transitive licenses
assertFalse("Should NOT contain POM license for sidecar", content.contains("http://ignored-pom-url.com"));
assertTrue("Should contain Granular license 'safeparcel'", content.contains("safeparcel"));
assertTrue("Should contain Granular license 'JSR 305'", content.contains("JSR 305"));
}

private void createLicenseZip(String name) throws IOException {
File zipFile = new File(name);
ZipOutputStream output = new ZipOutputStream(new FileOutputStream(zipFile));
Expand Down
Loading