diff --git a/plugins/package-managers/gradle-inspector/src/main/kotlin/GradleDependencyHandler.kt b/plugins/package-managers/gradle-inspector/src/main/kotlin/GradleDependencyHandler.kt index 92919e979c39a..fabcb32d262be 100644 --- a/plugins/package-managers/gradle-inspector/src/main/kotlin/GradleDependencyHandler.kt +++ b/plugins/package-managers/gradle-inspector/src/main/kotlin/GradleDependencyHandler.kt @@ -19,7 +19,7 @@ package org.ossreviewtoolkit.plugins.packagemanagers.gradleinspector -import OrtDependency +import OrtComponent import java.lang.invoke.MethodHandles @@ -58,16 +58,18 @@ import org.ossreviewtoolkit.utils.spdxexpression.SpdxOperator internal class GradleDependencyHandler( /** The type of projects to handle. */ private val projectType: String -) : DependencyHandler { - override fun identifierFor(dependency: OrtDependency): Identifier = - with(dependency) { Identifier(getIdentifierType(projectType), groupId, artifactId, version) } +) : DependencyHandler { + override fun identifierFor(dependency: OrtComponent): Identifier = + with(dependency.componentId) { + Identifier(dependency.getIdentifierType(projectType), groupId, artifactId, version) + } - override fun dependenciesFor(dependency: OrtDependency): List = dependency.dependencies + override fun dependenciesFor(dependency: OrtComponent): List = dependency.dependencies - override fun linkageFor(dependency: OrtDependency): PackageLinkage = + override fun linkageFor(dependency: OrtComponent): PackageLinkage = if (dependency.isProjectDependency) PackageLinkage.PROJECT_DYNAMIC else PackageLinkage.DYNAMIC - override fun createPackage(dependency: OrtDependency, issues: MutableCollection): Package? { + override fun createPackage(dependency: OrtComponent, issues: MutableCollection): Package? { // Only look for a package if there was no error resolving the dependency and it is no project dependency. if (dependency.error != null || dependency.isProjectDependency) return null @@ -136,7 +138,7 @@ internal class GradleDependencyHandler( ) } - override fun areDependenciesEqual(dependenciesA: List, dependenciesB: List): Boolean { + override fun areDependenciesEqual(dependenciesA: List, dependenciesB: List): Boolean { if (dependenciesA === dependenciesB) return true if (dependenciesA.isEmpty() && dependenciesB.isEmpty()) return true @@ -167,7 +169,7 @@ private val USER_HOST_REGEX = Regex("scm:(?[^:@]+)@(?[^:]+)[:/](? @@ -178,7 +180,7 @@ private fun OrtDependency.toVcsInfo(): VcsInfo = } ?: handleInvalidScmInfo(connection, tag) }.orEmpty() -private fun OrtDependency.handleValidScmInfo(type: String, url: String, tag: String): VcsInfo = +private fun OrtComponent.handleValidScmInfo(type: String, url: String, tag: String): VcsInfo = when { // Maven does not officially support git-repo as an SCM, see http://maven.apache.org/scm/scms-overview.html, so // come up with the convention to use the "manifest" query parameter for the path to the manifest inside the @@ -208,8 +210,10 @@ private fun OrtDependency.handleValidScmInfo(type: String, url: String, tag: Str // Try to detect the Maven SCM provider from the URL only, e.g. by looking at the host or special URL paths. VcsHost.parseUrl(fixedUrl).copy(revision = tag).also { - logger.info { - "Fixed up invalid SCM connection without a provider in '$groupId:$artifactId:$version' to $it." + with(componentId) { + logger.info { + "Fixed up invalid SCM connection without a provider in '$groupId:$artifactId:$version' to $it." + } } } } @@ -229,7 +233,7 @@ private fun OrtDependency.handleValidScmInfo(type: String, url: String, tag: Str } } -private fun OrtDependency.handleInvalidScmInfo(connection: String, tag: String): VcsInfo = +private fun OrtComponent.handleInvalidScmInfo(connection: String, tag: String): VcsInfo = @Suppress("UnsafeCallOnNullableType") USER_HOST_REGEX.matchEntire(connection)?.let { match -> // Some projects omit the provider and use the SCP-like Git URL syntax, for example @@ -244,7 +248,7 @@ private fun OrtDependency.handleInvalidScmInfo(connection: String, tag: String): VcsInfo.EMPTY } } ?: run { - val dep = "$groupId:$artifactId:$version" + val dep = with(componentId) { "$groupId:$artifactId:$version" } if (connection.startsWith("git://") || connection.endsWith(".git")) { // It is a common mistake to omit the "scm:[provider]:" prefix. Add fall-backs for nevertheless clear diff --git a/plugins/package-managers/gradle-model/src/main/kotlin/Extensions.kt b/plugins/package-managers/gradle-model/src/main/kotlin/Extensions.kt index 43320120274ea..0ffc69c997843 100644 --- a/plugins/package-managers/gradle-model/src/main/kotlin/Extensions.kt +++ b/plugins/package-managers/gradle-model/src/main/kotlin/Extensions.kt @@ -19,13 +19,13 @@ package org.ossreviewtoolkit.plugins.packagemanagers.gradlemodel -import OrtDependency +import OrtComponent /** * The type of this Gradle dependency. In case of a project, it is [projectType]. Otherwise it is "Maven" unless there * is no POM, then it is "Unknown". */ -fun OrtDependency.getIdentifierType(projectType: String) = +fun OrtComponent.getIdentifierType(projectType: String) = when { isProjectDependency -> projectType pomFile != null -> "Maven" @@ -35,5 +35,5 @@ fun OrtDependency.getIdentifierType(projectType: String) = /** * A flag to indicate whether this Gradle dependency refers to a project, or to a package. */ -val OrtDependency.isProjectDependency: Boolean +val OrtComponent.isProjectDependency: Boolean get() = localPath != null diff --git a/plugins/package-managers/gradle-model/src/main/kotlin/GradleModel.kt b/plugins/package-managers/gradle-model/src/main/kotlin/GradleModel.kt index 55d61d8259fbc..835830d2572f4 100644 --- a/plugins/package-managers/gradle-model/src/main/kotlin/GradleModel.kt +++ b/plugins/package-managers/gradle-model/src/main/kotlin/GradleModel.kt @@ -34,17 +34,21 @@ interface OrtDependencyTreeModel { interface OrtConfiguration { val name: String - val dependencies: List + val dependencies: List } -interface OrtDependency { +interface OrtComponentIdentifier { val groupId: String val artifactId: String val version: String +} + +interface OrtComponent { + val componentId: OrtComponentIdentifier val classifier: String val extension: String val variants: Map> - val dependencies: List + val dependencies: List val error: String? val warning: String? val pomFile: String? diff --git a/plugins/package-managers/gradle-plugin/src/main/kotlin/OrtModelBuilder.kt b/plugins/package-managers/gradle-plugin/src/main/kotlin/OrtModelBuilder.kt index 649c5d3382dc4..538b3a9b65de0 100644 --- a/plugins/package-managers/gradle-plugin/src/main/kotlin/OrtModelBuilder.kt +++ b/plugins/package-managers/gradle-plugin/src/main/kotlin/OrtModelBuilder.kt @@ -19,7 +19,7 @@ package org.ossreviewtoolkit.plugins.packagemanagers.gradleplugin -import OrtDependency +import OrtComponent import OrtDependencyTreeModel import org.apache.maven.model.building.FileModelSource @@ -54,10 +54,10 @@ internal class OrtModelBuilder : ToolingModelBuilder { private val logger = Logging.getLogger(OrtModelBuilder::class.java) private val errors = mutableListOf() private val warnings = mutableListOf() - private val globalDependencySubtrees = mutableMapOf>() + private val globalDependencySubtrees = mutableMapOf>() - // Only create one "OrtDependency" for each "ResolvedComponentResult". - private val ortDependencyCache = mutableMapOf() + // Only create one "OrtComponent" for each "ResolvedComponentResult". + private val ortComponentCache = mutableMapOf() override fun canBuild(modelName: String): Boolean = modelName == OrtDependencyTreeModel::class.java.name @@ -88,7 +88,7 @@ internal class OrtModelBuilder : ToolingModelBuilder { // Omit configurations without dependencies. root.dependencies.takeUnless { it.isEmpty() }?.let { dep -> - OrtConfigurationImpl(name = config.name, dependencies = dep.toOrtDependencies(poms, emptySet())) + OrtConfigurationImpl(name = config.name, dependencies = dep.toOrtComponents(poms, emptySet())) } } @@ -103,63 +103,22 @@ internal class OrtModelBuilder : ToolingModelBuilder { ) } - /** - * Resolve the POM files for all dependences in the given [Gradle configuration][config] incl. their parent POMs. - */ - private fun Project.resolvePoms(config: Configuration): Map { - val allComponentIds = config.incoming.resolutionResult.allDependencies - .filterIsInstance() - .map { it.selected.id } - .distinct() - - // Get the POM files for all resolved dependencies. - val pomFiles = resolvePoms(allComponentIds) - - val fileModelBuilder = FileModelBuilder { groupId, artifactId, version -> - val moduleId = DefaultModuleIdentifier.newId(groupId, artifactId) - val componentId = DefaultModuleComponentIdentifier.newId(moduleId, version) - - val pomFile = resolvePoms(listOf(componentId)).single().file - - FileModelSource(pomFile) - } - - return pomFiles.associate { - // Trigger resolution of parent POMs by building the POM model. - it.id.componentIdentifier.toString() to fileModelBuilder.buildModel(it.file) - } - } - - /** - * Resolve the POM files for the given [componentIds] and return them. - */ - private fun Project.resolvePoms(componentIds: List): List { - val resolutionResult = dependencies.createArtifactResolutionQuery() - .forComponents(componentIds) - .withArtifacts(MavenModule::class.java, MavenPomArtifact::class.java) - .execute() - - return resolutionResult.resolvedComponents.flatMap { - it.getArtifacts(MavenPomArtifact::class.java) - }.filterIsInstance() - } - - private fun Collection.toOrtDependencies( + private fun Collection.toOrtComponents( poms: Map, visited: Set - ): List = + ): List = if (GradleVersion.current() < GradleVersion.version("5.1")) { this } else { filterNot { it.isConstraint } }.mapNotNull { - it.toOrtDependency(poms, visited) + it.toOrtComponent(poms, visited) } - private fun DependencyResult.toOrtDependency( + private fun DependencyResult.toOrtComponent( poms: Map, visited: Set - ): OrtDependency? { + ): OrtComponent? { if (this is UnresolvedDependencyResult) { if (attempted is ProjectComponentSelector) { // Ignore unresolved project dependencies. For example for complex Android projects, Gradle's @@ -199,8 +158,8 @@ internal class OrtModelBuilder : ToolingModelBuilder { // Cut the graph on cyclic dependencies. if (id in visited) return null - if (selected in ortDependencyCache) { - return ortDependencyCache[selected] + if (selected in ortComponentCache) { + return ortComponentCache[selected] } if (id is ModuleComponentIdentifier) { @@ -215,13 +174,11 @@ internal class OrtModelBuilder : ToolingModelBuilder { // Check if we have scanned the dependencies of this subtree before, and if so, reuse them. val dependencies = globalDependencySubtrees.getOrPut(id.displayName) { - selected.dependencies.toOrtDependencies(poms, visited + id) + selected.dependencies.toOrtComponents(poms, visited + id) } - return OrtDependencyImpl( - groupId = id.group, - artifactId = id.module, - version = id.version, + return OrtComponentImpl( + componentId = OrtComponentIdentifierImpl(id.group, id.module, id.version), classifier = "", extension = modelBuildingResult?.effectiveModel?.packaging.orEmpty(), variants = selected.variants.associate { @@ -244,18 +201,20 @@ internal class OrtModelBuilder : ToolingModelBuilder { }, localPath = null ).also { - ortDependencyCache[selected] = it + ortComponentCache[selected] = it } } if (id is ProjectComponentIdentifier) { val moduleId = selected.moduleVersion ?: return null - val dependencies = selected.dependencies.toOrtDependencies(poms, visited + id) - - return OrtDependencyImpl( - groupId = moduleId.group, - artifactId = moduleId.name, - version = moduleId.version.takeUnless { it == "unspecified" }.orEmpty(), + val dependencies = selected.dependencies.toOrtComponents(poms, visited + id) + + return OrtComponentImpl( + componentId = OrtComponentIdentifierImpl( + groupId = moduleId.group, + artifactId = moduleId.name, + version = moduleId.version.takeUnless { it == "unspecified" }.orEmpty() + ), classifier = "", extension = "", variants = selected.variants.associate { @@ -270,7 +229,7 @@ internal class OrtModelBuilder : ToolingModelBuilder { mavenModel = null, localPath = id.projectPath ).also { - ortDependencyCache[selected] = it + ortComponentCache[selected] = it } } @@ -316,6 +275,44 @@ internal class OrtModelBuilder : ToolingModelBuilder { } } +/** + * Resolve the POM files for all dependences in the given [Gradle configuration][config] incl. their parent POMs. + */ +private fun Project.resolvePoms(config: Configuration): Map { + val allComponentIds = config.incoming.resolutionResult.allComponents.map { it.id } + + // Get the POM files for all resolved dependencies. + val pomFiles = resolvePoms(allComponentIds) + + val fileModelBuilder = FileModelBuilder { groupId, artifactId, version -> + val moduleId = DefaultModuleIdentifier.newId(groupId, artifactId) + val componentId = DefaultModuleComponentIdentifier.newId(moduleId, version) + + val pomFile = resolvePoms(listOf(componentId)).single().file + + FileModelSource(pomFile) + } + + return pomFiles.associate { + // Trigger resolution of parent POMs by building the POM model. + it.id.componentIdentifier.toString() to fileModelBuilder.buildModel(it.file) + } +} + +/** + * Resolve the POM files for the given [componentIds] and return them. + */ +private fun Project.resolvePoms(componentIds: List): List { + val resolutionResult = dependencies.createArtifactResolutionQuery() + .forComponents(componentIds.distinct()) + .withArtifacts(MavenModule::class.java, MavenPomArtifact::class.java) + .execute() + + return resolutionResult.resolvedComponents.flatMap { + it.getArtifacts(MavenPomArtifact::class.java) + }.filterIsInstance() +} + /** * Add a string with information about the causes of the given [exception] to this [StringBuilder]. This is used to * log the reason why a dependency could not be resolved. To get meaningful information, all causes need to be obtained diff --git a/plugins/package-managers/gradle-plugin/src/main/kotlin/OrtModelImpl.kt b/plugins/package-managers/gradle-plugin/src/main/kotlin/OrtModelImpl.kt index cc45188f2e564..ac9aa6dea8ea5 100644 --- a/plugins/package-managers/gradle-plugin/src/main/kotlin/OrtModelImpl.kt +++ b/plugins/package-managers/gradle-plugin/src/main/kotlin/OrtModelImpl.kt @@ -19,8 +19,9 @@ package org.ossreviewtoolkit.plugins.packagemanagers.gradleplugin +import OrtComponent +import OrtComponentIdentifier import OrtConfiguration -import OrtDependency import OrtDependencyTreeModel import OrtMavenModel import OrtRepository @@ -42,24 +43,29 @@ internal class OrtDependencyTreeModelImpl( @Suppress("SerialVersionUIDInSerializableClass") internal class OrtConfigurationImpl( override val name: String, - override val dependencies: List + override val dependencies: List ) : OrtConfiguration, Serializable @Suppress("LongParameterList", "SerialVersionUIDInSerializableClass") -internal class OrtDependencyImpl( +internal class OrtComponentIdentifierImpl( override val groupId: String, override val artifactId: String, - override val version: String, + override val version: String +) : OrtComponentIdentifier, Serializable + +@Suppress("LongParameterList", "SerialVersionUIDInSerializableClass") +internal class OrtComponentImpl( + override val componentId: OrtComponentIdentifier, override val classifier: String, override val extension: String, override val variants: Map>, - override val dependencies: List, + override val dependencies: List, override val error: String?, override val warning: String?, override val pomFile: String?, override val mavenModel: OrtMavenModel?, override val localPath: String? -) : OrtDependency, Serializable +) : OrtComponent, Serializable @Suppress("SerialVersionUIDInSerializableClass") internal class OrtMavenModelImpl( diff --git a/plugins/package-managers/gradle/build.gradle.kts b/plugins/package-managers/gradle/build.gradle.kts index ffb30aa2ba954..780656a71c77b 100644 --- a/plugins/package-managers/gradle/build.gradle.kts +++ b/plugins/package-managers/gradle/build.gradle.kts @@ -30,7 +30,6 @@ dependencies { implementation(libs.maven.core) implementation(libs.maven.resolver.api) implementation(projects.downloader) - implementation(projects.plugins.packageManagers.gradleModel) implementation(projects.plugins.packageManagers.mavenPackageManager) implementation(projects.utils.commonUtils) implementation(projects.utils.ortUtils) diff --git a/plugins/package-managers/gradle/src/main/kotlin/Extensions.kt b/plugins/package-managers/gradle/src/main/kotlin/Extensions.kt new file mode 100644 index 0000000000000..b9869811e683a --- /dev/null +++ b/plugins/package-managers/gradle/src/main/kotlin/Extensions.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 The ORT Project Copyright Holders + * + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +package org.ossreviewtoolkit.plugins.packagemanagers.gradle + +import OrtComponent + +/** + * The type of this Gradle dependency. In case of a project, it is [projectType]. Otherwise it is "Maven" unless there + * is no POM, then it is "Unknown". + */ +internal fun OrtComponent.getIdentifierType(projectType: String) = + when { + isProjectDependency -> projectType + pomFile != null -> "Maven" + else -> "Unknown" + } + +/** + * A flag to indicate whether this Gradle dependency refers to a project, or to a package. + */ +internal val OrtComponent.isProjectDependency: Boolean + get() = localPath != null diff --git a/plugins/package-managers/gradle/src/main/kotlin/GradleDependencyHandler.kt b/plugins/package-managers/gradle/src/main/kotlin/GradleDependencyHandler.kt index 20c356f666fe0..80159367b3ac7 100644 --- a/plugins/package-managers/gradle/src/main/kotlin/GradleDependencyHandler.kt +++ b/plugins/package-managers/gradle/src/main/kotlin/GradleDependencyHandler.kt @@ -19,7 +19,7 @@ package org.ossreviewtoolkit.plugins.packagemanagers.gradle -import OrtDependency +import OrtComponent import org.apache.maven.project.ProjectBuildingException @@ -34,8 +34,6 @@ import org.ossreviewtoolkit.model.PackageLinkage import org.ossreviewtoolkit.model.Severity import org.ossreviewtoolkit.model.createAndLogIssue import org.ossreviewtoolkit.model.utils.DependencyHandler -import org.ossreviewtoolkit.plugins.packagemanagers.gradlemodel.getIdentifierType -import org.ossreviewtoolkit.plugins.packagemanagers.gradlemodel.isProjectDependency import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenSupport import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.identifier import org.ossreviewtoolkit.utils.common.collectMessages @@ -50,7 +48,7 @@ internal class GradleDependencyHandler( /** The helper object to resolve packages via Maven. */ private val maven: MavenSupport -) : DependencyHandler { +) : DependencyHandler { /** * A list with repositories to use when resolving packages. This list must be set before using this handler for * constructing the dependency graph of a project. As different projects may use different repositories, this @@ -58,12 +56,14 @@ internal class GradleDependencyHandler( */ var repositories = emptyList() - override fun identifierFor(dependency: OrtDependency): Identifier = - with(dependency) { Identifier(getIdentifierType(projectType), groupId, artifactId, version) } + override fun identifierFor(dependency: OrtComponent): Identifier = + with(dependency.componentId) { + Identifier(dependency.getIdentifierType(projectType), groupId, artifactId, version) + } - override fun dependenciesFor(dependency: OrtDependency): List = dependency.dependencies + override fun dependenciesFor(dependency: OrtComponent): List = dependency.dependencies - override fun issuesFor(dependency: OrtDependency): List = + override fun issuesFor(dependency: OrtComponent): List = listOfNotNull( dependency.error?.let { createAndLogIssue( @@ -82,16 +82,16 @@ internal class GradleDependencyHandler( } ) - override fun linkageFor(dependency: OrtDependency): PackageLinkage = + override fun linkageFor(dependency: OrtComponent): PackageLinkage = if (dependency.isProjectDependency) PackageLinkage.PROJECT_DYNAMIC else PackageLinkage.DYNAMIC - override fun createPackage(dependency: OrtDependency, issues: MutableCollection): Package? { + override fun createPackage(dependency: OrtComponent, issues: MutableCollection): Package? { // Only look for a package if there was no error resolving the dependency and it is no project dependency. if (dependency.error != null || dependency.isProjectDependency) return null val artifact = DefaultArtifact( - dependency.groupId, dependency.artifactId, dependency.classifier, - dependency.extension, dependency.version + dependency.componentId.groupId, dependency.componentId.artifactId, dependency.classifier, + dependency.extension, dependency.componentId.version ) return runCatching { diff --git a/plugins/package-managers/gradle/src/main/kotlin/GradleModel.kt b/plugins/package-managers/gradle/src/main/kotlin/GradleModel.kt new file mode 100644 index 0000000000000..024b6059d4d6a --- /dev/null +++ b/plugins/package-managers/gradle/src/main/kotlin/GradleModel.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 The ORT Project Copyright Holders + * + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +// As it is not possible to declare a package in "init.gradle" also no package is declared here. + +// The following interfaces have to match those in "plugins/package-managers/gradle/src/main/resources/init.gradle" +// because they are used to deserialize the model produced there. + +internal interface OrtDependencyTreeModel { + val group: String + val name: String + val version: String + val configurations: List + val repositories: List + val errors: List + val warnings: List +} + +internal interface OrtConfiguration { + val name: String + val dependencies: List +} + +internal interface OrtComponentIdentifier { + val groupId: String + val artifactId: String + val version: String +} + +internal interface OrtComponent { + val componentId: OrtComponentIdentifier + val classifier: String + val extension: String + val variants: Map> + val dependencies: List + val error: String? + val warning: String? + val pomFile: String? + val mavenModel: OrtMavenModel? + val localPath: String? +} + +internal interface OrtMavenModel { + val licenses: Set + val authors: Set + val description: String? + val homepageUrl: String? + val vcs: OrtVcsModel? +} + +internal interface OrtVcsModel { + val connection: String + val tag: String + val browsableUrl: String +} + +internal interface OrtRepository { + val url: String + val username: String? + val password: String? + val headerName: String? + val headerValue: String? +} diff --git a/plugins/package-managers/gradle/src/main/resources/init.gradle b/plugins/package-managers/gradle/src/main/resources/init.gradle index 96bbffef95812..2c8c064b3a9bc 100644 --- a/plugins/package-managers/gradle/src/main/resources/init.gradle +++ b/plugins/package-managers/gradle/src/main/resources/init.gradle @@ -66,17 +66,21 @@ interface OrtDependencyTreeModel { interface OrtConfiguration { String getName() - List getDependencies() + List getDependencies() } -interface OrtDependency { +interface OrtComponentIdentifier { String getGroupId() String getArtifactId() String getVersion() +} + +interface OrtComponent { + OrtComponentIdentifier getComponentId() String getClassifier() String getExtension() Map> getVariants() - List getDependencies() + List getDependencies() String getError() String getWarning() String getPomFile() @@ -107,19 +111,25 @@ class OrtDependencyTreeModelImpl implements OrtDependencyTreeModel, Serializable @TupleConstructor class OrtConfigurationImpl implements OrtConfiguration, Serializable { String name - List dependencies + List dependencies } @ToString(includeNames = true) @TupleConstructor -class OrtDependencyImpl implements OrtDependency, Serializable { +class OrtComponentIdentifierImpl implements OrtComponentIdentifier, Serializable { String groupId = '' String artifactId = '' String version = '' +} + +@ToString(includeNames = true) +@TupleConstructor +class OrtComponentImpl implements OrtComponent, Serializable { + OrtComponentIdentifier componentId String classifier = '' String extension = '' Map> variants = [:] - List dependencies = [] + List dependencies = [] String error String warning String pomFile @@ -162,15 +172,15 @@ class AbstractOrtDependencyTreePlugin implements Plugin { private static class OrtDependencyTreeModelBuilder implements ToolingModelBuilder { /** - * Stores the OrtDependency objects created by this builder, so that they can be reused when the same + * Stores the OrtComponent objects created by this builder, so that they can be reused when the same * dependency is encountered again in the dependency graph. As dependencies can occur many times in large * dependency graphs, de-duplicating these objects can save a significant amount of memory. * * Note, however, that packages can be referenced in the graph multiple times with a different set of - * dependencies. Therefore, for a package with a given identifier, there can be different OrtDependency + * dependencies. Therefore, for a package with a given identifier, there can be different OrtComponent * objects; hence the values of the map are lists. */ - private final Map> dependencies = new HashMap<>() + private final Map> dependencies = new HashMap<>() /** * All root projects of included builds, mapped by included build name. @@ -232,7 +242,7 @@ class AbstractOrtDependencyTreePlugin implements Plugin { "available: ${collectCauses(e)}") } - List dependencies = result.getRoot().getDependencies().findResults { + List dependencies = result.getRoot().getDependencies().findResults { fetchDependency(it, project, resolvedArtifacts, [] as Set) } @@ -273,17 +283,17 @@ class AbstractOrtDependencyTreePlugin implements Plugin { } /** - * Returns an OrtDependency for the given DependencyResult. The function checks whether there is already an - * OrtDependency instance in the cache compatible with the result. If this is not the case, parseDependency() + * Returns an OrtComponent for the given DependencyResult. The function checks whether there is already an + * OrtComponent instance in the cache compatible with the result. If this is not the case, parseDependency() * is called to generate a new instance. * * @param dependencyResult represents the package to be processed * @param project the current project * @param resolvedArtifacts the set of resolved artifacts * @param visited a set with dependency nodes already visited to detect cycles in the graph - * @return the OrtDependency representing this result + * @return the OrtComponent representing this result */ - private OrtDependency fetchDependency(DependencyResult dependencyResult, Project project, + private OrtComponent fetchDependency(DependencyResult dependencyResult, Project project, Set resolvedArtifacts, Set visited) { // Ignore version constraints, because they are not real dependencies but only document why a certain // version of their parent was chosen. @@ -330,19 +340,19 @@ class AbstractOrtDependencyTreePlugin implements Plugin { } /** - * Creates a new OrtDependency object to represent the given DependencyResult. The different types of + * Creates a new OrtComponent object to represent the given DependencyResult. The different types of * dependencies are evaluated. The (transitive) dependencies of this dependency are processed recursively. * * @param dependencyResult represents the package to be processed * @param project the current project * @param resolvedArtifacts the set of resolved artifacts * @param parents a set with dependency nodes already visited to detect cycles in the graph - * @return the newly created OrtDependency + * @return the newly created OrtComponent */ - private OrtDependency parseDependency(DependencyResult dependencyResult, Project project, + private OrtComponent parseDependency(DependencyResult dependencyResult, Project project, Set resolvedArtifacts, Set parents) { if (dependencyResult instanceof ResolvedDependencyResult) { - List dependencies = dependencyResult.selected.dependencies.findResults { dependency -> + List dependencies = dependencyResult.selected.dependencies.findResults { dependency -> // Do not follow constraints, because they are not real dependencies but only document why a certain // version of their parent was chosen. // Do not follow circular dependencies, these can exist for project dependencies. @@ -396,25 +406,31 @@ class AbstractOrtDependencyTreePlugin implements Plugin { def classifier = artifact?.classifier ?: '' def extension = artifact?.extension ?: '' + def componentId = new OrtComponentIdentifierImpl(groupId: id.group, artifactId: id.module, version: id.version) - return new OrtDependencyImpl(id.group, id.module, id.version, classifier, extension, [:], dependencies, - error, warning, pomFile, null) + return new OrtComponentImpl(componentId, classifier, extension, [:], dependencies, error, warning, + pomFile, null) } else if (id instanceof ProjectComponentIdentifier) { if (isCurrentBuild(id.build, project) || !project.gradle.gradleVersion.isAtLeastVersion(7, 2)) { def dependencyProject = project.rootProject.findProject(id.projectPath) - return new OrtDependencyImpl(groupId: dependencyProject.group.toString(), - artifactId: dependencyProject.name, version: dependencyProject.version.toString(), - dependencies: dependencies, localPath: dependencyProject.projectDir.absolutePath) + + def componentId = new OrtComponentIdentifierImpl(groupId: dependencyProject.group.toString(), + artifactId: dependencyProject.name, version: dependencyProject.version.toString()) + + return new OrtComponentImpl(componentId: componentId, dependencies: dependencies, + localPath: dependencyProject.projectDir.absolutePath) } else { project.logger.quiet("Project '$id' is not part of current build. Assuming included build.") def includedProject = findRootProjectOfIncludedBuild(project, id) def dependencyProject = id.projectPath == ":" ? includedProject : includedProject.findProject(id.projectName) + def componentId = new OrtComponentIdentifierImpl(groupId: dependencyProject.group.toString(), + artifactId: dependencyProject.name, version: dependencyProject.version.toString()) + - return new OrtDependencyImpl(groupId: dependencyProject.group.toString(), - artifactId: dependencyProject.name, version: dependencyProject.version.toString(), - dependencies: dependencies, localPath: dependencyProject.projectDir.absolutePath) + return new OrtComponentImpl(componentId: componentId, dependencies: dependencies, + localPath: dependencyProject.projectDir.absolutePath) } } else { return dependencyFromDisplayName(id.displayName, dependencies, @@ -480,7 +496,7 @@ class AbstractOrtDependencyTreePlugin implements Plugin { * @param visited stores the name of already visited dependencies to deal with cycles * @return a flag whether the dependency trees are equal */ - private boolean dependencyTreeEquals(OrtDependency dependency, DependencyResult dependencyResult, + private boolean dependencyTreeEquals(OrtComponent dependency, DependencyResult dependencyResult, Project project, Set visited) { def displayName = dependencyResult.requested.displayName if (displayName in visited) { @@ -501,7 +517,7 @@ class AbstractOrtDependencyTreePlugin implements Plugin { return dependencyMap.every { entry -> def matchingResult = resultMap[entry.key] - matchingResult != null && dependencyTreeEquals(entry.value as OrtDependency, + matchingResult != null && dependencyTreeEquals(entry.value as OrtComponent, matchingResult as DependencyResult, project, nextVisited) } } @@ -578,13 +594,14 @@ class AbstractOrtDependencyTreePlugin implements Plugin { } /** - * Generates a unique identifier for the given OrtDependency. + * Generates a unique identifier for the given OrtComponent. * - * @param dependency the OrtDependency - * @return the identifier for this OrtDependency + * @param dependency the OrtComponent + * @return the identifier for this OrtComponent */ - private static String identifierFor(OrtDependency dependency) { - return toIdentifier(dependency.groupId, dependency.artifactId, dependency.version) + private static String identifierFor(OrtComponent dependency) { + def componentId = dependency.componentId + return toIdentifier(componentId.groupId, componentId.artifactId, componentId.version) } /** @@ -598,11 +615,13 @@ class AbstractOrtDependencyTreePlugin implements Plugin { * @param warning an optional warning message * @return the newly created dependency representation */ - private static OrtDependencyImpl dependencyFromDisplayName(String displayName, List dependencies, - String error, String warning) { + private static OrtComponentImpl dependencyFromDisplayName(String displayName, List dependencies, + String error, String warning) { def coordinates = displayNameToCoordinates(displayName) - return new OrtDependencyImpl(groupId: coordinates[0], artifactId: coordinates[1], version: coordinates[2], - dependencies: dependencies, error: error?.toString(), warning: warning?.toString()) + def componentId = new OrtComponentIdentifierImpl(groupId: coordinates[0], artifactId: coordinates[1], + version: coordinates[2]) + return new OrtComponentImpl(componentId: componentId, dependencies: dependencies, error: error?.toString(), + warning: warning?.toString()) } /** diff --git a/plugins/package-managers/gradle/src/test/kotlin/GradleDependencyHandlerTest.kt b/plugins/package-managers/gradle/src/test/kotlin/GradleDependencyHandlerTest.kt index 3230172545602..54a69d0b90b39 100644 --- a/plugins/package-managers/gradle/src/test/kotlin/GradleDependencyHandlerTest.kt +++ b/plugins/package-managers/gradle/src/test/kotlin/GradleDependencyHandlerTest.kt @@ -19,7 +19,8 @@ package org.ossreviewtoolkit.plugins.packagemanagers.gradle -import OrtDependency +import OrtComponent +import OrtComponentIdentifier import io.kotest.core.spec.style.WordSpec import io.kotest.inspectors.forAll @@ -55,7 +56,6 @@ import org.ossreviewtoolkit.model.Scope import org.ossreviewtoolkit.model.Severity import org.ossreviewtoolkit.model.VcsInfo import org.ossreviewtoolkit.model.utils.DependencyGraphBuilder -import org.ossreviewtoolkit.plugins.packagemanagers.gradlemodel.getIdentifierType import org.ossreviewtoolkit.plugins.packagemanagers.maven.utils.MavenSupport /** @@ -315,7 +315,9 @@ class GradleDependencyHandlerTest : WordSpec({ with(issues.first()) { source shouldBe GradleFactory.descriptor.displayName severity shouldBe Severity.ERROR - message should contain("${dep.groupId}:${dep.artifactId}:${dep.version}") + with(dep.componentId) { + message should contain("$groupId:$artifactId:$version") + } } } } @@ -332,12 +334,15 @@ private fun createDependency( artifact: String, version: String, path: String? = null, - dependencies: List = emptyList() -): OrtDependency { - val dependency = mockk() - every { dependency.groupId } returns group - every { dependency.artifactId } returns artifact - every { dependency.version } returns version + dependencies: List = emptyList() +): OrtComponent { + val componentId = mockk() + every { componentId.groupId } returns group + every { componentId.artifactId } returns artifact + every { componentId.version } returns version + + val dependency = mockk() + every { dependency.componentId } returns componentId every { dependency.localPath } returns path every { dependency.pomFile } returns "pom.xml" every { dependency.dependencies } returns dependencies @@ -352,7 +357,7 @@ private fun createDependency( * Create a [DependencyGraphBuilder] equipped with a [GradleDependencyHandler] that is used by the test cases in * this class. */ -private fun createGraphBuilder(): DependencyGraphBuilder { +private fun createGraphBuilder(): DependencyGraphBuilder { val dependencyHandler = GradleDependencyHandler(PROJECT_TYPE, createMavenSupport()) dependencyHandler.repositories = remoteRepositories return DependencyGraphBuilder(dependencyHandler) @@ -382,9 +387,12 @@ private fun createMavenSupport(): MavenSupport { } /** - * Returns an [Identifier] for this [OrtDependency]. + * Returns an [Identifier] for this [OrtComponent]. */ -private fun OrtDependency.toId() = Identifier(getIdentifierType(PROJECT_TYPE), groupId, artifactId, version) +private fun OrtComponent.toId() = + with(componentId) { + Identifier(getIdentifierType(PROJECT_TYPE), groupId, artifactId, version) + } /** * Return the package references from the given [scopes] associated with the scope with the given [scopeName]. @@ -400,7 +408,7 @@ private fun Collection.identifiers(): List = map { /** * Find the package corresponding to the given [dependency] in this collection. */ -private fun Collection.findDependency(dependency: OrtDependency): PackageReference = +private fun Collection.findDependency(dependency: OrtComponent): PackageReference = findId(dependency.toId()) /** @@ -412,7 +420,7 @@ private fun Collection.findId(id: Identifier): PackageReferenc /** * Check whether this [PackageReference] contains exactly the given [dependencies][expectedDependencies]. */ -private fun PackageReference.checkDependencies(vararg expectedDependencies: OrtDependency): Set { +private fun PackageReference.checkDependencies(vararg expectedDependencies: OrtComponent): Set { val ids = expectedDependencies.map { it.toId() } dependencies.identifiers() should containExactlyInAnyOrder(ids) return dependencies