Skip to content

Commit d352301

Browse files
committed
refactor(vcs): Enable to provide VCS-specific configuration options
While VCS implementations are already plugins, they are not yet configurable. VCS implementations require common configurations (e.g., `revision`, `recursive`) and should support also VCS-specific configurations if they are consumed via their API. This allows to add functionality to individual VCS implementations without the need to implement them for all of them. The effects are limited to usage through the command-line interface of the Downloader tool, designed primarily for scripted operations. In other stages of the ORT pipeline these configuration options have no effect. Fixes #8556. Signed-off-by: Wolfgang Klenk <[email protected]>
1 parent ddfdef1 commit d352301

File tree

20 files changed

+342
-60
lines changed

20 files changed

+342
-60
lines changed

cli/src/funTest/kotlin/AnalyzerFunTest.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit
2929
import org.ossreviewtoolkit.analyzer.Analyzer
3030
import org.ossreviewtoolkit.analyzer.PackageManagerFactory
3131
import org.ossreviewtoolkit.analyzer.analyze
32+
import org.ossreviewtoolkit.downloader.VersionControlSystemConfiguration
3233
import org.ossreviewtoolkit.model.Package
3334
import org.ossreviewtoolkit.model.VcsInfo
3435
import org.ossreviewtoolkit.model.VcsType
@@ -52,7 +53,10 @@ class AnalyzerFunTest : WordSpec({
5253
revision = "31588aa8f8555474e1c3c66a359ec99e4cd4b1fa"
5354
)
5455
)
55-
val outputDir = tempdir().also { GitRepo().download(pkg, it) }
56+
val outputDir = tempdir().also {
57+
GitRepo.Factory().create(VersionControlSystemConfiguration())
58+
.download(pkg, it)
59+
}
5660

5761
val result = analyze(outputDir, packageManagers = emptySet()).toYaml()
5862

downloader/src/main/kotlin/Downloader.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,9 @@ class Downloader(private val config: DownloaderConfiguration) {
232232
var applicableVcs: VersionControlSystem? = null
233233

234234
if (pkg.vcsProcessed.type != VcsType.UNKNOWN) {
235-
applicableVcs = VersionControlSystem.forType(pkg.vcsProcessed.type)
235+
applicableVcs = VersionControlSystem.forType(
236+
pkg.vcsProcessed.type, config.getCaseInsensitiveVersionControlSystems()
237+
)
236238
logger.info {
237239
applicableVcs?.let {
238240
"Detected VCS type '${it.type}' from type name '${pkg.vcsProcessed.type}'."
@@ -241,7 +243,9 @@ class Downloader(private val config: DownloaderConfiguration) {
241243
}
242244

243245
if (applicableVcs == null) {
244-
applicableVcs = VersionControlSystem.forUrl(pkg.vcsProcessed.url)
246+
applicableVcs = VersionControlSystem.forUrl(
247+
pkg.vcsProcessed.url, config.getCaseInsensitiveVersionControlSystems()
248+
)
245249
logger.info {
246250
applicableVcs?.let {
247251
"Detected VCS type '${it.type}' from URL ${pkg.vcsProcessed.url}."

downloader/src/main/kotlin/VersionControlSystem.kt

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@ import java.io.IOException
2424

2525
import org.apache.logging.log4j.kotlin.logger
2626

27+
import org.ossreviewtoolkit.downloader.VersionControlSystemFactory.Companion.ALL
2728
import org.ossreviewtoolkit.model.Package
2829
import org.ossreviewtoolkit.model.VcsInfo
2930
import org.ossreviewtoolkit.model.VcsType
3031
import org.ossreviewtoolkit.model.config.LicenseFilePatterns
32+
import org.ossreviewtoolkit.model.config.VersionControlSystemConfiguration
3133
import org.ossreviewtoolkit.model.orEmpty
3234
import org.ossreviewtoolkit.utils.common.CommandLineTool
33-
import org.ossreviewtoolkit.utils.common.Plugin
3435
import org.ossreviewtoolkit.utils.common.collectMessages
3536
import org.ossreviewtoolkit.utils.common.uppercaseFirstChar
3637
import org.ossreviewtoolkit.utils.ort.ORT_REPO_CONFIG_FILENAME
@@ -44,22 +45,23 @@ abstract class VersionControlSystem(
4445
* the version control system is available.
4546
*/
4647
private val commandLineTool: CommandLineTool? = null
47-
) : Plugin {
48+
) {
4849
companion object {
49-
/**
50-
* All [version control systems][VersionControlSystem] available in the classpath, sorted by their priority.
51-
*/
52-
val ALL by lazy {
53-
Plugin.getAll<VersionControlSystem>().toList().sortedByDescending { (_, vcs) -> vcs.priority }.toMap()
54-
}
55-
5650
/**
5751
* Return the applicable VCS for the given [vcsType], or null if none is applicable.
5852
*/
59-
fun forType(vcsType: VcsType) =
60-
ALL.values.find {
61-
it.isAvailable() && it.isApplicableType(vcsType)
53+
fun forType(
54+
vcsType: VcsType,
55+
versionControlSystemsConfiguration: Map<String, VersionControlSystemConfiguration> = emptyMap()
56+
) = ALL.values.filter { vcsFactory -> vcsFactory.type == vcsType.toString() }
57+
.map { vcsFactory ->
58+
// If there is a configuration for the VCS type, use it, otherwise create
59+
// the VCS with an empty configuration.
60+
versionControlSystemsConfiguration[vcsFactory.type]?.let { vcsConfig ->
61+
vcsFactory.create(options = vcsConfig.options, secrets = emptyMap())
62+
} ?: vcsFactory.create(options = emptyMap(), secrets = emptyMap())
6263
}
64+
.firstOrNull { vcs -> vcs.isAvailable() }
6365

6466
/**
6567
* A map to cache the [VersionControlSystem], if any, for previously queried URLs. This helps to speed up
@@ -72,8 +74,10 @@ abstract class VersionControlSystem(
7274
* Return the applicable VCS for the given [vcsUrl], or null if none is applicable.
7375
*/
7476
@Synchronized
75-
fun forUrl(vcsUrl: String) =
76-
// Do not use getOrPut() here as it cannot handle null values, also see
77+
fun forUrl(
78+
vcsUrl: String,
79+
versionControlSystemsConfiguration: Map<String, VersionControlSystemConfiguration> = emptyMap()
80+
) = // Do not use getOrPut() here as it cannot handle null values, also see
7781
// https://youtrack.jetbrains.com/issue/KT-21392.
7882
if (vcsUrl in urlToVcsMap) {
7983
urlToVcsMap[vcsUrl]
@@ -82,12 +86,18 @@ abstract class VersionControlSystem(
8286
when (val type = VcsHost.parseUrl(vcsUrl).type) {
8387
VcsType.UNKNOWN -> {
8488
// ...then eventually try to determine the type also dynamically.
85-
ALL.values.find {
86-
it.isAvailable() && it.isApplicableUrl(vcsUrl)
87-
}
89+
ALL.values
90+
.map { vcsFactory ->
91+
// If there is a configuration for the VCS type, use it, otherwise create
92+
// the VCS with an empty configuration.
93+
versionControlSystemsConfiguration[vcsFactory.type]
94+
?.let { vcsConfig ->
95+
vcsFactory.create(options = vcsConfig.options, secrets = emptyMap())
96+
} ?: vcsFactory.create(options = emptyMap(), secrets = emptyMap())
97+
}.firstOrNull { vcs -> vcs.isAvailable() && vcs.isApplicableUrl(vcsUrl) }
8898
}
8999

90-
else -> forType(type)
100+
else -> forType(type, versionControlSystemsConfiguration)
91101
}.also {
92102
urlToVcsMap[vcsUrl] = it
93103
}
@@ -109,28 +119,31 @@ abstract class VersionControlSystem(
109119
return if (absoluteVcsDirectory in dirToVcsMap) {
110120
dirToVcsMap[absoluteVcsDirectory]
111121
} else {
112-
ALL.values.asSequence().mapNotNull {
113-
if (it is CommandLineTool && !it.isInPath()) {
114-
null
115-
} else {
116-
it.getWorkingTree(absoluteVcsDirectory)
117-
}
118-
}.find {
119-
try {
120-
it.isValid()
121-
} catch (e: IOException) {
122-
e.showStackTrace()
123-
124-
logger.debug {
125-
"Exception while validating ${it.vcsType} working tree, treating it as non-applicable: " +
126-
e.collectMessages()
122+
ALL.values.asSequence()
123+
.map { vcsFactory -> vcsFactory.create(options = emptyMap(), secrets = emptyMap()) }
124+
.mapNotNull {
125+
if (it is CommandLineTool && !it.isInPath()) {
126+
null
127+
} else {
128+
it.getWorkingTree(absoluteVcsDirectory)
127129
}
128-
129-
false
130+
}.find {
131+
try {
132+
it.isValid()
133+
} catch (e: IOException) {
134+
e.showStackTrace()
135+
136+
logger.debug {
137+
"Exception while validating ${it.vcsType} working tree, " +
138+
"treating it as non-applicable: " +
139+
e.collectMessages()
140+
}
141+
142+
false
143+
}
144+
}.also {
145+
dirToVcsMap[absoluteVcsDirectory] = it
130146
}
131-
}.also {
132-
dirToVcsMap[absoluteVcsDirectory] = it
133-
}
134147
}
135148
}
136149

@@ -165,9 +178,9 @@ abstract class VersionControlSystem(
165178
}
166179

167180
/**
168-
* The priority in which this VCS should be probed. A higher value means a higher priority.
181+
* The type of CVS that is supported by this VCS plugin.
169182
*/
170-
protected open val priority: Int = 0
183+
abstract val type: String
171184

172185
/**
173186
* A list of symbolic names that point to the latest revision.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (C) 2024 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.ossreviewtoolkit.downloader
21+
22+
import org.ossreviewtoolkit.utils.common.Plugin
23+
import org.ossreviewtoolkit.utils.common.TypedConfigurablePluginFactory
24+
25+
/**
26+
* An abstract class to be implemented by factories for [version contral systems][VersionControlSystem].
27+
* The constructor parameter [type] denotes which VCS type is supported by this plugin.
28+
* The constructor parameter [priority] is used to determine the order in which the VCS plugins are used.
29+
*/
30+
abstract class VersionControlSystemFactory<CONFIG>(override val type: String, val priority: Int) :
31+
TypedConfigurablePluginFactory<CONFIG, VersionControlSystem> {
32+
companion object {
33+
/**
34+
* All [version control system factories][VersionControlSystemFactory] available in the classpath,
35+
* associated by their names, sorted by priority.
36+
*/
37+
val ALL by lazy {
38+
Plugin.getAll<VersionControlSystemFactory<*>>()
39+
.toList()
40+
.sortedByDescending { (_, vcsFactory) -> vcsFactory.priority }
41+
.toMap()
42+
}
43+
}
44+
}
45+
46+
/**
47+
* A base class for specific version control system configurations.
48+
*/
49+
open class VersionControlSystemConfiguration

downloader/src/test/kotlin/VersionControlSystemTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ class VersionControlSystemTest : WordSpec({
8787

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

90-
Git().getRevisionCandidates(workingTree, pkg, allowMovingRevisions = true) shouldBeSuccess listOf(
90+
Git.Factory().create(VersionControlSystemConfiguration())
91+
.getRevisionCandidates(workingTree, pkg, allowMovingRevisions = true) shouldBeSuccess listOf(
9192
"v1.6.0"
9293
)
9394
}
@@ -110,7 +111,8 @@ class VersionControlSystemTest : WordSpec({
110111
every { workingTree.listRemoteBranches() } returns listOf("main")
111112
every { workingTree.listRemoteTags() } returns emptyList()
112113

113-
Git().getRevisionCandidates(workingTree, pkg, allowMovingRevisions = true) shouldBeSuccess listOf(
114+
Git.Factory().create(VersionControlSystemConfiguration())
115+
.getRevisionCandidates(workingTree, pkg, allowMovingRevisions = true) shouldBeSuccess listOf(
114116
"master",
115117
"main"
116118
)

integrations/schemas/ort-configuration-schema.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,28 @@
8989
"items": {
9090
"$ref": "#/definitions/SourceCodeOrigins"
9191
}
92+
},
93+
"versionControlSystems": {
94+
"type": "object",
95+
"properties": {
96+
"Git": {
97+
"type": "object",
98+
"properties": {
99+
"options": {
100+
"type": "object",
101+
"properties": {
102+
"submoduleHistoryDepth": {
103+
"type": "integer",
104+
"minimum": 1
105+
},
106+
"updateNestedSubmodules": {
107+
"type": "boolean"
108+
}
109+
}
110+
}
111+
}
112+
}
113+
}
92114
}
93115
}
94116
},

model/src/main/kotlin/config/DownloaderConfiguration.kt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,35 @@ data class DownloaderConfiguration(
4444
* Configuration of the considered source code origins and their priority order. This must not be empty and not
4545
* contain any duplicates.
4646
*/
47-
val sourceCodeOrigins: List<SourceCodeOrigin> = listOf(SourceCodeOrigin.VCS, SourceCodeOrigin.ARTIFACT)
47+
val sourceCodeOrigins: List<SourceCodeOrigin> = listOf(SourceCodeOrigin.VCS, SourceCodeOrigin.ARTIFACT),
48+
49+
/**
50+
* Version control system specific configurations. The key needs to match VCS type,
51+
* e.g. "Git" for the Git version control system.
52+
*/
53+
val versionControlSystems: Map<String, VersionControlSystemConfiguration> = emptyMap()
4854
) {
55+
/**
56+
* A copy of [versionControlSystems] with case-insensitive keys.
57+
*/
58+
private val versionControlSystemsCaseInsensitive: Map<String, VersionControlSystemConfiguration> =
59+
versionControlSystems.toSortedMap(String.CASE_INSENSITIVE_ORDER)
60+
4961
init {
5062
sourceCodeOrigins.requireNotEmptyNoDuplicates()
63+
64+
val duplicateVersionControlSystems =
65+
versionControlSystems.keys - versionControlSystemsCaseInsensitive.keys.toSet()
66+
67+
require(duplicateVersionControlSystems.isEmpty()) {
68+
"The following version control systems have duplicate configuration: " +
69+
"${duplicateVersionControlSystems.joinToString()}."
70+
}
5171
}
72+
73+
/**
74+
* Get a [VersionControlSystemConfiguration] from [versionControlSystems].
75+
* The difference to accessing the map directly is that VCS type can be case-insensitive.
76+
*/
77+
fun getCaseInsensitiveVersionControlSystems() = versionControlSystemsCaseInsensitive
5278
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (C) 2024 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.ossreviewtoolkit.model.config
21+
22+
import com.fasterxml.jackson.annotation.JsonInclude
23+
24+
import org.ossreviewtoolkit.utils.common.Options
25+
26+
/**
27+
* The configuration for a Version Control System (VCS).
28+
*/
29+
@JsonInclude(JsonInclude.Include.NON_NULL)
30+
data class VersionControlSystemConfiguration(
31+
/**
32+
* Custom configuration options. See the documentation of the respective class for available options.
33+
*/
34+
val options: Options = emptyMap()
35+
)

model/src/main/resources/reference.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,20 @@ ort:
167167

168168
sourceCodeOrigins: [VCS, ARTIFACT]
169169

170+
# Optional VCS-specific configuration options that allow to add VCS-specific configurations to leverage certain
171+
# features directly in a VCS plugin. The effects are limited to usage through the command-line interface of the
172+
# Downloader tool, designed primarily for scripted operations. In other stages of the ORT pipeline these
173+
# configuration options have no effect.
174+
versionControlSystems:
175+
Git:
176+
options:
177+
# Depth of the commit history to fetch when updating submodules
178+
submoduleHistoryDepth: 10
179+
180+
# A flag to control whether nested submodules should be updated (true), or if only the submodules
181+
# on the first layer should be considered (false).
182+
updateNestedSubmodules: true
183+
170184
scanner:
171185
skipConcluded: true
172186
skipExcluded: true

0 commit comments

Comments
 (0)