Skip to content

Commit 212eb7b

Browse files
committedFeb 13, 2025
refactor(scanner)!: Extract command line tools
Refactor scanner plugins to not inherit from `CommandLineTool` and move the `CommandLineTool` implementations to separate objects. Also rename `CommandLinePathScannerWrapper` to `LocalPathScannerWrapper`. This provides better separation of concerns and helps with the upcoming migration of scanner plugins to the new plugin API, because with that the scanner plugins will not have empty constructors anymore which are required by the `RequirementsCommand` to list required command line tools. Signed-off-by: Martin Nonnenmacher <martin.nonnenmacher@bosch.com>
1 parent 5956a45 commit 212eb7b

File tree

8 files changed

+70
-63
lines changed

8 files changed

+70
-63
lines changed
 

‎plugins/commands/requirements/src/main/kotlin/RequirementsCommand.kt

+1-11
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ import org.ossreviewtoolkit.plugins.api.OrtPlugin
4242
import org.ossreviewtoolkit.plugins.api.PluginDescriptor
4343
import org.ossreviewtoolkit.plugins.commands.api.OrtCommand
4444
import org.ossreviewtoolkit.plugins.commands.api.OrtCommandFactory
45-
import org.ossreviewtoolkit.scanner.CommandLinePathScannerWrapper
46-
import org.ossreviewtoolkit.scanner.ScannerWrapperConfig
4745
import org.ossreviewtoolkit.utils.common.CommandLineTool
4846
import org.ossreviewtoolkit.utils.common.Plugin
4947
import org.ossreviewtoolkit.utils.common.enumSetOf
@@ -194,6 +192,7 @@ class RequirementsCommand(
194192

195193
when {
196194
it.isBundledPlugin("packagemanagers") -> category = "PackageManager"
195+
it.isBundledPlugin("scanners") -> category = "Scanner"
197196
it.isBundledPlugin("versioncontrolsystems") -> category = "VersionControlSystem"
198197
}
199198

@@ -216,15 +215,6 @@ class RequirementsCommand(
216215
)
217216
}
218217

219-
CommandLinePathScannerWrapper::class.java.isAssignableFrom(it) -> {
220-
category = "Scanner"
221-
logger.debug { "$it is a $category." }
222-
it.getDeclaredConstructor(
223-
String::class.java,
224-
ScannerWrapperConfig::class.java
225-
).newInstance("", ScannerWrapperConfig.EMPTY)
226-
}
227-
228218
VersionControlSystem::class.java.isAssignableFrom(it) -> {
229219
category = "VersionControlSystem"
230220
logger.debug { "$it is a $category." }

‎plugins/scanners/askalono/src/main/kotlin/Askalono.kt

+16-11
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,32 @@ import org.ossreviewtoolkit.model.LicenseFinding
3232
import org.ossreviewtoolkit.model.ScanSummary
3333
import org.ossreviewtoolkit.model.Severity
3434
import org.ossreviewtoolkit.model.TextLocation
35-
import org.ossreviewtoolkit.scanner.CommandLinePathScannerWrapper
35+
import org.ossreviewtoolkit.scanner.LocalPathScannerWrapper
3636
import org.ossreviewtoolkit.scanner.ScanContext
3737
import org.ossreviewtoolkit.scanner.ScanException
3838
import org.ossreviewtoolkit.scanner.ScannerMatcher
3939
import org.ossreviewtoolkit.scanner.ScannerWrapperConfig
4040
import org.ossreviewtoolkit.scanner.ScannerWrapperFactory
41+
import org.ossreviewtoolkit.utils.common.CommandLineTool
4142
import org.ossreviewtoolkit.utils.common.Options
4243
import org.ossreviewtoolkit.utils.common.Os
4344

4445
private const val CONFIDENCE_NOTICE = "Confidence threshold not high enough for any known license"
4546

4647
private val JSON = Json { ignoreUnknownKeys = true }
4748

49+
object AskalonoCommand : CommandLineTool {
50+
override fun command(workingDir: File?) =
51+
listOfNotNull(workingDir, if (Os.isWindows) "askalono.exe" else "askalono").joinToString(File.separator)
52+
53+
override fun transformVersion(output: String) =
54+
// The version string can be something like:
55+
// askalono 0.2.0-beta.1
56+
output.removePrefix("askalono ")
57+
}
58+
4859
class Askalono internal constructor(name: String, private val wrapperConfig: ScannerWrapperConfig) :
49-
CommandLinePathScannerWrapper(name) {
60+
LocalPathScannerWrapper(name) {
5061
class Factory : ScannerWrapperFactory<Unit>("Askalono") {
5162
override fun create(config: Unit, wrapperConfig: ScannerWrapperConfig) = Askalono(type, wrapperConfig)
5263

@@ -57,20 +68,14 @@ class Askalono internal constructor(name: String, private val wrapperConfig: Sca
5768

5869
override val matcher by lazy { ScannerMatcher.create(details, wrapperConfig.matcherConfig) }
5970

71+
override val version by lazy { AskalonoCommand.getVersion() }
72+
6073
override val readFromStorage by lazy { wrapperConfig.readFromStorageWithDefault(matcher) }
6174

6275
override val writeToStorage by lazy { wrapperConfig.writeToStorageWithDefault(matcher) }
6376

64-
override fun command(workingDir: File?) =
65-
listOfNotNull(workingDir, if (Os.isWindows) "askalono.exe" else "askalono").joinToString(File.separator)
66-
67-
override fun transformVersion(output: String) =
68-
// The version string can be something like:
69-
// askalono 0.2.0-beta.1
70-
output.removePrefix("askalono ")
71-
7277
override fun runScanner(path: File, context: ScanContext): String {
73-
val process = run(
78+
val process = AskalonoCommand.run(
7479
"--format", "json",
7580
"crawl", path.absolutePath
7681
)

‎plugins/scanners/boyterlc/src/main/kotlin/BoyterLc.kt

+16-11
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,32 @@ import org.ossreviewtoolkit.model.LicenseFinding
3131
import org.ossreviewtoolkit.model.ScanSummary
3232
import org.ossreviewtoolkit.model.Severity
3333
import org.ossreviewtoolkit.model.TextLocation
34-
import org.ossreviewtoolkit.scanner.CommandLinePathScannerWrapper
34+
import org.ossreviewtoolkit.scanner.LocalPathScannerWrapper
3535
import org.ossreviewtoolkit.scanner.ScanContext
3636
import org.ossreviewtoolkit.scanner.ScanException
3737
import org.ossreviewtoolkit.scanner.ScannerMatcher
3838
import org.ossreviewtoolkit.scanner.ScannerWrapperConfig
3939
import org.ossreviewtoolkit.scanner.ScannerWrapperFactory
40+
import org.ossreviewtoolkit.utils.common.CommandLineTool
4041
import org.ossreviewtoolkit.utils.common.Options
4142
import org.ossreviewtoolkit.utils.common.Os
4243
import org.ossreviewtoolkit.utils.common.safeDeleteRecursively
4344
import org.ossreviewtoolkit.utils.ort.createOrtTempDir
4445

4546
private val JSON = Json { ignoreUnknownKeys = true }
4647

48+
object BoyterLcCommand : CommandLineTool {
49+
override fun command(workingDir: File?) =
50+
listOfNotNull(workingDir, if (Os.isWindows) "lc.exe" else "lc").joinToString(File.separator)
51+
52+
override fun transformVersion(output: String) =
53+
// The version string can be something like:
54+
// licensechecker version 1.1.1
55+
output.removePrefix("licensechecker version ")
56+
}
57+
4758
class BoyterLc internal constructor(name: String, private val wrapperConfig: ScannerWrapperConfig) :
48-
CommandLinePathScannerWrapper(name) {
59+
LocalPathScannerWrapper(name) {
4960
companion object {
5061
val CONFIGURATION_OPTIONS = listOf(
5162
"--confidence", "0.95", // Cut-off value to only get most relevant matches.
@@ -63,21 +74,15 @@ class BoyterLc internal constructor(name: String, private val wrapperConfig: Sca
6374

6475
override val matcher by lazy { ScannerMatcher.create(details, wrapperConfig.matcherConfig) }
6576

77+
override val version by lazy { BoyterLcCommand.getVersion() }
78+
6679
override val readFromStorage by lazy { wrapperConfig.readFromStorageWithDefault(matcher) }
6780

6881
override val writeToStorage by lazy { wrapperConfig.writeToStorageWithDefault(matcher) }
6982

70-
override fun command(workingDir: File?) =
71-
listOfNotNull(workingDir, if (Os.isWindows) "lc.exe" else "lc").joinToString(File.separator)
72-
73-
override fun transformVersion(output: String) =
74-
// The version string can be something like:
75-
// licensechecker version 1.1.1
76-
output.removePrefix("licensechecker version ")
77-
7883
override fun runScanner(path: File, context: ScanContext): String {
7984
val resultFile = createOrtTempDir().resolve("result.json")
80-
val process = run(
85+
val process = BoyterLcCommand.run(
8186
*CONFIGURATION_OPTIONS.toTypedArray(),
8287
"--output", resultFile.absolutePath,
8388
path.absolutePath

‎plugins/scanners/licensee/src/main/kotlin/Licensee.kt

+13-8
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ import org.ossreviewtoolkit.model.ScanSummary
3838
import org.ossreviewtoolkit.model.ScannerDetails
3939
import org.ossreviewtoolkit.model.Severity
4040
import org.ossreviewtoolkit.model.TextLocation
41-
import org.ossreviewtoolkit.scanner.CommandLinePathScannerWrapper
41+
import org.ossreviewtoolkit.scanner.LocalPathScannerWrapper
4242
import org.ossreviewtoolkit.scanner.ScanContext
4343
import org.ossreviewtoolkit.scanner.ScanException
4444
import org.ossreviewtoolkit.scanner.ScannerMatcher
4545
import org.ossreviewtoolkit.scanner.ScannerWrapperConfig
4646
import org.ossreviewtoolkit.scanner.ScannerWrapperFactory
47+
import org.ossreviewtoolkit.utils.common.CommandLineTool
4748
import org.ossreviewtoolkit.utils.common.Options
4849
import org.ossreviewtoolkit.utils.common.Os
4950

@@ -52,8 +53,15 @@ private val JSON = Json {
5253
namingStrategy = JsonNamingStrategy.SnakeCase
5354
}
5455

56+
object LicenseeCommand : CommandLineTool {
57+
override fun command(workingDir: File?) =
58+
listOfNotNull(workingDir, if (Os.isWindows) "licensee.bat" else "licensee").joinToString(File.separator)
59+
60+
override fun getVersionArguments() = "version"
61+
}
62+
5563
class Licensee internal constructor(name: String, private val wrapperConfig: ScannerWrapperConfig) :
56-
CommandLinePathScannerWrapper(name) {
64+
LocalPathScannerWrapper(name) {
5765
companion object {
5866
val CONFIGURATION_OPTIONS = listOf("--json")
5967
}
@@ -68,17 +76,14 @@ class Licensee internal constructor(name: String, private val wrapperConfig: Sca
6876

6977
override val matcher by lazy { ScannerMatcher.create(details, wrapperConfig.matcherConfig) }
7078

79+
override val version by lazy { LicenseeCommand.getVersion() }
80+
7181
override val readFromStorage by lazy { wrapperConfig.readFromStorageWithDefault(matcher) }
7282

7383
override val writeToStorage by lazy { wrapperConfig.writeToStorageWithDefault(matcher) }
7484

75-
override fun command(workingDir: File?) =
76-
listOfNotNull(workingDir, if (Os.isWindows) "licensee.bat" else "licensee").joinToString(File.separator)
77-
78-
override fun getVersionArguments() = "version"
79-
8085
override fun runScanner(path: File, context: ScanContext): String {
81-
val process = run(
86+
val process = LicenseeCommand.run(
8287
"detect",
8388
*CONFIGURATION_OPTIONS.toTypedArray(),
8489
path.absolutePath

‎plugins/scanners/scancode/src/main/kotlin/ScanCode.kt

+18-13
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ import org.ossreviewtoolkit.model.ScanSummary
2828
import org.ossreviewtoolkit.model.ScannerDetails
2929
import org.ossreviewtoolkit.model.config.PluginConfiguration
3030
import org.ossreviewtoolkit.model.config.ScannerConfiguration
31-
import org.ossreviewtoolkit.scanner.CommandLinePathScannerWrapper
31+
import org.ossreviewtoolkit.scanner.LocalPathScannerWrapper
3232
import org.ossreviewtoolkit.scanner.ScanContext
3333
import org.ossreviewtoolkit.scanner.ScanStorage
3434
import org.ossreviewtoolkit.scanner.ScannerMatcher
3535
import org.ossreviewtoolkit.scanner.ScannerWrapperConfig
3636
import org.ossreviewtoolkit.scanner.ScannerWrapperFactory
37+
import org.ossreviewtoolkit.utils.common.CommandLineTool
3738
import org.ossreviewtoolkit.utils.common.Options
3839
import org.ossreviewtoolkit.utils.common.Os
3940
import org.ossreviewtoolkit.utils.common.ProcessCapture
@@ -45,6 +46,18 @@ import org.semver4j.RangesList
4546
import org.semver4j.RangesListFactory
4647
import org.semver4j.Semver
4748

49+
object ScanCodeCommand : CommandLineTool {
50+
override fun command(workingDir: File?) =
51+
listOfNotNull(workingDir, if (Os.isWindows) "scancode.bat" else "scancode").joinToString(File.separator)
52+
53+
override fun getVersionRequirement(): RangesList = RangesListFactory.create(">=30.0.0")
54+
55+
override fun transformVersion(output: String): String =
56+
output.lineSequence().firstNotNullOfOrNull { line ->
57+
line.withoutPrefix("ScanCode version")?.removePrefix(":")?.trim()
58+
}.orEmpty()
59+
}
60+
4861
/**
4962
* A wrapper for [ScanCode](https://github.com/aboutcode-org/scancode-toolkit).
5063
*
@@ -68,7 +81,7 @@ class ScanCode internal constructor(
6881
name: String,
6982
private val config: ScanCodeConfig,
7083
private val wrapperConfig: ScannerWrapperConfig
71-
) : CommandLinePathScannerWrapper(name) {
84+
) : LocalPathScannerWrapper(name) {
7285
// This constructor is required by the `RequirementsCommand`.
7386
constructor(name: String, wrapperConfig: ScannerWrapperConfig) : this(name, ScanCodeConfig.DEFAULT, wrapperConfig)
7487

@@ -111,20 +124,12 @@ class ScanCode internal constructor(
111124

112125
override val matcher by lazy { ScannerMatcher.create(details, wrapperConfig.matcherConfig) }
113126

127+
override val version by lazy { ScanCodeCommand.getVersion() }
128+
114129
override val readFromStorage by lazy { wrapperConfig.readFromStorageWithDefault(matcher) }
115130

116131
override val writeToStorage by lazy { wrapperConfig.writeToStorageWithDefault(matcher) }
117132

118-
override fun command(workingDir: File?) =
119-
listOfNotNull(workingDir, if (Os.isWindows) "scancode.bat" else "scancode").joinToString(File.separator)
120-
121-
override fun getVersionRequirement(): RangesList = RangesListFactory.create(">=30.0.0")
122-
123-
override fun transformVersion(output: String): String =
124-
output.lineSequence().firstNotNullOfOrNull { line ->
125-
line.withoutPrefix("ScanCode version")?.removePrefix(":")?.trim()
126-
}.orEmpty()
127-
128133
override fun runScanner(path: File, context: ScanContext): String {
129134
val resultFile = createOrtTempDir().resolve("result.json")
130135
val process = runScanCode(path, resultFile)
@@ -161,7 +166,7 @@ class ScanCode internal constructor(
161166
*/
162167
internal fun runScanCode(path: File, resultFile: File) =
163168
ProcessCapture(
164-
command(),
169+
ScanCodeCommand.command(),
165170
*commandLineOptions.toTypedArray(),
166171
// The output format option needs to directly precede the result file path.
167172
OUTPUT_FORMAT_OPTION, resultFile.absolutePath,

‎plugins/scanners/scancode/src/test/kotlin/ScanCodeTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class ScanCodeTest : WordSpec({
126126

127127
"transformVersion()" should {
128128
"work with a version output without a colon" {
129-
scanner.transformVersion(
129+
ScanCodeCommand.transformVersion(
130130
"""
131131
ScanCode version 30.0.1
132132
ScanCode Output Format version 1.0.0
@@ -136,7 +136,7 @@ class ScanCodeTest : WordSpec({
136136
}
137137

138138
"work with a version output with a colon" {
139-
scanner.transformVersion(
139+
ScanCodeCommand.transformVersion(
140140
"""
141141
ScanCode version: 31.0.0b4
142142
ScanCode Output Format version: 2.0.0

‎scanner/src/main/kotlin/CommandLinePathScannerWrapper.kt ‎scanner/src/main/kotlin/LocalPathScannerWrapper.kt

+2-5
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,11 @@ import java.time.Instant
2424

2525
import org.ossreviewtoolkit.model.ScanSummary
2626
import org.ossreviewtoolkit.model.ScannerDetails
27-
import org.ossreviewtoolkit.utils.common.CommandLineTool
2827

2928
/**
30-
* A [PathScannerWrapper] that is executed as a [CommandLineTool] on the local machine.
29+
* A [PathScannerWrapper] that is executed on the local machine.
3130
*/
32-
abstract class CommandLinePathScannerWrapper(override val name: String) : PathScannerWrapper, CommandLineTool {
33-
override val version by lazy { getVersion() }
34-
31+
abstract class LocalPathScannerWrapper(override val name: String) : PathScannerWrapper {
3532
final override fun scanPath(path: File, context: ScanContext): ScanSummary {
3633
val startTime = Instant.now()
3734
val result = runScanner(path, context)

‎scanner/src/main/kotlin/storages/ClearlyDefinedStorage.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import org.ossreviewtoolkit.model.config.ClearlyDefinedStorageConfiguration
4747
import org.ossreviewtoolkit.model.jsonMapper
4848
import org.ossreviewtoolkit.model.utils.toClearlyDefinedCoordinates
4949
import org.ossreviewtoolkit.model.utils.toClearlyDefinedSourceLocation
50-
import org.ossreviewtoolkit.scanner.CommandLinePathScannerWrapper
50+
import org.ossreviewtoolkit.scanner.LocalPathScannerWrapper
5151
import org.ossreviewtoolkit.scanner.ScanStorageException
5252
import org.ossreviewtoolkit.scanner.ScannerMatcher
5353
import org.ossreviewtoolkit.scanner.ScannerWrapperFactory
@@ -110,7 +110,7 @@ class ClearlyDefinedStorage(
110110
// For the ClearlyDefined tool names see https://github.com/clearlydefined/service#tool-name-registry.
111111
ScannerWrapperFactory.ALL[name]?.let { factory ->
112112
val scanner = factory.create(emptyMap(), emptyMap())
113-
(scanner as? CommandLinePathScannerWrapper)?.let { cliScanner -> cliScanner to versions.last() }
113+
(scanner as? LocalPathScannerWrapper)?.let { cliScanner -> cliScanner to versions.last() }
114114
}.also { factory ->
115115
factory ?: logger.debug { "Unsupported tool '$name' for coordinates '$coordinates'." }
116116
}

0 commit comments

Comments
 (0)
Please sign in to comment.