diff --git a/build.gradle b/build.gradle index 922c4db..4fd2bca 100644 --- a/build.gradle +++ b/build.gradle @@ -1,30 +1,29 @@ // First, apply the publishing plugin buildscript { - repositories { - maven { - url "https://plugins.gradle.org/m2/" - jcenter() + repositories { + gradlePluginPortal() + google() + jcenter() + } + dependencies { + classpath "com.gradle.publish:plugin-publish-plugin:0.9.1" + classpath 'com.netflix.nebula:gradle-extra-configurations-plugin:2.2.2' } - } - dependencies { - classpath "com.gradle.publish:plugin-publish-plugin:0.9.1" - classpath 'com.netflix.nebula:gradle-extra-configurations-plugin:2.2.+' - } } plugins { - id 'com.gradle.build-scan' version '1.16' - id 'groovy' - id 'idea' - id "org.ajoberstar.release-opinion" version "1.4.2" - id 'com.gradle.plugin-publish' version '0.10.0' - id 'java-gradle-plugin' - id 'ru.vyarus.animalsniffer' version '1.4.2' + id 'com.gradle.build-scan' version '2.0.2' + id 'groovy' + id 'idea' + id "org.ajoberstar.release-opinion" version "1.4.2" + id 'com.gradle.plugin-publish' version '0.10.0' + id 'java-gradle-plugin' + id 'ru.vyarus.animalsniffer' version '1.5.1' } buildScan { - termsOfServiceUrl = 'https://gradle.com/terms-of-service' - termsOfServiceAgree = 'yes' + termsOfServiceUrl = 'https://gradle.com/terms-of-service' + termsOfServiceAgree = 'yes' } apply plugin: 'com.github.hierynomus.license' @@ -36,7 +35,12 @@ group = 'com.hierynomus.gradle.plugins' sourceCompatibility = 1.7 targetCompatibility = 1.7 -repositories { jcenter() } + +repositories { + gradlePluginPortal() + google() + jcenter() +} idea { module { @@ -46,24 +50,24 @@ idea { } release { - grgit = org.ajoberstar.grgit.Grgit.open(project.projectDir) + grgit = org.ajoberstar.grgit.Grgit.open(project.projectDir) } configurations.compile.transitive = false dependencies { - signature 'org.codehaus.mojo.signature:java17:1.0@signature' + signature 'org.codehaus.mojo.signature:java17:1.0@signature' compile "org.codehaus.plexus:plexus-utils:2.0.5" compile "com.mycila.xmltool:xmltool:3.3" // Using compile instead of groovy, so that it goes into the pom - compile ('com.mycila:license-maven-plugin:3.0') { + compile('com.mycila:license-maven-plugin:3.0') { exclude group: 'org.apache.maven', module: 'maven-plugin-api' exclude group: 'org.apache.maven', module: 'maven-project' } compile gradleApi() - def androidGradlePlugin = 'com.android.tools.build:gradle:2.0.+' + def androidGradlePlugin = 'com.android.tools.build:gradle:3.6.3' compileOnly androidGradlePlugin testCompile androidGradlePlugin @@ -97,14 +101,19 @@ license { ignoreFailures true } +animalsniffer { + excludeJars 'gradle-api-*' + ignoreFailures true +} + test { - afterSuite { descriptor, result -> - def indicator = "\u001B[32m✓\u001b[0m" - if (result.failedTestCount > 0) { - indicator = "\u001B[31m✘\u001b[0m" + afterSuite { descriptor, result -> + def indicator = "\u001B[32m✓\u001b[0m" + if (result.failedTestCount > 0) { + indicator = "\u001B[31m✘\u001b[0m" + } + logger.lifecycle("$indicator Test ${descriptor.name}; Executed: ${result.testCount}/\u001B[32m${result.successfulTestCount}\u001B[0m/\u001B[31m${result.failedTestCount}\u001B[0m") } - logger.lifecycle("$indicator Test ${descriptor.name}; Executed: ${result.testCount}/\u001B[32m${result.successfulTestCount}\u001B[0m/\u001B[31m${result.failedTestCount}\u001B[0m") - } } def pomConfig = { @@ -181,7 +190,7 @@ pluginBundle { licenseReportPlugin { displayName = "License Report plugin for Gradle" description = "Reports over licenses" - tags = [ "gradle", "plugin", "license", "report" ] + tags = ["gradle", "plugin", "license", "report"] } } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 1c1ff09..6fd11e8 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -1,6 +1,8 @@ apply plugin: 'groovy' repositories { + maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://dl.google.com/dl/android/maven2/" } jcenter() } @@ -9,13 +11,13 @@ configurations.compile.transitive = false dependencies { compile "org.codehaus.plexus:plexus-utils:2.0.5" compile "com.mycila.xmltool:xmltool:3.3" - compile ('com.mycila:license-maven-plugin:3.0') { + compile('com.mycila:license-maven-plugin:3.0') { exclude group: 'org.apache.maven', module: 'maven-plugin-api' exclude group: 'org.apache.maven', module: 'maven-project' } compile gradleApi() - compileOnly 'com.android.tools.build:gradle:2.0.+' + compileOnly 'com.android.tools.build:gradle:3.6.3' } sourceSets { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index db3eeb2..ea21908 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Dec 08 16:48:23 EST 2014 +#Mon Apr 20 15:51:30 BST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/src/main/groovy/nl/javadude/gradle/plugins/license/LicenseResolver.groovy b/src/main/groovy/nl/javadude/gradle/plugins/license/LicenseResolver.groovy index b5ad1a8..8e6beb8 100644 --- a/src/main/groovy/nl/javadude/gradle/plugins/license/LicenseResolver.groovy +++ b/src/main/groovy/nl/javadude/gradle/plugins/license/LicenseResolver.groovy @@ -20,19 +20,22 @@ import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.FileCollectionDependency +import org.gradle.api.artifacts.ResolveException import org.gradle.api.artifacts.ResolvedArtifact +import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging import java.util.regex.Pattern import static DependencyMetadata.noLicenseMetaData + /** * License resolver for dependencies. */ class LicenseResolver { - private static final Logger logger = Logging.getLogger(LicenseResolver); + private static final Logger logger = Logging.getLogger(LicenseResolver) /** * Reference to gradle project. @@ -45,6 +48,12 @@ class LicenseResolver { private boolean ignoreFatalParseErrors private List patternsToIgnore + protected static final String LOCAL_LIBRARY_VERSION = "unspecified" + private static final String TEST_PREFIX = "test" + private static final String ANDROID_TEST_PREFIX = "androidTest" + private static final Set TEST_COMPILE = ["testCompile", "androidTestCompile"] + private static final Set PACKAGED_DEPENDENCIES_PREFIXES = ["compile", "implementation", "api"] + /** * Provide set with dependencies metadata. * @@ -55,7 +64,7 @@ class LicenseResolver { */ public Set provideLicenseMap4Dependencies() { Set licenseSet = new HashSet() - def subprojects = project.rootProject.subprojects.groupBy { Project p -> "$p.group:$p.name:$p.version".toString()} + def subprojects = project.rootProject.subprojects.groupBy { Project p -> "$p.group:$p.name:$p.version".toString() } Set projects = new HashSet() projects.add(project) @@ -64,68 +73,68 @@ class LicenseResolver { projects.each { p -> - // Resolve each dependency - resolveProjectDependencies(p).each { - rd -> - String dependencyDesc = "$rd.moduleVersion.id.group:$rd.moduleVersion.id.name:$rd.moduleVersion.id.version".toString() - Map.Entry licenseEntry = licenses.find { - dep -> - if(dep.key instanceof String) { - dep.key == dependencyDesc - } else if (dep.key instanceof DependencyGroup) { - rd.moduleVersion.id.group == dep.key.group - } - } - if (licenseEntry != null) { - def license = licenseEntry.value - def licenseMetadata = license instanceof String ? DownloadLicensesExtension.license(license) : license - licenseSet << new DependencyMetadata( - dependency: dependencyDesc, dependencyFileName: rd.file.name, licenseMetadataList: [ licenseMetadata ] - ) - } else { - Closure dependencyMetadata = { - if(!subprojects[dependencyDesc]) { - def depMetadata = retrieveLicensesForDependency(p, dependencyDesc) - depMetadata.dependencyFileName = rd.file.name - depMetadata - } else { - noLicenseMetaData(dependencyDesc, rd.file.name) + // Resolve each dependency + resolveProjectDependencies(p).each { + rd -> + String dependencyDesc = "$rd.moduleVersion.id.group:$rd.moduleVersion.id.name:$rd.moduleVersion.id.version".toString() + Map.Entry licenseEntry = licenses.find { + dep -> + if (dep.key instanceof String) { + dep.key == dependencyDesc + } else if (dep.key instanceof DependencyGroup) { + rd.moduleVersion.id.group == dep.key.group + } } - } + if (licenseEntry != null) { + def license = licenseEntry.value + def licenseMetadata = license instanceof String ? DownloadLicensesExtension.license(license) : license + licenseSet << new DependencyMetadata( + dependency: dependencyDesc, dependencyFileName: rd.file.name, licenseMetadataList: [licenseMetadata] + ) + } else { + Closure dependencyMetadata = { + if (!subprojects[dependencyDesc]) { + def depMetadata = retrieveLicensesForDependency(p, dependencyDesc) + depMetadata.dependencyFileName = rd.file.name + depMetadata + } else { + noLicenseMetaData(dependencyDesc, rd.file.name) + } + } - licenseSet << dependencyMetadata() + licenseSet << dependencyMetadata() + } } - } - provideFileDependencies(p).each { - fileDependency -> - Closure licenseMetadata = { - if (licenses.containsKey(fileDependency)) { - def license = licenses[fileDependency] - LicenseMetadata licenseMetadata = license instanceof String ? DownloadLicensesExtension.license(license) : license - def alias = aliases.find { - aliasEntry -> - aliasEntry.value.any { - aliasElem -> - if (aliasElem instanceof String) { - return aliasElem == licenseMetadata.licenseName - } else if(aliasElem instanceof LicenseMetadata) { - return aliasElem == licenseMetadata - } - - } - } - if (alias) { - licenseMetadata = alias.key + provideFileDependencies(p).each { + fileDependency -> + Closure licenseMetadata = { + if (licenses.containsKey(fileDependency)) { + def license = licenses[fileDependency] + LicenseMetadata licenseMetadata = license instanceof String ? DownloadLicensesExtension.license(license) : license + def alias = aliases.find { + aliasEntry -> + aliasEntry.value.any { + aliasElem -> + if (aliasElem instanceof String) { + return aliasElem == licenseMetadata.licenseName + } else if (aliasElem instanceof LicenseMetadata) { + return aliasElem == licenseMetadata + } + + } + } + if (alias) { + licenseMetadata = alias.key + } + new DependencyMetadata(dependency: fileDependency, dependencyFileName: fileDependency, licenseMetadataList: [licenseMetadata]) + } else { + noLicenseMetaData(fileDependency, fileDependency) } - new DependencyMetadata(dependency: fileDependency, dependencyFileName: fileDependency, licenseMetadataList: [licenseMetadata]) - } else { - noLicenseMetaData(fileDependency, fileDependency) } - } - licenseSet << licenseMetadata() - } + licenseSet << licenseMetadata() + } } licenseSet @@ -134,29 +143,26 @@ class LicenseResolver { /** * Provide full list of resolved artifacts to handle for a given project. * - * @param project the project + * @param project the project * @return Set with resolved artifacts */ Set resolveProjectDependencies(Project project) { - Set dependenciesToHandle = new HashSet() - def subprojects = project.rootProject.subprojects.groupBy { Project p -> "$p.group:$p.name:$p.version".toString()} - if (project.configurations.any { it.name == dependencyConfiguration && isResolvable(it) }) { + if (project.configurations.any { it.name == dependencyConfiguration }) { def configuration = project.configurations.getByName(dependencyConfiguration) - configuration.resolvedConfiguration.resolvedArtifacts.each { ResolvedArtifact d -> - String dependencyDesc = "$d.moduleVersion.id.group:$d.moduleVersion.id.name:$d.moduleVersion.id.version".toString() - if(isDependencyIncluded(dependencyDesc)) { - Project subproject = subprojects[dependencyDesc]?.first() - if (subproject) { - if(includeProjectDependencies) { - dependenciesToHandle.add(d) - } - dependenciesToHandle.addAll(resolveProjectDependencies(subproject)) - } else if (!subproject) { - dependenciesToHandle.add(d) - } - } + + Set dependencies = getResolvedArtifacts(configuration) + dependenciesToHandle.addAll(dependencies) + + println(project.name + " -> " + configuration.name + " -> " + dependencies.size()) + } else if (dependencyConfiguration == 'all') { + // Hack to look at configurations + project.configurations.each { Configuration configuration -> + Set dependencies = getResolvedArtifacts(configuration) + dependenciesToHandle.addAll(dependencies) + + println(project.name + " -> " + configuration.name + " -> " + dependencies.size()) } } @@ -164,6 +170,50 @@ class LicenseResolver { dependenciesToHandle } + Set getResolvedArtifacts(Configuration configuration) { + if (!isResolvable(configuration) || isTest(configuration) || !isPackagedDependency(configuration)) { + logger.warn("Failed to resolve OSS licenses for $configuration.name.") + } else { + try { + return getResolvedArtifactsFromResolvedDependencies( + configuration.getResolvedConfiguration() + .getLenientConfiguration() + .getFirstLevelModuleDependencies()) + + } catch (ResolveException exception) { + logger.warn("Failed to resolve OSS licenses for $configuration.name.", exception) + } catch (Exception exception) { + logger.warn("Failed to resolve OSS licenses for $configuration.name.", exception) + } + } + + return new HashSet() + } + + protected Set getResolvedArtifactsFromResolvedDependencies(Set resolvedDependencies) { + HashSet resolvedArtifacts = new HashSet<>() + + for (resolvedDependency in resolvedDependencies) { + try { + if (resolvedDependency.getModuleVersion() == LOCAL_LIBRARY_VERSION) { + /** + * 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.getChildren())) + } else { + resolvedArtifacts.addAll(resolvedDependency.getAllModuleArtifacts()) + } + } catch (Exception exception) { + logger.warn("Failed to process $resolvedDependency.name", exception) + } + } + return resolvedArtifacts + } + + Set provideFileDependencies(Project project) { Set fileDependencies = new HashSet() @@ -195,19 +245,46 @@ class LicenseResolver { * @param conf Configuration * @return whether conf is resolvable * - * @see Gradle 3.4 release notes + * @see Gradle 3.4 release notes */ boolean isResolvable(Configuration conf) { return conf.metaClass.respondsTo(conf, "isCanBeResolved") ? conf.isCanBeResolved() : true } - boolean isDependencyIncluded(String depName){ - for(Pattern pattern: this.patternsToIgnore){ - if(pattern.matcher(depName).matches()){ - return false; + /** + * Checks if the configuration is from test. + * @param configuration + * @return true if configuration is a test configuration or its parent + * configurations are either testCompile or androidTestCompile, otherwise + * false. + */ + protected boolean isTest(Configuration configuration) { + boolean isTestConfiguration = (configuration.name.startsWith(TEST_PREFIX) || configuration.name.startsWith(ANDROID_TEST_PREFIX)) + return isTestConfiguration || configuration.hierarchy.any { TEST_COMPILE.contains(it.name) } + } + + /** + * Checks if the configuration is for a packaged dependency (rather than e.g. a build or test time dependency) + * @param configuration + * @return true if the configuration is in the set of @link #BINARY_DEPENDENCIES + */ + protected boolean isPackagedDependency(Configuration configuration) { + boolean isPackagedDependency = PACKAGED_DEPENDENCIES_PREFIXES.any { + configuration.name.startsWith(it) + } + configuration.hierarchy.each { + String configurationHierarchyName = it.name + isPackagedDependency |= PACKAGED_DEPENDENCIES_PREFIXES.any { + configurationHierarchyName.startsWith(it) } } - return true; + + return isPackagedDependency + } + + + boolean isDependencyIncluded(String depName) { + return !patternsToIgnore.any { it.matcher(depName).matches() } } @@ -221,7 +298,7 @@ class LicenseResolver { * Implementation note: We rely that while resolving configuration with one dependency we get one pom. * Otherwise we have IllegalStateException * - * @param project the project + * @param project the project * @param dependencyDesc dependency description * @param aliases alias mapping for similar license names * @param initialDependency base dependency (not parent) @@ -266,8 +343,8 @@ class LicenseResolver { aliasEntry.value.any { aliasElem -> if (aliasElem instanceof String) { - return aliasElem == license.licenseName - } else if(aliasElem instanceof LicenseMetadata) { + return aliasElem == license.licenseName + } else if (aliasElem instanceof LicenseMetadata) { return aliasElem == license } @@ -292,14 +369,14 @@ class LicenseResolver { } } - void setDependenciesToIgnore(List dependenciesToIgnore){ - if(dependenciesToIgnore == null){ + void setDependenciesToIgnore(List dependenciesToIgnore) { + if (dependenciesToIgnore == null) { this.patternsToIgnore = Collections.emptyList(); return; } this.patternsToIgnore = new ArrayList<>(dependenciesToIgnore.size()); - for(String toIgnore: dependenciesToIgnore){ + for (String toIgnore : dependenciesToIgnore) { this.patternsToIgnore.add(Pattern.compile(toIgnore)) } }