Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add android library subproject dependencies to the report #134

Merged
merged 2 commits into from
Dec 30, 2020
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
62 changes: 34 additions & 28 deletions src/main/kotlin/com/jaredsburrows/license/LicenseReportTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import java.util.UUID
import org.gradle.api.DefaultTask
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.logging.LogLevel
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
Expand Down Expand Up @@ -96,38 +98,16 @@ open class LicenseReportTask : DefaultTask() { // tasks can't be final

// If Android project, add extra configurations
variantName?.let { variant ->
// Add buildType configurations
configurations.find { it.name == "compile" }?.let {
configurationSet.add(configurations.getByName("${buildType}Compile"))
}
configurations.find { it.name == "api" }?.let {
configurationSet.add(configurations.getByName("${buildType}Api"))
}
configurations.find { it.name == "implementation" }?.let {
configurationSet.add(configurations.getByName("${buildType}Implementation"))
}

// Add productFlavors configurations
productFlavors.forEach { flavor ->
// Works for productFlavors and productFlavors with dimensions
if (variant.capitalize().contains(flavor.name.capitalize())) {
configurations.find { it.name == "compile" }?.let {
configurationSet.add(configurations.getByName("${flavor.name}Compile"))
}
configurations.find { it.name == "api" }?.let {
configurationSet.add(configurations.getByName("${flavor.name}Api"))
}
configurations.find { it.name == "implementation" }?.let {
configurationSet.add(configurations.getByName("${flavor.name}Implementation"))
}
}
configurations.find { it.name == "${variant}RuntimeClasspath" }?.also {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much cleaner, we no longer need to check for any "special" flavors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what do you mean by "special" flavors. This is based on my observations that all the dependencies can be found in ${variant}RuntimeClasspath configuration, including subproject dependencies which were missing in the other ones. I've seen there are some tests for flavored builds and since they have passed I assumed we should be fine.

Actually I've seen Google is doing it bit differently in their oss licenses plugin. Instead of specifying configuration that should be taken into account in license generation, they're taking all the configurations and excluding only some of them like the ones that cannot be resolved or tests. Not sure which approach is better though.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok thanks. I did not know about that plugin.

Copy link
Contributor Author

@mkubiczek mkubiczek Dec 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that plugin but it has its flaws. Eg it cannot only be applied to a project of type com.android.application but not when it's com.android.library which is my use case.

configurationSet.add(it)
}
}

// Iterate through all the configurations's dependencies
configurationSet.forEach { set ->
if (set.isCanBeResolved) {
set.resolvedConfiguration.lenientConfiguration.artifacts.forEach { artifact ->
configurationSet.forEach { configuration ->
if (configuration.isCanBeResolved) {
val allDeps = configuration.resolvedConfiguration.lenientConfiguration.allModuleDependencies
getResolvedArtifactsFromResolvedDependencies(allDeps).forEach { artifact ->
val id = artifact.moduleVersion.id
val gav = "${id.group}:${id.name}:${id.version}@pom"
configurations.getByName(pomConfiguration).dependencies.add(
Expand All @@ -138,6 +118,32 @@ open class LicenseReportTask : DefaultTask() { // tasks can't be final
}
}

private fun getResolvedArtifactsFromResolvedDependencies(
resolvedDependencies: Set<ResolvedDependency>
): Set<ResolvedArtifact> {
val resolvedArtifacts = hashSetOf<ResolvedArtifact>()
for (resolvedDependency in resolvedDependencies) {
try {
if (resolvedDependency.moduleVersion == "unspecified") {
/**
* Attempting to getAllModuleArtifacts on a local library project will result
* in AmbiguousVariantSelectionException as there are not enough criteria
* to match a specific variant of the library project. Instead we skip the
* the library project itself and enumerate its dependencies.
*/
resolvedArtifacts.addAll(
getResolvedArtifactsFromResolvedDependencies(resolvedDependency.children)
)
} else {
resolvedArtifacts.addAll(resolvedDependency.allModuleArtifacts)
}
} catch (e: Exception) {
logger.warn("Failed to process $resolvedDependency.name", e)
}
}
return resolvedArtifacts
}

/** Get POM information from the dependency artifacts. */
private fun generatePOMInfo() {
// Iterate through all POMs in order from our custom POM configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,144 @@ final class LicensePluginAndroidSpec extends Specification {
taskName << ['licenseDebugReport', 'licenseReleaseReport']
}

@Unroll def '#taskName with default buildTypes, multi module and android and android'() {
given:
testProjectDir.newFile('settings.gradle') <<
"""
include 'subproject'
"""

buildFile <<
"""
buildscript {
dependencies {
classpath files($classpathString)
}
}
allprojects {
repositories {
maven {
url '${mavenRepoUrl}'
}
}
}
apply plugin: 'com.android.application'
apply plugin: 'com.jaredsburrows.license'
android {
compileSdkVersion 28
defaultConfig {
applicationId 'com.example'
}
}
dependencies {
api project(':subproject')
implementation 'group:name:1.0.0'
}
project(':subproject') {
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
}
dependencies {
implementation 'com.android.support:design:26.1.0'
}
}
"""

when:
def result = gradleWithCommand(testProjectDir.root, "${taskName}", '-s')
def actualHtml = new File("${reportFolder}/${taskName}.html").text
def expectedHtml =
"""
<html>
<head>
<style>body { font-family: sans-serif } pre { background-color: #eeeeee; padding: 1em; white-space: pre-wrap; display: inline-block }</style>
<title>Open source licenses</title>
</head>
<body>
<h3>Notice for packages:</h3>
<ul>
<li><a href="#1934118923">design (26.1.0)</a>
<dl>
<dt>Copyright &copy; 20xx The original author or authors</dt>
</dl>
</li>
<a name="1934118923"></a>
<pre>${myGetLicenseText('apache-2.0.txt')}</pre>
<br>
<hr>
<li><a href="#-296292112">Fake dependency name (1.0.0)</a>
<dl>
<dt>Copyright &copy; 2017 name</dt>
</dl>
</li>
<a name="-296292112"></a>
<pre>Some license
<a href="http://website.tld/">http://website.tld/</a></pre>
<br>
<hr>
</ul>
</body>
</html>
"""
def actualJson = new File("${reportFolder}/${taskName}.json").text
def expectedJson =
"""
[
{
"project":"design",
"description":null,
"version":"26.1.0",
"developers":[],
"url":null,
"year":null,
"licenses":[
{
"license":"The Apache Software License",
"license_url":"http://www.apache.org/licenses/LICENSE-2.0.txt"
}
],
"dependency":"com.android.support:design:26.1.0"
},
{
"project":"Fake dependency name",
"description":"Fake dependency description",
"version":"1.0.0",
"developers":[
"name"
],
"url":"https://github.com/user/repo",
"year":"2017",
"licenses":[
{
"license":"Some license",
"license_url":"http://website.tld/"
}
],
"dependency":"group:name:1.0.0"
}
]
"""

then:
result.task(":${taskName}").outcome == SUCCESS
result.output.find("Wrote HTML report to .*${reportFolder}/${taskName}.html.")
result.output.find("Wrote JSON report to .*${reportFolder}/${taskName}.json.")
assertHtml(expectedHtml, actualHtml)
assertJson(expectedJson, actualJson)

where:
taskName << ['licenseDebugReport', 'licenseReleaseReport']
}

@Unroll def '#taskName with reports enabled and copy enabled #copyEnabled'() {
given:
buildFile <<
Expand Down