From c3d9a18aad74f120c855eee5db2dfa8bd74160d0 Mon Sep 17 00:00:00 2001 From: Carl Mai Date: Wed, 10 Apr 2024 22:13:22 +0200 Subject: [PATCH] Fix license resolution for pom-dependencies (usually "bom"-packages) The ResolvedDependency class does not contain any artifacts for pure-pom dependencies. When it is detected, that a ResolvedDependency does not contain any artifacts, it will now use the createArtifactResolutionQuery to retrieve the pom. --- .../jk1/license/reader/ModuleReader.groovy | 37 +++++- .../jk1/license/reader/PomReader.groovy | 6 + .../PomDependencyResolutionFuncSpec.groovy | 118 ++++++++++++++++++ 3 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 src/test/groovy/com/github/jk1/license/reader/PomDependencyResolutionFuncSpec.groovy diff --git a/src/main/groovy/com/github/jk1/license/reader/ModuleReader.groovy b/src/main/groovy/com/github/jk1/license/reader/ModuleReader.groovy index 16c6e3fa..88b68670 100644 --- a/src/main/groovy/com/github/jk1/license/reader/ModuleReader.groovy +++ b/src/main/groovy/com/github/jk1/license/reader/ModuleReader.groovy @@ -21,8 +21,12 @@ import com.github.jk1.license.task.ReportTask import org.gradle.api.Project import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.artifacts.ResolvedDependency +import org.gradle.api.artifacts.result.ResolvedArtifactResult +import org.gradle.api.internal.artifacts.query.DefaultArtifactResolutionQuery import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging +import org.gradle.maven.MavenModule +import org.gradle.maven.MavenPomArtifact interface ModuleReader { ModuleData read(Project project, ResolvedDependency dependency) @@ -48,7 +52,7 @@ class ModuleReaderImpl implements ModuleReader { dependency.moduleArtifacts.each { ResolvedArtifact artifact -> LOGGER.info("Processing artifact: $artifact ($artifact.file)") moduleData.hasArtifactFile = artifact.file.exists() - if (moduleData.hasArtifactFile){ + if (moduleData.hasArtifactFile) { def pom = pomReader.readPomData(project, artifact) def manifest = manifestReader.readManifestData(artifact) def licenseFile = filesReader.read(artifact) @@ -60,8 +64,39 @@ class ModuleReaderImpl implements ModuleReader { LOGGER.info("Skipping artifact file $artifact.file as it does not exist") } } + if (dependency.moduleArtifacts.isEmpty()) { + def extraPomResults = resolvePom(project, dependency) + extraPomResults.each { ResolvedArtifactResult artifact -> + LOGGER.info("Processing artifact: $artifact ($artifact.file)") + if (artifact.file.exists()) { + def pom = pomReader.readPomData(artifact) + if (pom) moduleData.poms << pom + } else { + LOGGER.info("Skipping artifact file $artifact.file as it does not exist") + } + } + } return moduleData } + + private static Collection resolvePom(Project project, ResolvedDependency dependency) { + try { + DefaultArtifactResolutionQuery resolutionQuery = (DefaultArtifactResolutionQuery) project.dependencies.createArtifactResolutionQuery() + return resolutionQuery + .forModule(dependency.moduleGroup, dependency.moduleName, dependency.moduleVersion) + .withArtifacts(MavenModule, MavenPomArtifact) + .execute() + .resolvedComponents + .collectMany { + it.getArtifacts(MavenPomArtifact) + .findAll { it instanceof ResolvedArtifactResult } + .collect { (ResolvedArtifactResult) it } + } + } catch (Exception e) { + project.logger.info("Failed to resolve the pom artifact", e) + return[] + } + } } class CachedModuleReader implements ModuleReader { diff --git a/src/main/groovy/com/github/jk1/license/reader/PomReader.groovy b/src/main/groovy/com/github/jk1/license/reader/PomReader.groovy index 950f25f5..5e60359c 100644 --- a/src/main/groovy/com/github/jk1/license/reader/PomReader.groovy +++ b/src/main/groovy/com/github/jk1/license/reader/PomReader.groovy @@ -27,6 +27,7 @@ import groovy.xml.XmlSlurper import groovy.xml.slurpersupport.GPathResult import org.gradle.api.Project import org.gradle.api.artifacts.ResolvedArtifact +import org.gradle.api.artifacts.result.ResolvedArtifactResult import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging import org.xml.sax.SAXException @@ -73,6 +74,11 @@ class PomReader { } } + PomData readPomData(ResolvedArtifactResult artifact) { + GPathResult pomContent = findAndSlurpPom(artifact.file) + return readPomFile(pomContent) + } + private GPathResult findAndSlurpPom(File toSlurp) { if (toSlurp.name == "pom.xml") { LOGGER.debug("Slurping pom from pom.xml file: $toSlurp") diff --git a/src/test/groovy/com/github/jk1/license/reader/PomDependencyResolutionFuncSpec.groovy b/src/test/groovy/com/github/jk1/license/reader/PomDependencyResolutionFuncSpec.groovy new file mode 100644 index 00000000..d7539f9a --- /dev/null +++ b/src/test/groovy/com/github/jk1/license/reader/PomDependencyResolutionFuncSpec.groovy @@ -0,0 +1,118 @@ +/* + * Copyright 2018 Evgeny Naumenko + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.jk1.license.reader + +import com.github.jk1.license.AbstractGradleRunnerFunctionalSpec +import org.gradle.testkit.runner.TaskOutcome + +/** + * Dependencies, which are just available as .pom (usually "-bom"-modules) require a slightly different strategy, because + * gradle will not put the artifacts inside ResolvedDependency. + */ +class PomDependencyResolutionFuncSpec extends AbstractGradleRunnerFunctionalSpec { + + def setup() { + settingsGradle = new File(testProjectDir, "settings.gradle") + + buildFile << """ + plugins { + id 'com.github.jk1.dependency-license-report' + id 'java' + } + repositories { + mavenCentral() + } + """ + } + + def "report task resolves bom license"() { + setup: + buildFile << generateBuildWith( + """ + implementation platform("com.fasterxml.jackson:jackson-bom:2.12.3") + """.trim() + ) + + when: + def runResult = runGradleBuild() + + def resultFileGPath = jsonSlurper.parse(rawJsonFile) + removeDevelopers(resultFileGPath) + def configurationsGPath = resultFileGPath.configurations + def configurationsString = prettyPrintJson(configurationsGPath) + + then: + runResult.task(":generateLicenseReport").outcome == TaskOutcome.SUCCESS + configurationsString == """[ + { + "dependencies": [ + { + "group": "com.fasterxml.jackson", + "manifests": [ + + ], + "hasArtifactFile": false, + "version": "2.12.3", + "poms": [ + { + "inceptionYear": "", + "projectUrl": "https://github.com/FasterXML/jackson-bom", + "description": "Bill of Materials pom for getting full, complete set of compatible versions\\nof Jackson components maintained by FasterXML.com\\n ", + "name": "Jackson BOM", + "organization": { + "url": "http://fasterxml.com/", + "name": "FasterXML" + }, + "licenses": [ + { + "url": "http://www.apache.org/licenses/LICENSE-2.0.txt", + "name": "Apache License, Version 2.0" + } + ] + } + ], + "licenseFiles": [ + + ], + "empty": false, + "name": "jackson-bom" + } + ], + "name": "runtimeClasspath" + } +]""" + } + + private def generateBuildWith(String dependencies) { + """ + import com.github.jk1.license.render.* + + dependencies { + $dependencies + } + + licenseReport { + outputDir = "${fixPathForBuildFile(outputDir.absolutePath)}" + renderers = [new com.github.jk1.license.render.RawProjectDataJsonRenderer()] + configurations = ["runtimeClasspath"] + } + """ + } + + static void removeDevelopers(Map rawFile) { + rawFile.configurations*.dependencies.flatten().poms.flatten().each { it.remove("developers") } + } +}