Skip to content

Commit

Permalink
refactor(commands)!: Migrate VCS plugins to new plugin API
Browse files Browse the repository at this point in the history
Relates to #9403.

Signed-off-by: Sebastian Schuberth <[email protected]>
  • Loading branch information
sschuberth committed Jan 15, 2025
1 parent fa6e2be commit 87b4c04
Show file tree
Hide file tree
Showing 22 changed files with 113 additions and 94 deletions.
5 changes: 3 additions & 2 deletions cli/src/funTest/kotlin/AnalyzerFunTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
import org.ossreviewtoolkit.model.config.PackageManagerConfiguration
import org.ossreviewtoolkit.model.config.RepositoryConfiguration
import org.ossreviewtoolkit.model.toYaml
import org.ossreviewtoolkit.plugins.versioncontrolsystems.git.GitRepo
import org.ossreviewtoolkit.plugins.api.PluginConfig
import org.ossreviewtoolkit.plugins.versioncontrolsystems.git.GitRepoFactory
import org.ossreviewtoolkit.utils.test.getAssetFile
import org.ossreviewtoolkit.utils.test.matchExpectedResult
import org.ossreviewtoolkit.utils.test.patchActualResult
Expand All @@ -53,7 +54,7 @@ class AnalyzerFunTest : WordSpec({
)
)
val outputDir = tempdir().also {
GitRepo.Factory().create().download(pkg, it)
GitRepoFactory().create(PluginConfig()).download(pkg, it)
}

val result = analyze(outputDir, packageManagers = emptySet()).toYaml()
Expand Down
1 change: 1 addition & 0 deletions downloader/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ plugins {

dependencies {
api(projects.model)
api(projects.plugins.api)

implementation(projects.utils.ortUtils)

Expand Down
46 changes: 25 additions & 21 deletions downloader/src/main/kotlin/VersionControlSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ import org.ossreviewtoolkit.model.Package
import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.model.config.LicenseFilePatterns
import org.ossreviewtoolkit.model.config.PluginConfiguration
import org.ossreviewtoolkit.model.orEmpty
import org.ossreviewtoolkit.plugins.api.Plugin
import org.ossreviewtoolkit.plugins.api.PluginConfig
import org.ossreviewtoolkit.utils.common.CommandLineTool
import org.ossreviewtoolkit.utils.common.collectMessages
import org.ossreviewtoolkit.utils.common.uppercaseFirstChar
Expand All @@ -39,19 +40,22 @@ import org.ossreviewtoolkit.utils.ort.showStackTrace

import org.semver4j.Semver

abstract class VersionControlSystem {
abstract class VersionControlSystem : Plugin {
companion object {
private fun getAllVcsByPriority(configs: Map<String, PluginConfig>) =
ALL.map { (id, factory) ->
val config = configs[id] ?: PluginConfig()
factory.create(config)
}.sortedByDescending {
it.priority
}

/**
* Return the applicable VCS for the given [vcsType], or null if none is applicable.
*/
fun forType(vcsType: VcsType, configs: Map<String, PluginConfiguration> = emptyMap()) =
ALL.values.filter { factory ->
VcsType.forName(factory.type) == vcsType
}.asSequence().map { factory ->
val config = configs[factory.type]
factory.create(options = config?.options.orEmpty(), secrets = emptyMap())
}.find { vcs ->
vcs.isAvailable()
fun forType(vcsType: VcsType, configs: Map<String, PluginConfig> = emptyMap()) =
getAllVcsByPriority(configs).find { vcs ->
vcs.type == vcsType && vcs.isAvailable()
}

/**
Expand All @@ -65,7 +69,7 @@ abstract class VersionControlSystem {
* Return the applicable VCS for the given [vcsUrl], or null if none is applicable.
*/
@Synchronized
fun forUrl(vcsUrl: String, configs: Map<String, PluginConfiguration> = emptyMap()) =
fun forUrl(vcsUrl: String, configs: Map<String, PluginConfig> = emptyMap()) =
// Do not use getOrPut() here as it cannot handle null values, also see
// https://youtrack.jetbrains.com/issue/KT-21392.
if (vcsUrl in urlToVcsMap) {
Expand All @@ -75,11 +79,8 @@ abstract class VersionControlSystem {
when (val type = VcsHost.parseUrl(vcsUrl).type) {
VcsType.UNKNOWN -> {
// ...then eventually try to determine the type also dynamically.
ALL.values.asSequence().map { factory ->
val config = configs[factory.type]
factory.create(options = config?.options.orEmpty(), secrets = emptyMap())
}.find { vcs ->
vcs.isAvailable() && vcs.isApplicableUrl(vcsUrl)
getAllVcsByPriority(configs).find { vcs ->
vcs.isApplicableUrl(vcsUrl) && vcs.isAvailable()
}
}

Expand All @@ -99,16 +100,13 @@ abstract class VersionControlSystem {
* Return the applicable VCS working tree for the given [vcsDirectory], or null if none is applicable.
*/
@Synchronized
fun forDirectory(vcsDirectory: File, configs: Map<String, PluginConfiguration> = emptyMap()): WorkingTree? {
fun forDirectory(vcsDirectory: File, configs: Map<String, PluginConfig> = emptyMap()): WorkingTree? {
val absoluteVcsDirectory = vcsDirectory.absoluteFile

return if (absoluteVcsDirectory in dirToVcsMap) {
dirToVcsMap[absoluteVcsDirectory]
} else {
ALL.values.asSequence().map { factory ->
val config = configs[factory.type]
factory.create(options = config?.options.orEmpty(), secrets = emptyMap())
}.mapNotNull {
getAllVcsByPriority(configs).mapNotNull {
if (it is CommandLineTool && !it.isInPath()) {
null
} else {
Expand Down Expand Up @@ -168,6 +166,12 @@ abstract class VersionControlSystem {
*/
abstract val type: VcsType

/**
* The [priority] defines the order in which VCS implementaions are to be used. A higher value means a higher
* priority.
*/
abstract val priority: Int

/**
* A list of symbolic names that point to the latest revision.
*/
Expand Down
21 changes: 5 additions & 16 deletions downloader/src/main/kotlin/VersionControlSystemFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,17 @@

package org.ossreviewtoolkit.downloader

import java.util.ServiceLoader

import org.ossreviewtoolkit.utils.common.Plugin
import org.ossreviewtoolkit.utils.common.TypedConfigurablePluginFactory
import org.ossreviewtoolkit.plugins.api.PluginFactory

/**
* An abstract class to be implemented by factories for [Version Control Systems][VersionControlSystem] for use with the
* [ServiceLoader] mechanism. The [type] parameter denotes which VCS type is supported by this plugin, while the
* [priority] parameter defines the order if more than one plugin supports the same VCS type.
* A factory interface for creating [VersionControlSystem] instances.
*/
abstract class VersionControlSystemFactory<CONFIG>(override val type: String, val priority: Int) :
TypedConfigurablePluginFactory<CONFIG, VersionControlSystem> {
interface VersionControlSystemFactory : PluginFactory<VersionControlSystem> {
companion object {
/**
* All [Version Control System factories][VersionControlSystemFactory] available in the classpath, associated by
* their names, sorted by priority.
* their ids.
*/
val ALL by lazy {
Plugin.getAll<VersionControlSystemFactory<*>>()
.toList()
.sortedByDescending { (_, vcsFactory) -> vcsFactory.priority }
.toMap()
}
val ALL by lazy { PluginFactory.getAll<VersionControlSystemFactory, VersionControlSystem>() }
}
}
7 changes: 4 additions & 3 deletions downloader/src/test/kotlin/VersionControlSystemTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import java.io.IOException
import org.ossreviewtoolkit.model.Package
import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.plugins.versioncontrolsystems.git.Git
import org.ossreviewtoolkit.plugins.api.PluginConfig
import org.ossreviewtoolkit.plugins.versioncontrolsystems.git.GitFactory

class VersionControlSystemTest : WordSpec({
val vcsRoot = File("..").absoluteFile.normalize()
Expand Down Expand Up @@ -85,7 +86,7 @@ class VersionControlSystemTest : WordSpec({

every { workingTree.guessRevisionName(any(), any()) } returns "v1.6.0"

Git.Factory().create()
GitFactory().create(PluginConfig())
.getRevisionCandidates(workingTree, pkg, allowMovingRevisions = true) shouldBeSuccess listOf(
"v1.6.0"
)
Expand All @@ -109,7 +110,7 @@ class VersionControlSystemTest : WordSpec({
every { workingTree.listRemoteBranches() } returns listOf("main")
every { workingTree.listRemoteTags() } returns emptyList()

Git.Factory().create()
GitFactory().create(PluginConfig())
.getRevisionCandidates(workingTree, pkg, allowMovingRevisions = true) shouldBeSuccess listOf(
"master",
"main"
Expand Down
4 changes: 3 additions & 1 deletion plugins/version-control-systems/git/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

plugins {
// Apply precompiled plugins.
id("ort-library-conventions")
id("ort-plugin-conventions")

// Apply third-party plugins.
alias(libs.plugins.kotlinSerialization)
Expand All @@ -43,6 +43,8 @@ dependencies {
implementation(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.serialization.xml)

ksp(projects.downloader)

runtimeOnly(libs.jgit.ssh.apache.agent) {
exclude(group = "org.apache.sshd", module = "sshd-sftp")
.because("it is not required for cloning via SSH and causes issues with GraalVM native images")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.ossreviewtoolkit.model.Identifier
import org.ossreviewtoolkit.model.Package
import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.plugins.api.PluginConfig

private const val PKG_VERSION = "0.4.1"

Expand All @@ -43,7 +44,7 @@ private const val REPO_REV_FOR_VERSION = "371b23f37da064687518bace268d607a92ecbe
private const val REPO_PATH_FOR_VERSION = "specs"

class GitDownloadFunTest : StringSpec() {
private val git = Git.Factory().create()
private val git = GitFactory().create(PluginConfig())
private lateinit var outputDir: File

override suspend fun beforeTest(testCase: TestCase) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import java.io.File
import org.ossreviewtoolkit.downloader.WorkingTree
import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.plugins.api.PluginConfig

private val branches = mapOf(
"main" to "6f09f276c4426c387c6663f54bbd45aea8d81dac",
Expand All @@ -44,7 +45,7 @@ private val tags = mapOf(
)

class GitFunTest : WordSpec({
val git = Git.Factory().create()
val git = GitFactory().create(PluginConfig())
val vcsInfo = VcsInfo(
type = VcsType.GIT,
url = "https://github.com/oss-review-toolkit/ort-test-data-git.git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.ossreviewtoolkit.downloader.WorkingTree
import org.ossreviewtoolkit.model.Package
import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.plugins.api.PluginConfig

private const val REPO_URL = "https://github.com/oss-review-toolkit/ort-test-data-git-repo?manifest=manifest.xml"
private const val REPO_REV = "31588aa8f8555474e1c3c66a359ec99e4cd4b1fa"
Expand All @@ -44,7 +45,7 @@ class GitRepoDownloadFunTest : StringSpec() {

override suspend fun beforeSpec(spec: Spec) {
outputDir = tempdir()
workingTree = GitRepo().download(pkg, outputDir)
workingTree = GitRepoFactory().create(PluginConfig()).download(pkg, outputDir)
}

init {
Expand Down Expand Up @@ -99,7 +100,7 @@ class GitRepoDownloadFunTest : StringSpec() {
"submodules/test-data-npm/long.js"
).associateWith { VersionControlSystem.getPathInfo(outputDir.resolve(it)) }

val workingTree = GitRepo().getWorkingTree(outputDir)
val workingTree = GitRepoFactory().create(PluginConfig()).getWorkingTree(outputDir)
workingTree.getNested() shouldBe expectedSubmodules
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ import org.ossreviewtoolkit.downloader.VersionControlSystem
import org.ossreviewtoolkit.downloader.WorkingTree
import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.plugins.api.PluginConfig

class GitWorkingTreeFunTest : StringSpec({
val git = Git.Factory().create()
val git = GitFactory().create(PluginConfig())
val repoDir = tempdir()
val vcsInfo = VcsInfo(
type = VcsType.GIT,
Expand Down
26 changes: 12 additions & 14 deletions plugins/version-control-systems/git/src/main/kotlin/Git.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ import org.ossreviewtoolkit.downloader.VersionControlSystemFactory
import org.ossreviewtoolkit.downloader.WorkingTree
import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.plugins.api.OrtPlugin
import org.ossreviewtoolkit.plugins.api.PluginDescriptor
import org.ossreviewtoolkit.utils.common.CommandLineTool
import org.ossreviewtoolkit.utils.common.Options
import org.ossreviewtoolkit.utils.common.Os
import org.ossreviewtoolkit.utils.common.collectMessages
import org.ossreviewtoolkit.utils.common.safeMkdirs
Expand All @@ -61,8 +62,6 @@ import org.ossreviewtoolkit.utils.ort.showStackTrace
import org.semver4j.RangesList
import org.semver4j.RangesListFactory

const val DEFAULT_HISTORY_DEPTH = 50

// Replace prefixes of Git submodule repository URLs.
private val REPOSITORY_URL_PREFIX_REPLACEMENTS = listOf(
"git://" to "https://"
Expand Down Expand Up @@ -94,7 +93,15 @@ object GitCommand : CommandLineTool {
* - *updateNestedSubmodules*: Whether nested submodules should be updated, or if only top-level submodules should be
* considered. Defaults to true.
*/
class Git internal constructor(private val config: GitConfig) : VersionControlSystem() {
@OrtPlugin(
displayName = "Git",
description = "A VCS implementation to interact with Git repositories.",
factory = VersionControlSystemFactory::class
)
class Git(
override val descriptor: PluginDescriptor,
private val config: GitConfig
) : VersionControlSystem() {
companion object {
init {
// Make sure that JGit uses the exact same authentication information as ORT itself. This addresses
Expand Down Expand Up @@ -126,17 +133,8 @@ class Git internal constructor(private val config: GitConfig) : VersionControlSy
}
}

class Factory : VersionControlSystemFactory<GitConfig>(VcsType.GIT.toString(), 100) {
override fun create(config: GitConfig) = Git(config)

override fun parseConfig(options: Options, secrets: Options) =
GitConfig(
historyDepth = options["historyDepth"]?.toIntOrNull() ?: DEFAULT_HISTORY_DEPTH,
updateNestedSubmodules = options["updateNestedSubmodules"]?.toBooleanStrictOrNull() ?: true
)
}

override val type = VcsType.GIT
override val priority = 100
override val latestRevisionNames = listOf("HEAD", "@")

override fun getVersion() = GitCommand.getVersion()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@

package org.ossreviewtoolkit.plugins.versioncontrolsystems.git

/** Git-specific [org.ossreviewtoolkit.downloader.VersionControlSystem] configuration. */
import org.ossreviewtoolkit.downloader.VersionControlSystem
import org.ossreviewtoolkit.plugins.api.OrtPluginOption

const val DEFAULT_HISTORY_DEPTH = 50

/** Git-specific [VersionControlSystem] configuration. */
data class GitConfig(
/** Depth of the commit history to fetch. */
@OrtPluginOption(defaultValue = "$DEFAULT_HISTORY_DEPTH")
val historyDepth: Int,

/** Whether nested submodules should be updated, or if only top-level submodules should be considered. */
@OrtPluginOption(defaultValue = "true")
val updateNestedSubmodules: Boolean
)
21 changes: 13 additions & 8 deletions plugins/version-control-systems/git/src/main/kotlin/GitRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ import org.ossreviewtoolkit.downloader.WorkingTree
import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.model.utils.parseRepoManifestPath
import org.ossreviewtoolkit.plugins.api.OrtPlugin
import org.ossreviewtoolkit.plugins.api.PluginDescriptor
import org.ossreviewtoolkit.utils.common.CommandLineTool
import org.ossreviewtoolkit.utils.common.Options
import org.ossreviewtoolkit.utils.common.Os
import org.ossreviewtoolkit.utils.common.ProcessCapture
import org.ossreviewtoolkit.utils.common.collectMessages
Expand Down Expand Up @@ -88,13 +89,17 @@ internal object GitRepoCommand : CommandLineTool {
override fun displayName(): String = "GitRepo"
}

class GitRepo internal constructor() : VersionControlSystem() {
class Factory : VersionControlSystemFactory<Unit>(VcsType.GIT_REPO.toString(), 50) {
override fun create(config: Unit) = GitRepo()
override fun parseConfig(options: Options, secrets: Options) = Unit
}

@OrtPlugin(
displayName = "Git-Repo",
description = "A VCS implementation to interact with Git-Repo repositories.",
factory = VersionControlSystemFactory::class
)
class GitRepo(
override val descriptor: PluginDescriptor,
private val config: GitConfig
) : VersionControlSystem() {
override val type = VcsType.GIT_REPO
override val priority = 50
override val latestRevisionNames = listOf("HEAD", "@")

override fun getVersion() = GitRepoCommand.getVersion()
Expand Down Expand Up @@ -141,7 +146,7 @@ class GitRepo internal constructor() : VersionControlSystem() {

paths.forEach { path ->
// Add the nested Repo project.
val workingTree = Git.Factory().create().getWorkingTree(getRootPath().resolve(path))
val workingTree = Git(GitFactory.descriptor, config).getWorkingTree(getRootPath().resolve(path))
nested[path] = workingTree.getInfo()

// Add the Git submodules of the nested Repo project.
Expand Down

This file was deleted.

Loading

0 comments on commit 87b4c04

Please sign in to comment.