Skip to content

Commit

Permalink
Project: Drop support for a project-local dependency graph
Browse files Browse the repository at this point in the history
Dependency graphs should always be shared between projects, as this is
more efficient.

Signed-off-by: Oliver Heger <oliver.heger@bosch.io>
  • Loading branch information
oheger-bosch committed Apr 28, 2021
1 parent 63dddfa commit 756773a
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 93 deletions.
28 changes: 3 additions & 25 deletions model/src/main/kotlin/Project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,6 @@ data class Project(
@JsonProperty("scopes")
val scopeDependencies: SortedSet<Scope>? = null,

/**
* Contains dependency information as a [DependencyGraph]. This is an alternative format to store the dependencies
* referenced by the various scopes. Use the [scopes] property to access dependency information independent on
* the concrete representation.
* TODO: Remove this after all affected package managers have been converted to use a shared graph.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
val dependencyGraph: DependencyGraph? = null,

/**
* Contains dependency information as a set of scope names in case a shared [DependencyGraph] is used. The scopes
* of this project and their dependencies can then be constructed as the corresponding sub graph of the shared
Expand Down Expand Up @@ -131,8 +122,8 @@ data class Project(
}

init {
require(scopeDependencies == null || dependencyGraph == null) {
"Not both 'scopeDependencies' and 'dependencyGraph' may be set, as otherwise it is ambiguous which one " +
require(scopeDependencies == null || scopeNames == null) {
"Not both 'scopeDependencies' and 'scopeNames' may be set, as otherwise it is ambiguous which one " +
"to use."
}
}
Expand All @@ -142,19 +133,7 @@ data class Project(
* matter whether this information has been initialized directly or has been encoded in a [DependencyGraph].
*/
@get:JsonIgnore
val scopes by lazy {
dependencyGraph?.createScopes() ?: scopeDependencies ?: sortedSetOf()
}

/**
* Return a [Project] instance that has its scope information directly available. A project can be constructed
* either with a set of [Scope] objects or with a [DependencyGraph]. In the latter case, the graph has to be
* converted first into the scope representation. This function ensures that this step was done: If the project
* has a [DependencyGraph], it returns a new instance with the converted scope information (and the dependency
* graph removed to save memory); otherwise, it returns this same object.
*/
fun withResolvedScopes(): Project =
takeUnless { dependencyGraph != null } ?: copy(scopeDependencies = scopes, dependencyGraph = null)
val scopes by lazy { scopeDependencies ?: sortedSetOf() }

/**
* Return a [Project] instance that has its scope information directly available, resolved from the given [graph].
Expand All @@ -166,7 +145,6 @@ data class Project(
takeUnless { graph != null && scopeNames != null }
?: copy(
scopeDependencies = graph!!.createScopes(qualifiedScopeNames()),
dependencyGraph = null,
scopeNames = null
)

Expand Down
90 changes: 22 additions & 68 deletions model/src/test/kotlin/ProjectTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,23 @@
package org.ossreviewtoolkit.model

import io.kotest.assertions.fail
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.collections.beEmpty
import io.kotest.matchers.collections.containExactlyInAnyOrder
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.collections.shouldContainExactly
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.nulls.beNull
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.beTheSameInstanceAs

import io.mockk.mockk

import java.io.File
import java.time.Instant
import java.util.SortedSet

import org.ossreviewtoolkit.utils.test.containExactly
import org.ossreviewtoolkit.utils.test.createTestTempFile

private fun readAnalyzerResult(analyzerResultFilename: String): Project =
File("../analyzer/src/funTest/assets/projects/synthetic")
Expand All @@ -53,20 +52,6 @@ private val langId = Identifier("$MANAGER:org.apache.commons:commons-lang3:3.5")
private val strutsId = Identifier("$MANAGER:org.apache.struts:struts2-assembly:2.5.14.1")
private val csvId = Identifier("$MANAGER:org.apache.commons:commons-csv:1.4")

/**
* Create a [Project] whose dependencies are represented as a [DependencyGraph].
*/
private fun projectWithDependencyGraph(): Project =
Project(
id = projectId,
definitionFilePath = "/some/path",
declaredLicenses = sortedSetOf(),
vcs = VcsInfo.EMPTY,
homepageUrl = "https//www.test-project.org",
scopeDependencies = null,
dependencyGraph = createDependencyGraph()
)

/**
* Create a [DependencyGraph] containing some test dependencies, optionally with [qualified] scope names.
*/
Expand Down Expand Up @@ -101,18 +86,23 @@ private fun createDependencyGraph(qualified: Boolean = false): DependencyGraph {
*/
private fun Identifier.toDependencyId() = "$MANAGER:$namespace:$name:$version"

/**
* Lookup the scope with the given [name] in the set of [scopes].
*/
private fun findScope(scopes: SortedSet<Scope>, name: String): Scope =
scopes.find { it.name == name } ?: fail("Could not resolve scope $name.")

/**
* Return a set with the identifiers of the (direct) dependencies of the given [scope].
*/
private fun scopeDependencies(scope: Scope): Set<Identifier> = scope.dependencies.map { it.id }.toSet()

class ProjectTest : WordSpec({
"init" should {
"fail if both scopeDependencies and scopeNames are provided" {
shouldThrow<IllegalArgumentException> {
Project(
id = projectId,
definitionFilePath = "/some/path/pom.xml",
declaredLicenses = sortedSetOf(),
vcs = VcsInfo.EMPTY,
homepageUrl = "https://test-project.example.org/home.html",
scopeDependencies = sortedSetOf(mockk()),
scopeNames = sortedSetOf("test", "compile", "other")
)
}
}
}

"collectDependencies" should {
"get all dependencies by default" {
val project = readAnalyzerResult("gradle-expected-output-lib.yml")
Expand Down Expand Up @@ -183,52 +173,28 @@ class ProjectTest : WordSpec({
project.scopes shouldBe project.scopeDependencies
}

"be initialized from a dependency graph" {
val project = projectWithDependencyGraph()
val scopes = project.scopes
scopes.map { it.name } shouldContainExactly listOf("compile", "default", "partial", "test")

val defaultScope = findScope(scopes, "default")
scopeDependencies(defaultScope) should io.kotest.matchers.collections.containExactly(exampleId)
val testScope = findScope(scopes, "test")
scopeDependencies(testScope) should containExactlyInAnyOrder(exampleId, csvId)
val partialScope = findScope(scopes, "partial")
scopeDependencies(partialScope) should io.kotest.matchers.collections.containExactly(textId)
}

"be initialized to an empty set if no information is available" {
val project = Project(
id = projectId,
definitionFilePath = "/some/path",
declaredLicenses = sortedSetOf(),
vcs = VcsInfo.EMPTY,
homepageUrl = "https//www.test-project.org",
scopeDependencies = null,
dependencyGraph = null
)

project.scopes.shouldBeEmpty()
}
}

"withResolvedScopes" should {
"return the same instance if no dependency graph is available" {
"return the same instance if scope dependencies are available" {
val project = readAnalyzerResult("maven-expected-output-app.yml")

val resolvedProject = project.withResolvedScopes()
val resolvedProject = project.withResolvedScopes(createDependencyGraph())

resolvedProject should beTheSameInstanceAs(project)
}

"return an instance with scope information extracted from the dependency graph" {
val project = projectWithDependencyGraph()

val resolvedProject = project.withResolvedScopes()

resolvedProject.dependencyGraph.shouldBeNull()
resolvedProject.scopes shouldBe project.scopes
}

"return an instance with scope information extracted from a sub graph of a shared dependency graph" {
val project = Project(
id = projectId,
Expand All @@ -246,19 +212,7 @@ class ProjectTest : WordSpec({

resolvedProject.scopeNames should beNull()
resolvedProject.scopes shouldHaveSize 1
findScope(resolvedProject.scopes, "partial")
}
}

"A Project" should {
"be serializable with a dependency graph" {
val outputFile = createTestTempFile(prefix = "project", suffix = ".yml")

val project = projectWithDependencyGraph()
outputFile.writeValue(project)

val projectCopy = outputFile.readValue<Project>()
projectCopy.scopes shouldBe project.scopes
resolvedProject.scopes.find { it.name == "partial" } ?: fail("Could not resolve scope ${"partial"}.")
}
}
})

0 comments on commit 756773a

Please sign in to comment.