From 8d096c16a75b202db57ca293b8f052d12e222670 Mon Sep 17 00:00:00 2001 From: Oliver Heger Date: Mon, 12 Apr 2021 09:28:04 +0200 Subject: [PATCH 1/6] Add DependencyHandler interface This interface plays a central role to generalize the GradleDependencyBuilder class to support other package managers as well. The idea is that each package manager can have its own, internal representation of a dependency. A DependencyHandler allows extracting information from such a representation, so that it can be processed by a generic builder, which does not need to have knowledge about the internals of a specific package manager implementation. Signed-off-by: Oliver Heger --- .../managers/utils/DependencyHandler.kt | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 analyzer/src/main/kotlin/managers/utils/DependencyHandler.kt diff --git a/analyzer/src/main/kotlin/managers/utils/DependencyHandler.kt b/analyzer/src/main/kotlin/managers/utils/DependencyHandler.kt new file mode 100644 index 0000000000000..49054c4551633 --- /dev/null +++ b/analyzer/src/main/kotlin/managers/utils/DependencyHandler.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 Bosch.IO GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +package org.ossreviewtoolkit.analyzer.managers.utils + +import org.ossreviewtoolkit.analyzer.PackageManager +import org.ossreviewtoolkit.model.Identifier +import org.ossreviewtoolkit.model.OrtIssue +import org.ossreviewtoolkit.model.Package +import org.ossreviewtoolkit.model.PackageLinkage + +/** + * An interface used by [DependencyGraphBuilder] to handle the specific representations of concrete [PackageManager] + * implementations in a generic way. + * + * A package manager may use its own, internal representation of a type [D] of a dependency. When constructing the + * [org.ossreviewtoolkit.model.DependencyGraph] by passing the dependencies of the single scopes, the builder must be + * able to extract certain information from the dependency objects. This is done via an implementation of this + * interface. + */ +interface DependencyHandler { + /** + * Construct a unique identifier for the given [dependency]. This identifier should be derived from the + * coordinates of the dependency. It is later converted to an [Identifier]; so it has to adhere to the + * string-representation of this class. + */ + fun identifierFor(dependency: D): String + + /** + * Return a collection with the dependencies of the given [dependency]. [DependencyGraphBuilder] invokes this + * function to construct the whole dependency tree spawned by this [dependency]. + */ + fun dependenciesFor(dependency: D): Collection + + /** + * Return the [PackageLinkage] for the given [dependency]. + */ + fun linkageFor(dependency: D): PackageLinkage + + /** + * Create a [Package] to represent the [dependency] with the given [identifier]. This is used to populate the + * packages in the analyzer result. The creation of a package may fail, e.g. if the dependency cannot be resolved. + * In this case, a concrete implementation is expected to return a dummy [Package] with correct coordinates and + * add a corresponding issue to the provided [issues] list. If the [dependency] does not map to a package, an + * implementation should return *null*. + */ + fun createPackage(identifier: String, dependency: D, issues: MutableList): Package? + + /** + * Return a collection with known issues for the given [dependency]. Some package manager implementations may + * already encounter problems when obtaining dependency representations. These can be reported here. This base + * implementation returns an empty collection. + */ + fun issuesForDependency(dependency: D): Collection = emptyList() +} From d3293d5275b895c778e67b1b4b11c1549708ac97 Mon Sep 17 00:00:00 2001 From: Oliver Heger Date: Mon, 12 Apr 2021 10:59:17 +0200 Subject: [PATCH 2/6] Add DependencyGraphBuilder class This class is a generalized variant of GradleDependencyGraphBuilder that can deal with arbitrary dependency models for which a DependencyHandler is available. In future, it is going to replace GradleDependencyGraphBuilder and be used for other package managers as well. Signed-off-by: Oliver Heger --- .../managers/utils/DependencyGraphBuilder.kt | 328 ++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 analyzer/src/main/kotlin/managers/utils/DependencyGraphBuilder.kt diff --git a/analyzer/src/main/kotlin/managers/utils/DependencyGraphBuilder.kt b/analyzer/src/main/kotlin/managers/utils/DependencyGraphBuilder.kt new file mode 100644 index 0000000000000..2d309491d4eaa --- /dev/null +++ b/analyzer/src/main/kotlin/managers/utils/DependencyGraphBuilder.kt @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2021 Bosch.IO GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +package org.ossreviewtoolkit.analyzer.managers.utils + +import org.ossreviewtoolkit.model.DependencyGraph +import org.ossreviewtoolkit.model.DependencyReference +import org.ossreviewtoolkit.model.OrtIssue +import org.ossreviewtoolkit.model.Package +import org.ossreviewtoolkit.model.RootDependencyIndex + +/** + * Internal class to represent the result of a search in the dependency graph. The outcome of the search + * determines how to integrate a specific dependency into the dependency graph. + */ +private sealed class DependencyGraphSearchResult { + /** + * A specialized [DependencyGraphSearchResult] that indicates that the dependency that was searched for is already + * present in the dependency graph. This is the easiest case, as the [DependencyReference] that was found + * can directly be reused. + */ + data class Found( + /** The reference to the dependency that was searched in the graph. */ + val ref: DependencyReference + ) : DependencyGraphSearchResult() + + /** + * A specialized [DependencyGraphSearchResult] that indicates that the dependency that was searched for was not + * found in the dependency graph, but a fragment has been detected, to which it can be added. + */ + data class NotFound( + /** The index of the fragment to which to add the dependency. */ + val fragmentIndex: Int + ) : DependencyGraphSearchResult() + + /** + * A specialized [DependencyGraphSearchResult] that indicates that the dependency that was searched for in its + * current form cannot be added to any of the existing fragments. This means that either there are no + * fragments yet or each fragment contains a variant of the dependency with an incompatible dependency + * tree. In this case, a new fragment has to be added to the graph to host this special variant of this + * dependency. + */ + object Incompatible : DependencyGraphSearchResult() +} + +/** + * A class that can construct a [DependencyGraph] from a set of dependencies that provides an efficient storage format + * for large dependency sets in many scopes. + * + * For larger projects the network of transitive dependencies tends to become complex. Single packages can occur many + * times in this structure if they are referenced by multiple scopes or are fundamental libraries, on which many other + * packages depend. A naive implementation, which simply duplicates the dependency trees for each scope and package + * therefore leads to a high consumption of memory. This class addresses this problem by generating an optimized + * structure that shares the components of the dependency graph between the scopes as far as possible. + * + * This builder class provides the _addDependency()_ function, which has to be called for all the direct dependencies + * of the different scopes. From these dependencies it constructs a single, optimized graph that is referenced by all + * scopes. To reduce the amount of memory required even further, package identifiers are replaced by numeric indices, + * so that references in the graph are just numbers. + * + * Ideally, the resulting dependency graph contains each dependency exactly once. There are, however, cases, in which + * packages occur multiple times in the project's dependency graph with different dependencies, for instance if + * exclusions for transitive dependencies are used or a version resolution mechanism comes into play. In such cases, + * the corresponding packages need to form different nodes in the graph, so that they can be distinguished, and for + * packages depending on them, it must be ensured that the correct node is referenced. In the terminology of this class + * this is referred to as "fragmentation": A fragment is a consistent sub graph, in which each package occurs only + * once. Packages appearing multiple times with different dependencies need to be placed in separate fragments. It is + * then possible to uniquely identify a specific package by a combination of its numeric identifier and the index of + * the fragment it belongs to. + * + * This class implements the full logic to construct a [DependencyGraph], independent on the concrete representation of + * dependencies [D] used by specific package managers. To make this class compatible with such a dependency + * representation, the package manager implementation has to provide a [DependencyHandler]. Via this handler, all the + * relevant information about dependencies can be extracted. + */ +class DependencyGraphBuilder( + /** + * The [DependencyHandler] used by this builder instance to extract information from the dependency objects when + * constructing the [DependencyGraph]. + */ + val dependencyHandler: DependencyHandler +) { + /** + * A list storing the identifiers of all dependencies added to this builder. This list is then used to resolve + * dependencies based on their indices. + */ + private val dependencyIds = mutableListOf() + + /** + * A mapping of the identifiers of the dependencies known to this builder to their numeric indices. + */ + private val dependencyIndexMapping = mutableMapOf() + + /** + * Stores all the references to dependencies that have been added so far. Each element of the list represents a + * fragment of the dependency graph. For each fragment, there is a mapping from a dependency index to the + * reference pointing to the corresponding dependency tree. + */ + private val referenceMappings = mutableListOf>() + + /** The mapping from scopes to dependencies constructed by this builder. */ + private val scopeMapping = mutableMapOf>() + + /** Stores all packages encountered in the dependency tree. */ + private val resolvedPackages = mutableSetOf() + + /** + * A set storing the packages that are direct dependencies of one of the scopes. These are the entry points into + * the dependency graph. + */ + private val directDependencies = mutableSetOf() + + /** + * Add the scope with the given [scopeName] to this builder. In most cases, it is not necessary to add scopes + * explicitly, as they are recorded automatically by _addDependency()_. However, if there are scopes without + * dependencies, this function can be used to include them into the builder result. + */ + fun addScope(scopeName: String) { + scopeMapping.putIfAbsent(scopeName, emptyList()) + } + + /** + * Add the given [dependency] for the scope with the given [scopeName] to this builder. This function needs to be + * called all the direct dependencies of all scopes. That way the builder gets sufficient information to construct + * the [DependencyGraph]. + */ + fun addDependency(scopeName: String, dependency: D) { + addDependencyToGraph(scopeName, dependency, transitive = false) + } + + /** + * Construct the [DependencyGraph] from the dependencies passed to this builder so far. + */ + fun build(): DependencyGraph = DependencyGraph(dependencyIds, directDependencies, scopeMapping) + + /** + * Return a set with all the packages that have been encountered for the current project. + */ + fun packages(): Set = resolvedPackages + + /** + * Update the dependency graph by adding the given [dependency], which may be [transitive], for the scope with name + * [scopeName]. All the dependencies of this dependency are processed recursively. + */ + private fun addDependencyToGraph(scopeName: String, dependency: D, transitive: Boolean): DependencyReference { + val identifier = dependencyHandler.identifierFor(dependency) + val issues = dependencyHandler.issuesForDependency(dependency).toMutableList() + val index = updateDependencyMappingAndPackages(identifier, dependency, issues) + + val ref = when (val result = findDependencyInGraph(index, dependency)) { + is DependencyGraphSearchResult.Found -> result.ref + + is DependencyGraphSearchResult.NotFound -> { + insertIntoGraph( + RootDependencyIndex(index, result.fragmentIndex), + scopeName, + dependency, + issues, + transitive + ) + } + + is DependencyGraphSearchResult.Incompatible -> + insertIntoNewFragment(index, scopeName, dependency, issues, transitive) + } + + return updateScopeMapping(scopeName, ref, transitive) + } + + /** + * Update internal state for the given dependency [id]. Check whether this ID is already known. If not, add it + * to the data managed by this instance, resolve the package, and update the [issues] if necessary. Return the + * numeric index for this dependency. + */ + private fun updateDependencyMappingAndPackages(id: String, dependency: D, issues: MutableList): Int { + val dependencyIndex = dependencyIndexMapping[id] + if (dependencyIndex != null) return dependencyIndex + + updateResolvedPackages(id, dependency, issues) + return dependencyIds.size.also { + dependencyIds += id + dependencyIndexMapping[id] = it + } + } + + /** + * Search for the [dependency] with the given [index] in the fragments of the dependency graph. Return a + * [DependencyGraphSearchResult] that indicates how to proceed with this dependency. + */ + private fun findDependencyInGraph(index: Int, dependency: D): DependencyGraphSearchResult { + val mappingForCompatibleFragment = referenceMappings.find { mapping -> + mapping[index]?.takeIf { dependencyTreeEquals(it, dependency) } != null + } + + val compatibleReference = mappingForCompatibleFragment?.let { it[index] } + return compatibleReference?.let { DependencyGraphSearchResult.Found(it) } + ?: handleNoCompatibleDependencyInGraph(index) + } + + /** + * Determine how to deal with the dependency with the given [index], for which no compatible variant was found + * in the dependency graph. Try to find a fragment, in which the dependency can be inserted. If this fails, a new + * fragment has to be added. + */ + private fun handleNoCompatibleDependencyInGraph(index: Int): DependencyGraphSearchResult { + val mappingToInsert = referenceMappings.withIndex().find { index !in it.value } + return mappingToInsert?.let { DependencyGraphSearchResult.NotFound(it.index) } + ?: DependencyGraphSearchResult.Incompatible + } + + /** + * Check whether the dependency tree spawned by [dependency] matches the one [ref] points to. Using this function, + * packages are identified that occur multiple times in the dependency graph with different sets of dependencies; + * these have to be placed in separate fragments of the dependency graph. + */ + private fun dependencyTreeEquals(ref: DependencyReference, dependency: D): Boolean { + val dependencies = dependencyHandler.dependenciesFor(dependency) + if (ref.dependencies.size != dependencies.size) return false + + val dependencies1 = ref.dependencies.map { dependencyIds[it.pkg] } + val dependencies2 = dependencies.associateBy(dependencyHandler::identifierFor) + if (!dependencies2.keys.containsAll(dependencies1)) return false + + return ref.dependencies.all { refDep -> + dependencies2[dependencyIds[refDep.pkg]]?.let { dependencyTreeEquals(refDep, it) } ?: false + } + } + + /** + * Add a new fragment to the dependency graph for the [dependency] with the given [index], which may be + * [transitive] and belongs to the scope with the given [scopeName]. This function is called for dependencies that + * cannot be added to already existing fragments. Therefore, create a new fragment and add the [dependency] to it, + * together with its own dependencies. Store the given [issues] for the dependency. + */ + private fun insertIntoNewFragment( + index: Int, + scopeName: String, + dependency: D, + issues: List, + transitive: Boolean + ): DependencyReference { + val fragmentMapping = mutableMapOf() + val dependencyIndex = RootDependencyIndex(index, referenceMappings.size) + referenceMappings += fragmentMapping + return insertIntoGraph(dependencyIndex, scopeName, dependency, issues, transitive) + } + + /** + * Insert the [dependency] with the given [RootDependencyIndex][index], which belongs to the scope with the given + * [scopeName] and may be [transitive] into the dependency graph. Insert the dependencies of this [dependency] + * recursively. Create a new [DependencyReference] for the dependency and initialize it with the list of [issues]. + */ + private fun insertIntoGraph( + index: RootDependencyIndex, + scopeName: String, + dependency: D, + issues: List, + transitive: Boolean + ): DependencyReference { + val transitiveDependencies = dependencyHandler.dependenciesFor(dependency).map { + addDependencyToGraph(scopeName, it, transitive = true) + } + + val fragmentMapping = referenceMappings[index.fragment] + val ref = DependencyReference( + pkg = index.root, + fragment = index.fragment, + dependencies = transitiveDependencies.toSortedSet(), + linkage = dependencyHandler.linkageFor(dependency), + issues = issues + ) + + fragmentMapping[index.root] = ref + return updateDirectDependencies(ref, transitive) + } + + /** + * Construct a [Package] for the given [dependency]. Add the new package to the set managed by this object. If this + * fails, record a corresponding message in [issues]. + */ + private fun updateResolvedPackages(identifier: String, dependency: D, issues: MutableList) { + dependencyHandler.createPackage(identifier, dependency, issues)?.let { resolvedPackages += it } + } + + /** + * Add the given [dependency reference][ref] to the set of direct dependencies if it is not [transitive]. If one of + * the direct dependencies of this package is in this set, it is removed, as it is obviously no direct dependency. + * Because this function is called for all dependencies, all transitive dependencies are eventually removed. + */ + private fun updateDirectDependencies(ref: DependencyReference, transitive: Boolean): DependencyReference { + directDependencies.removeAll(ref.dependencies) + if (!transitive) directDependencies += ref + return ref + } + + /** + * Update the scope mapping for the given [scopeName] to depend on [ref], which may be a [transitive] dependency. + * The scope mapping records all the direct dependencies of scopes. + */ + private fun updateScopeMapping( + scopeName: String, ref: DependencyReference, transitive: Boolean + ): DependencyReference { + if (!transitive) { + val index = RootDependencyIndex(ref.pkg, ref.fragment) + scopeMapping.compute(scopeName) { _, ids -> + ids?.let { it + index } ?: listOf(index) + } + } + + return ref + } +} From 3c06ebc3b2ec16ad449fd9f75739557f9492546c Mon Sep 17 00:00:00 2001 From: Oliver Heger Date: Mon, 12 Apr 2021 11:01:35 +0200 Subject: [PATCH 3/6] Add a DependencyHandler implementation for Gradle This is a prerequisite to use the new generic DependencyGraphBuilder class with the Gradle package manager. Signed-off-by: Oliver Heger --- .../managers/GradleDependencyHandler.kt | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 analyzer/src/main/kotlin/managers/GradleDependencyHandler.kt diff --git a/analyzer/src/main/kotlin/managers/GradleDependencyHandler.kt b/analyzer/src/main/kotlin/managers/GradleDependencyHandler.kt new file mode 100644 index 0000000000000..3b4d39a177943 --- /dev/null +++ b/analyzer/src/main/kotlin/managers/GradleDependencyHandler.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021 Bosch.IO GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +package org.ossreviewtoolkit.analyzer.managers + +import Dependency + +import org.apache.maven.project.ProjectBuildingException + +import org.eclipse.aether.artifact.DefaultArtifact +import org.eclipse.aether.repository.RemoteRepository + +import org.ossreviewtoolkit.analyzer.managers.utils.DependencyHandler +import org.ossreviewtoolkit.analyzer.managers.utils.MavenSupport +import org.ossreviewtoolkit.model.Identifier +import org.ossreviewtoolkit.model.OrtIssue +import org.ossreviewtoolkit.model.Package +import org.ossreviewtoolkit.model.PackageLinkage +import org.ossreviewtoolkit.model.Severity +import org.ossreviewtoolkit.model.createAndLogIssue +import org.ossreviewtoolkit.utils.collectMessagesAsString +import org.ossreviewtoolkit.utils.showStackTrace + +/** + * A specialized [DependencyHandler] implementation for Gradle's dependency model. + */ +class GradleDependencyHandler( + /** The name of the associated package manager. */ + val managerName: String, + + /** The helper object to resolve packages via Maven. */ + private val maven: MavenSupport, + + /** A list with repositories to use when resolving packages. */ + private val repositories: List +) : DependencyHandler { + override fun identifierFor(dependency: Dependency): String = + "${dependency.dependencyType()}:${dependency.groupId}:${dependency.artifactId}:${dependency.version}" + + override fun dependenciesFor(dependency: Dependency): Collection = dependency.dependencies + + override fun issuesForDependency(dependency: Dependency): Collection = + listOfNotNull( + dependency.error?.let { + createAndLogIssue( + source = managerName, + message = it, + severity = Severity.ERROR + ) + }, + + dependency.warning?.let { + createAndLogIssue( + source = managerName, + message = it, + severity = Severity.WARNING + ) + } + ) + + override fun linkageFor(dependency: Dependency): PackageLinkage = dependency.linkage() + + override fun createPackage(identifier: String, dependency: Dependency, issues: MutableList): 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 + + return try { + val artifact = DefaultArtifact( + dependency.groupId, dependency.artifactId, dependency.classifier, + dependency.extension, dependency.version + ) + + maven.parsePackage(artifact, repositories) + } catch (e: ProjectBuildingException) { + e.showStackTrace() + + issues += createAndLogIssue( + source = managerName, + message = "Could not get package information for dependency '$identifier': " + + e.collectMessagesAsString() + ) + + Package.EMPTY.copy( + id = Identifier( + type = "Maven", + namespace = dependency.groupId, + name = dependency.artifactId, + version = dependency.version + ) + ) + } + } + + /** + * Determine the type of this dependency. This manager implementation uses Maven to resolve packages, so + * the type of dependencies to packages is typically _Maven_ unless no pom is available. Only for module + * dependencies, the type of this manager is used. + */ + private fun Dependency.dependencyType(): String = + if (isProjectDependency()) { + managerName + } else { + pomFile?.let { "Maven" } ?: "Unknown" + } +} + +/** + * Determine the [PackageLinkage] for this [Dependency]. + */ +private fun Dependency.linkage() = + if (isProjectDependency()) { + PackageLinkage.PROJECT_DYNAMIC + } else { + PackageLinkage.DYNAMIC + } + +/** + * Return a flag whether this dependency references another project in the current build. + */ +private fun Dependency.isProjectDependency() = localPath != null From 9c7099d6caf70a3ced86e00fe29900992cd20d29 Mon Sep 17 00:00:00 2001 From: Oliver Heger Date: Mon, 12 Apr 2021 11:46:29 +0200 Subject: [PATCH 4/6] Test DependencyGraphBuilder instead of GradleDependencyGraphBuilder Rework the test class for GradleDependencyGraphBuilder to operate on a generic DependencyGraphBuilder configured with a GradleDependencyHandler. This demonstrates that the Gradle-specific functionality has been correctly generalized. Rename the test class accordingly and move it to the managers package. Signed-off-by: Oliver Heger --- ...Test.kt => GradleDependencyHandlerTest.kt} | 93 ++++++++++--------- 1 file changed, 51 insertions(+), 42 deletions(-) rename analyzer/src/test/kotlin/managers/{utils/GradleDependencyGraphBuilderTest.kt => GradleDependencyHandlerTest.kt} (84%) diff --git a/analyzer/src/test/kotlin/managers/utils/GradleDependencyGraphBuilderTest.kt b/analyzer/src/test/kotlin/managers/GradleDependencyHandlerTest.kt similarity index 84% rename from analyzer/src/test/kotlin/managers/utils/GradleDependencyGraphBuilderTest.kt rename to analyzer/src/test/kotlin/managers/GradleDependencyHandlerTest.kt index 4d24faddb9f0e..2cdfb63983f6e 100644 --- a/analyzer/src/test/kotlin/managers/utils/GradleDependencyGraphBuilderTest.kt +++ b/analyzer/src/test/kotlin/managers/GradleDependencyHandlerTest.kt @@ -17,7 +17,7 @@ * License-Filename: LICENSE */ -package org.ossreviewtoolkit.analyzer.managers.utils +package org.ossreviewtoolkit.analyzer.managers import Dependency @@ -44,6 +44,8 @@ import java.util.SortedSet import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.repository.RemoteRepository +import org.ossreviewtoolkit.analyzer.managers.utils.DependencyGraphBuilder +import org.ossreviewtoolkit.analyzer.managers.utils.MavenSupport import org.ossreviewtoolkit.model.Identifier import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.PackageReference @@ -52,21 +54,24 @@ import org.ossreviewtoolkit.model.RootDependencyIndex import org.ossreviewtoolkit.model.Scope import org.ossreviewtoolkit.model.VcsInfo -class GradleDependencyGraphBuilderTest : WordSpec({ - "GradleDependencyGraphBuilder" should { +/** + * A test class to test the integration of the [Gradle] package manager with [DependencyGraphBuilder]. This class + * not only tests the dependency handler implementation itself but also the logic of the + */ +class GradleDependencyHandlerTest : WordSpec({ + "DependencyGraphBuilder" should { "collect the direct dependencies of scopes" { val scope1 = "compile" val scope2 = "test" val dep1 = createDependency("org.apache.commons", "commons-lang3", "3.11") val dep2 = createDependency("org.apache.commons", "commons-collections4", "4.4") val dep3 = createDependency("my-project", "my-module", "1.0", path = "subPath") - val maven = createMavenSupport() - val builder = GradleDependencyGraphBuilder(NAME, maven) + val builder = createGraphBuilder() - builder.addDependency(scope1, dep1, remoteRepositories) - builder.addDependency(scope1, dep3, remoteRepositories) - builder.addDependency(scope2, dep2, remoteRepositories) - builder.addDependency(scope2, dep1, remoteRepositories) + builder.addDependency(scope1, dep1) + builder.addDependency(scope1, dep3) + builder.addDependency(scope2, dep2) + builder.addDependency(scope2, dep1) val graph = builder.build() graph.scopeRoots shouldHaveSize 3 @@ -84,10 +89,9 @@ class GradleDependencyGraphBuilderTest : WordSpec({ "collect a dependency of type Maven" { val scope = "TheScope" val dep = createDependency("org.apache.commons", "commons-lang3", "3.10") - val maven = createMavenSupport() - val builder = GradleDependencyGraphBuilder(NAME, maven) + val builder = createGraphBuilder() - builder.addDependency(scope, dep, remoteRepositories) + builder.addDependency(scope, dep) val graph = builder.build() val scopes = graph.createScopes() @@ -98,10 +102,9 @@ class GradleDependencyGraphBuilderTest : WordSpec({ val scope = "TheScope" val dep = createDependency("org.apache.commons", "commons-lang3", "3.10") every { dep.pomFile } returns null - val maven = createMavenSupport() - val builder = GradleDependencyGraphBuilder(NAME, maven) + val builder = createGraphBuilder() - builder.addDependency(scope, dep, remoteRepositories) + builder.addDependency(scope, dep) val graph = builder.build() val scopes = graph.createScopes() @@ -111,10 +114,9 @@ class GradleDependencyGraphBuilderTest : WordSpec({ "collect a project dependency" { val scope = "TheScope" val dep = createDependency("a-project", "a-module", "1.0", path = "p") - val maven = createMavenSupport() - val builder = GradleDependencyGraphBuilder(NAME, maven) + val builder = createGraphBuilder() - builder.addDependency(scope, dep, remoteRepositories) + builder.addDependency(scope, dep) val graph = builder.build() val scopes = graph.createScopes() @@ -125,12 +127,11 @@ class GradleDependencyGraphBuilderTest : WordSpec({ val dep1 = createDependency("org.apache.commons", "commons-lang3", "3.11") val dep2 = createDependency("org.apache.commons", "commons-collections4", "4.4") val dep3 = createDependency("my-project", "my-module", "1.0", path = "foo") - val maven = createMavenSupport() - val builder = GradleDependencyGraphBuilder(NAME, maven) + val builder = createGraphBuilder() - builder.addDependency("s1", dep1, remoteRepositories) - builder.addDependency("s2", dep2, remoteRepositories) - builder.addDependency("s3", dep3, remoteRepositories) + builder.addDependency("s1", dep1) + builder.addDependency("s2", dep2) + builder.addDependency("s3", dep3) val packageIds = builder.packages().map { it.id } packageIds shouldContainExactlyInAnyOrder setOf(dep1.toId(), dep2.toId()) @@ -149,15 +150,14 @@ class GradleDependencyGraphBuilderTest : WordSpec({ ) val dep4 = createDependency("org.apache.commons", "commons-csv", "1.5", dependencies = listOf(dep1)) val dep5 = createDependency("com.acme", "dep", "0.7", dependencies = listOf(dep3)) - val maven = createMavenSupport() - val builder = GradleDependencyGraphBuilder(NAME, maven) - - builder.addDependency(scope1, dep1, remoteRepositories) - builder.addDependency(scope2, dep1, remoteRepositories) - builder.addDependency(scope2, dep2, remoteRepositories) - builder.addDependency(scope1, dep5, remoteRepositories) - builder.addDependency(scope1, dep3, remoteRepositories) - builder.addDependency(scope2, dep4, remoteRepositories) + val builder = createGraphBuilder() + + builder.addDependency(scope1, dep1) + builder.addDependency(scope2, dep1) + builder.addDependency(scope2, dep2) + builder.addDependency(scope1, dep5) + builder.addDependency(scope1, dep3) + builder.addDependency(scope2, dep4) val graph = builder.build() graph.scopeRoots shouldHaveSize 2 @@ -198,10 +198,10 @@ class GradleDependencyGraphBuilderTest : WordSpec({ dependencies = listOf(depConfig2) ) val depLib = createDependency("com.business", "lib", "1", dependencies = listOf(depConfig1, depAcmeExclude)) - val builder = GradleDependencyGraphBuilder(NAME, createMavenSupport()) + val builder = createGraphBuilder() - builder.addDependency(scope, depAcme, remoteRepositories) - builder.addDependency(scope, depLib, remoteRepositories) + builder.addDependency(scope, depAcme) + builder.addDependency(scope, depLib) val graph = builder.build() val scopeDependencies = scopeDependencies(graph.createScopes(), scope) @@ -236,11 +236,11 @@ class GradleDependencyGraphBuilderTest : WordSpec({ "com.acme", "lib-exclude", "1.1", dependencies = listOf(depConfig2) ) - val builder = GradleDependencyGraphBuilder(NAME, createMavenSupport()) + val builder = createGraphBuilder() - builder.addDependency(scope1, depLog, remoteRepositories) - builder.addDependency(scope1, depConfig1, remoteRepositories) - builder.addDependency(scope2, depAcmeExclude, remoteRepositories) + builder.addDependency(scope1, depLog) + builder.addDependency(scope1, depConfig1) + builder.addDependency(scope2, depAcmeExclude) val graph = builder.build() val scopes = graph.createScopes() @@ -262,7 +262,7 @@ class GradleDependencyGraphBuilderTest : WordSpec({ "support scopes without dependencies" { val scope = "EmptyScope" - val builder = GradleDependencyGraphBuilder(NAME, createMavenSupport()) + val builder = createGraphBuilder() builder.addScope(scope) val graph = builder.build() @@ -277,8 +277,8 @@ class GradleDependencyGraphBuilderTest : WordSpec({ "not override a scope's dependencies when adding it again" { val scope = "compile" val dep = createDependency("org.apache.commons", "commons-lang3", "3.11") - val builder = GradleDependencyGraphBuilder(NAME, createMavenSupport()) - builder.addDependency(scope, dep, remoteRepositories) + val builder = createGraphBuilder() + builder.addDependency(scope, dep) builder.addScope(scope) val graph = builder.build() @@ -319,6 +319,15 @@ private fun createDependency( return dependency } +/** + * Create a [DependencyGraphBuilder] equipped with a [GradleDependencyHandler] that is used by the test cases in + * this class. + */ +private fun createGraphBuilder(): DependencyGraphBuilder { + val dependencyHandler = GradleDependencyHandler(NAME, createMavenSupport(), remoteRepositories) + return DependencyGraphBuilder(dependencyHandler) +} + /** * Create a [MavenSupport] mock object which is prepared to convert arbitrary artifacts to [Package] objects. */ From affcfacc142fb6f9064f09c1bcd792677ea2b44f Mon Sep 17 00:00:00 2001 From: Oliver Heger Date: Mon, 12 Apr 2021 13:03:17 +0200 Subject: [PATCH 5/6] Gradle: Use the generic DependencyGraphBuilder Replace GradleDependencyGraphBuilder by a combination of DependencyGraphBuilder and GradleDependencyHandler. The Gradle-specific implementation is going to be removed. Signed-off-by: Oliver Heger --- analyzer/src/main/kotlin/managers/Gradle.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/analyzer/src/main/kotlin/managers/Gradle.kt b/analyzer/src/main/kotlin/managers/Gradle.kt index 718693611517c..731c842ff7536 100644 --- a/analyzer/src/main/kotlin/managers/Gradle.kt +++ b/analyzer/src/main/kotlin/managers/Gradle.kt @@ -36,7 +36,7 @@ import org.gradle.tooling.internal.consumer.DefaultGradleConnector import org.ossreviewtoolkit.analyzer.AbstractPackageManagerFactory import org.ossreviewtoolkit.analyzer.PackageManager -import org.ossreviewtoolkit.analyzer.managers.utils.GradleDependencyGraphBuilder +import org.ossreviewtoolkit.analyzer.managers.utils.DependencyGraphBuilder import org.ossreviewtoolkit.analyzer.managers.utils.MavenSupport import org.ossreviewtoolkit.analyzer.managers.utils.identifier import org.ossreviewtoolkit.downloader.VersionControlSystem @@ -222,10 +222,11 @@ class Gradle( "The Gradle project '$projectName' uses the following Maven repositories: $repositories" } - val graphBuilder = GradleDependencyGraphBuilder(managerName, maven) + val dependencyHandler = GradleDependencyHandler(managerName, maven, repositories) + val graphBuilder = DependencyGraphBuilder(dependencyHandler) dependencyTreeModel.configurations.forEach { configuration -> configuration.dependencies.forEach { dependency -> - graphBuilder.addDependency(configuration.name, dependency, repositories) + graphBuilder.addDependency(configuration.name, dependency) } // Make sure that scopes without dependencies are recorded. From 836610b452aa51b275a45c837e61e2e161754777 Mon Sep 17 00:00:00 2001 From: Oliver Heger Date: Mon, 12 Apr 2021 13:04:43 +0200 Subject: [PATCH 6/6] Delete obsolete GradleDependencyGraphBuilder class The class has been fully replaced by DependencyGraphBuilder. Signed-off-by: Oliver Heger --- .../utils/GradleDependencyGraphBuilder.kt | 431 ------------------ 1 file changed, 431 deletions(-) delete mode 100644 analyzer/src/main/kotlin/managers/utils/GradleDependencyGraphBuilder.kt diff --git a/analyzer/src/main/kotlin/managers/utils/GradleDependencyGraphBuilder.kt b/analyzer/src/main/kotlin/managers/utils/GradleDependencyGraphBuilder.kt deleted file mode 100644 index 97c39305c4a3d..0000000000000 --- a/analyzer/src/main/kotlin/managers/utils/GradleDependencyGraphBuilder.kt +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (C) 2021 Bosch.IO GmbH - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ - -package org.ossreviewtoolkit.analyzer.managers.utils - -import Dependency - -import org.apache.maven.project.ProjectBuildingException - -import org.eclipse.aether.artifact.DefaultArtifact -import org.eclipse.aether.repository.RemoteRepository - -import org.ossreviewtoolkit.model.DependencyGraph -import org.ossreviewtoolkit.model.DependencyReference -import org.ossreviewtoolkit.model.Identifier -import org.ossreviewtoolkit.model.OrtIssue -import org.ossreviewtoolkit.model.Package -import org.ossreviewtoolkit.model.PackageLinkage -import org.ossreviewtoolkit.model.RootDependencyIndex -import org.ossreviewtoolkit.model.Severity -import org.ossreviewtoolkit.model.createAndLogIssue -import org.ossreviewtoolkit.utils.collectMessagesAsString -import org.ossreviewtoolkit.utils.showStackTrace - -/** - * Internal class to represent the result of a search in the dependency graph. The outcome of the search determines - * how to integrate a specific dependency into the dependency graph. - */ -private sealed class GraphSearchResult - -/** - * A specialized [GraphSearchResult] that indicates that the dependency that was searched for is already present in - * the dependency graph. This is the easiest case, as the [DependencyReference] that was found can directly be - * reused. - */ -private data class GraphSearchResultFound( - /** The reference to the dependency that was searched in the graph. */ - val ref: DependencyReference -) : GraphSearchResult() - -/** - * A specialized [GraphSearchResult] that indicates that the dependency that was searched for was not found in the - * dependency graph, but a fragment has been detected, to which it can be added. - */ -private data class GraphSearchResultNotFound( - /** The index of the fragment to which to add the dependency. */ - val fragmentIndex: Int -) : GraphSearchResult() - -/** - * A specialized [GraphSearchResult] that indicates that the dependency that was searched for in its current form - * cannot be added to any of the existing fragments. This means that either there are no fragments yet or each - * fragment contains a variant of the dependency with an incompatible dependency tree. In this case, a new fragment - * has to be added to the graph to host this special variant of this dependency. - */ -private object GraphSearchResultIncompatible : GraphSearchResult() - -/** - * A class that can construct a [DependencyGraph] from a set of Gradle dependencies that provides an efficient storage - * format for large dependency sets in many scopes. - * - * Especially in Android projects, it is common habit to have many scopes that all define their own sets of (often - * identical) dependencies. Duplicating the dependency trees for the different scopes leads to a high consumption of - * memory. This class addresses this problem by sharing the components of the dependency graph between the scopes as - * far as possible. - * - * This builder class provides the _addDependency()_ function, which has to be called for all the direct dependencies - * of the different scopes. From these dependencies it constructs a single, optimized graph that is referenced by all - * scopes. To reduce the amount of memory required even further, package identifiers are replaced by numeric indices, - * so that references in the graph are just numbers. - * - * Ideally, the resulting dependency graph contains each dependency exactly once. There are, however, cases, in which - * packages occur multiple times in the project's dependency graph with different dependencies. Such cases are - * detected, and the corresponding packages form different nodes in the graph, so that they can be distinguished - * correctly. - */ -class GradleDependencyGraphBuilder( - /** The name of the dependency manager to use as type of identifiers. */ - private val managerName: String, - - /** The helper object to resolve packages via Maven. */ - private val maven: MavenSupport -) { - /** - * A list storing the identifiers of all dependencies added to this builder. This list is then used to resolve - * dependencies based on their indices. - */ - private val dependencyIds = mutableListOf() - - /** - * A map listing the dependencies known to this builder and their numeric indices. This is used for a fast - * calculation of the index for a specific dependency. - */ - private val dependencyIndexMapping = mutableMapOf() - - /** - * Stores all the references to dependencies that have been added so far. Each element of the list represents a - * fragment of the dependency graph. For each fragment, there is a mapping from a dependency index to the - * reference pointing to the corresponding dependency tree. - */ - private val referenceMappings = mutableListOf>() - - /** The mapping from scopes to dependencies constructed by this builder. */ - private val scopeMapping = mutableMapOf>() - - /** Stores all packages encountered in the dependency tree. */ - private val resolvedPackages = mutableSetOf() - - /** - * A set storing the packages that are direct dependencies of one of the scopes. These are the entry points into - * the dependency graph. - */ - private val directDependencies = mutableSetOf() - - /** - * Add the scope with the given [scopeName] to this builder. In most cases, it is not necessary to add scopes - * explicitly, as they are recorded automatically by _addDependency()_. However, if there are scopes without - * dependencies, this function can be used to include them into the builder result. - */ - fun addScope(scopeName: String) { - scopeMapping.putIfAbsent(scopeName, emptyList()) - } - - /** - * Add the given [dependency] for the scope with the given [scopeName] to this builder. Use the provided - * [repositories] to resolve the package if necessary. - */ - fun addDependency(scopeName: String, dependency: Dependency, repositories: List) { - addDependencyToGraph(scopeName, dependency, repositories, transitive = false) - } - - /** - * Construct the [DependencyGraph] from the dependencies passed to this builder so far. - */ - fun build(): DependencyGraph = DependencyGraph(dependencyIds, directDependencies, scopeMapping) - - /** - * Return a set with all the packages that have been encountered for the current project. - */ - fun packages(): Set = resolvedPackages - - /** - * Update the dependency graph by adding the given [dependency], which may be [transitive], for the scope with name - * [scopeName]. Use the provided [repositories] to resolve the package if necessary. All the dependencies of this - * dependency are processed recursively. - */ - private fun addDependencyToGraph( - scopeName: String, - dependency: Dependency, - repositories: List, - transitive: Boolean - ): DependencyReference { - val identifier = identifierFor(dependency) - val issues = issuesForDependency(dependency) - val index = updateDependencyMappingAndPackages(identifier, dependency, repositories, issues) - - val ref = when (val result = findDependencyInGraph(index, dependency)) { - is GraphSearchResultFound -> result.ref - is GraphSearchResultNotFound -> - insertIntoGraph( - RootDependencyIndex(index, result.fragmentIndex), - scopeName, - dependency, - repositories, - issues, - transitive - ) - is GraphSearchResultIncompatible -> - insertIntoNewFragment(index, scopeName, dependency, repositories, issues, transitive) - } - - return updateScopeMapping(scopeName, ref, transitive) - } - - /** - * Update internal state for the given dependency [id]. Check whether this ID is already known. If not, add it - * to the data managed by this instance, resolve the package, and update the [issues] if necessary. Return the - * numeric index for this dependency. - */ - private fun updateDependencyMappingAndPackages( - id: String, - dependency: Dependency, - repositories: List, - issues: MutableList - ): Int { - val dependencyIndex = dependencyIndexMapping[id] - if (dependencyIndex != null) return dependencyIndex - - updateResolvedPackages(id, dependency, repositories, issues) - return dependencyIds.size.also { - dependencyIds += id - dependencyIndexMapping[id] = it - } - } - - /** - * Search for the [dependency] with the given [index] in the fragments of the dependency graph. Return a - * [GraphSearchResult] that indicates how to proceed with this dependency. - */ - private fun findDependencyInGraph(index: Int, dependency: Dependency): GraphSearchResult { - val mappingForCompatibleFragment = referenceMappings.find { mapping -> - mapping[index]?.takeIf { dependencyTreeEquals(it, dependency) } != null - } - - val compatibleReference = mappingForCompatibleFragment?.let { it[index] } - return compatibleReference?.let { GraphSearchResultFound(it) } ?: handleNoCompatibleDependencyInGraph(index) - } - - /** - * Determine how to deal with the dependency with the given [index], for which no compatible variant was found - * in the dependency graph. Try to find a fragment, in which the dependency can be inserted. If this fails, a new - * fragment has to be added. - */ - private fun handleNoCompatibleDependencyInGraph(index: Int): GraphSearchResult { - val mappingToInsert = referenceMappings.withIndex().find { index !in it.value } - return mappingToInsert?.let { GraphSearchResultNotFound(it.index) } ?: GraphSearchResultIncompatible - } - - /** - * Check whether the dependency tree spawned by [dependency] matches the one [ref] points to. Using this function, - * packages are identified that occur multiple times in the dependency graph with different sets of dependencies; - * these have to be placed in separate fragments of the dependency graph. - */ - private fun dependencyTreeEquals(ref: DependencyReference, dependency: Dependency): Boolean { - if (ref.dependencies.size != dependency.dependencies.size) return false - - val dependencies1 = ref.dependencies.map { dependencyIds[it.pkg] } - val dependencies2 = dependency.dependencies.associateBy(::identifierFor) - if (!dependencies2.keys.containsAll(dependencies1)) return false - - return ref.dependencies.all { refDep -> - dependencies2[dependencyIds[refDep.pkg]]?.let { dependencyTreeEquals(refDep, it) } ?: false - } - } - - /** - * Add a new fragment to the dependency graph for the [dependency] with the given [index], which may be - * [transitive] and belongs to the scope with the given [scopeName]. This function is called for dependencies that - * cannot be added to already existing fragments. Therefore, create a new fragment and add the [dependency] to it, - * together with its own dependencies. Store the given [issues] for the dependency and use the given - * [repositories] to resolve packages. - */ - private fun insertIntoNewFragment( - index: Int, scopeName: String, - dependency: Dependency, - repositories: List, - issues: List, - transitive: Boolean - ): DependencyReference { - val fragmentMapping = mutableMapOf() - val dependencyIndex = RootDependencyIndex(index, referenceMappings.size) - referenceMappings += fragmentMapping - return insertIntoGraph(dependencyIndex, scopeName, dependency, repositories, issues, transitive) - } - - /** - * Insert the [dependency] with the given [RootDependencyIndex][index], which belongs to the scope with the given - * [scopeName] and may be [transitive] into the dependency graph. Insert the dependencies of this [dependency] - * recursively and use the [repositories] to resolve packages. Create a new [DependencyReference] for the - * dependency and initialize it with the list of [issues]. - */ - private fun insertIntoGraph( - index: RootDependencyIndex, - scopeName: String, - dependency: Dependency, - repositories: List, - issues: List, - transitive: Boolean - ): DependencyReference { - val transitiveDependencies = dependency.dependencies.map { - addDependencyToGraph(scopeName, it, repositories, transitive = true) - } - - val fragmentMapping = referenceMappings[index.fragment] - val ref = DependencyReference( - pkg = index.root, - fragment = index.fragment, - dependencies = transitiveDependencies.toSortedSet(), - linkage = dependency.linkage(), - issues = issues - ) - - fragmentMapping[index.root] = ref - return updateDirectDependencies(ref, transitive) - } - - /** - * Return a list of issues that is initially populated with errors or warnings from the given [dependency]. - */ - private fun issuesForDependency(dependency: Dependency): MutableList { - val issues = mutableListOf() - - dependency.error?.let { - issues += createAndLogIssue( - source = managerName, - message = it, - severity = Severity.ERROR - ) - } - - dependency.warning?.let { - issues += createAndLogIssue( - source = managerName, - message = it, - severity = Severity.WARNING - ) - } - - return issues - } - - /** - * Construct a [Package] for the given [dependency] using the [repositories] provided. Add the new package to the - * set managed by this object. If this fails, record a corresponding message in [issues]. - */ - private fun updateResolvedPackages( - identifier: String, - dependency: Dependency, - repositories: List, - issues: MutableList - ) { - // 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 - - val pkg = try { - val artifact = DefaultArtifact( - dependency.groupId, dependency.artifactId, dependency.classifier, - dependency.extension, dependency.version - ) - - maven.parsePackage(artifact, repositories) - } catch (e: ProjectBuildingException) { - e.showStackTrace() - - issues += createAndLogIssue( - source = managerName, - message = "Could not get package information for dependency '$identifier': " + - e.collectMessagesAsString() - ) - - Package.EMPTY.copy( - id = Identifier( - type = "Maven", - namespace = dependency.groupId, - name = dependency.artifactId, - version = dependency.version - ) - ) - } - - resolvedPackages += pkg - } - - /** - * Add the given [dependency reference][ref] to the set of direct dependencies if it is not [transitive]. If one of - * the direct dependencies of this package is in this set, it is removed, as it is obviously no direct dependency. - * Because this function is called for all dependencies, all transitive dependencies are eventually removed. - */ - private fun updateDirectDependencies(ref: DependencyReference, transitive: Boolean): DependencyReference { - directDependencies.removeAll(ref.dependencies) - if (!transitive) directDependencies += ref - return ref - } - - /** - * Update the scope mapping for the given [scopeName] to depend on [ref], which may be a [transitive] dependency. - * The scope mapping records all the direct dependencies of scopes. - */ - private fun updateScopeMapping( - scopeName: String, ref: DependencyReference, transitive: Boolean - ): DependencyReference { - if (!transitive) { - val index = RootDependencyIndex(ref.pkg, ref.fragment) - scopeMapping.compute(scopeName) { _, ids -> - ids?.let { it + index } ?: listOf(index) - } - } - - return ref - } - - /** - * Generate a string that uniquely identifies this [dependency]. This string is also used to construct an - * [Identifier] for this package. - */ - private fun identifierFor(dependency: Dependency): String = - "${dependencyType(dependency)}:${dependency.groupId}:${dependency.artifactId}:${dependency.version}" - - /** - * Determine the type of the given [dependency]. This manager implementation uses Maven to resolve packages, so - * the type of dependencies to packages is typically _Maven_ unless no pom is available. Only for module - * dependencies, the type of this manager is used. - */ - private fun dependencyType(dependency: Dependency): String = - if (dependency.isProjectDependency()) { - managerName - } else { - dependency.pomFile?.let { "Maven" } ?: "Unknown" - } -} - -/** - * Determine the [PackageLinkage] for this [Dependency]. - */ -private fun Dependency.linkage() = - if (isProjectDependency()) { - PackageLinkage.PROJECT_DYNAMIC - } else { - PackageLinkage.DYNAMIC - } - -/** - * Return a flag whether this dependency references another project in the current build. - */ -private fun Dependency.isProjectDependency() = localPath != null