Skip to content

Commit 2a75433

Browse files
scanner: Add Nomos plugin
Signed-off-by: Prakash Mishra <[email protected]>
1 parent 2384720 commit 2a75433

File tree

5 files changed

+374
-0
lines changed

5 files changed

+374
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (C) 2025 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+
plugins {
21+
22+
id("ort-plugin-conventions")
23+
24+
}
25+
26+
dependencies {
27+
api(projects.model)
28+
api(projects.scanner)
29+
30+
implementation(projects.utils.commonUtils)
31+
32+
implementation(projects.utils.ortUtils)
33+
implementation(projects.utils.spdxUtils)
34+
35+
ksp(projects.scanner)
36+
37+
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0")
38+
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.0")
39+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright (C) 2025 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.plugins.scanners.nomos
21+
22+
import java.io.File
23+
import java.time.Instant
24+
25+
import org.apache.logging.log4j.kotlin.logger
26+
27+
import org.ossreviewtoolkit.model.ScanSummary
28+
import org.ossreviewtoolkit.model.ScannerDetails
29+
import org.ossreviewtoolkit.plugins.api.OrtPlugin
30+
import org.ossreviewtoolkit.plugins.api.PluginDescriptor
31+
import org.ossreviewtoolkit.scanner.LocalPathScannerWrapper
32+
import org.ossreviewtoolkit.scanner.ScanContext
33+
import org.ossreviewtoolkit.scanner.ScannerMatcher
34+
import org.ossreviewtoolkit.scanner.ScannerWrapperFactory
35+
import org.ossreviewtoolkit.utils.common.CommandLineTool
36+
import org.ossreviewtoolkit.utils.common.ProcessCapture
37+
import org.ossreviewtoolkit.utils.ort.createOrtTempDir
38+
import org.ossreviewtoolkit.utils.common.safeDeleteRecursively
39+
40+
object NomossaCommand : CommandLineTool {
41+
override fun command(workingDir: File?): String {
42+
return listOfNotNull(workingDir, "nomossa").joinToString(File.separator)
43+
}
44+
45+
override fun transformVersion(output: String): String {
46+
// Example output: nomossasa build version: 4.5.1.1 r(ff4fa7)
47+
val versionRegex = Regex("""(\d+\.\d+\.\d+)(?:\.\d+)?""")
48+
return versionRegex.find(output)?.groupValues?.get(1).orEmpty() // Returns 4.5.1
49+
}
50+
51+
52+
override fun getVersionArguments() = "-V"
53+
54+
}
55+
56+
/**
57+
* A wrapper for [Nomossa](https://github.com/fossology/fossology).
58+
*
59+
* This plugin integrates FOSSology's Nomossa scanner into ORT by calling its CLI
60+
* and mapping its output to ORT's scan result format.
61+
*/
62+
@OrtPlugin(
63+
displayName = "Nomossa",
64+
description = "A wrapper for [Nomossa](https://github.com/fossology/fossology).",
65+
factory = ScannerWrapperFactory::class
66+
)
67+
class Nomossa(
68+
override val descriptor: PluginDescriptor = NomossaFactory.descriptor,
69+
private val config: NomossaConfig
70+
) : LocalPathScannerWrapper() {
71+
72+
companion object {
73+
private const val OUTPUT_FORMAT_OPTION = "--output-format=json" // Nomossa output format
74+
}
75+
76+
private val commandLineOptions by lazy { getCommandLineOptions() }
77+
78+
internal fun getCommandLineOptions(): List<String> {
79+
val options = LinkedHashSet(config.additionalOptions)
80+
options.add("-J")
81+
options.add("-l")
82+
options.add("-n")
83+
options.add(config.cpuCount.toString())
84+
return options.toList()
85+
}
86+
87+
88+
override val configuration by lazy {
89+
90+
config.additionalOptions.joinToString(" ")
91+
}
92+
93+
override val matcher by lazy { ScannerMatcher.create(details, config) }
94+
95+
override val version by lazy {
96+
require(NomossaCommand.isInPath()) {
97+
"The '${NomossaCommand.command()}' command is not available in the PATH environment."
98+
}
99+
100+
NomossaCommand.getVersion()
101+
}
102+
103+
override val readFromStorage = config.readFromStorage
104+
override val writeToStorage = config.writeToStorage
105+
106+
override fun runScanner(path: File, context: ScanContext): String {
107+
108+
val process = runNomossa(path)
109+
110+
val resultText = process.stdout
111+
logger.info { "Nomossa raw output:\n$resultText" }
112+
113+
114+
return with(process) {
115+
if (isError && stdout.isNotBlank()) logger.debug { stdout }
116+
if (stderr.isNotBlank()) logger.debug { stderr }
117+
118+
stdout
119+
}
120+
}
121+
122+
override fun parseDetails(result: String): ScannerDetails {
123+
return ScannerDetails(
124+
name = descriptor.id,
125+
version = version,
126+
configuration = config.additionalOptions.joinToString(" ")
127+
)
128+
}
129+
130+
override fun createSummary(result: String, startTime: Instant, endTime: Instant): ScanSummary =
131+
parseNomossaResult(result).toScanSummary(startTime, endTime)
132+
133+
/**
134+
* Execute Nomossa with the configured arguments to scan the given [path] and produce [resultFile].
135+
*/
136+
internal fun runNomossa(path: File): ProcessCapture =
137+
ProcessCapture(
138+
NomossaCommand.command(),
139+
*commandLineOptions.toTypedArray(),
140+
"-d", path.absolutePath // Scan this directory
141+
)
142+
143+
}
144+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (C) 2025 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.plugins.scanners.nomos
21+
22+
import org.ossreviewtoolkit.plugins.api.OrtPluginOption
23+
import org.ossreviewtoolkit.scanner.ScannerMatcherCriteria
24+
import org.ossreviewtoolkit.scanner.ScanStorage
25+
import org.ossreviewtoolkit.model.ScannerDetails
26+
27+
28+
/**
29+
* Configuration options for the Nomossa scanner.
30+
*/
31+
data class NomossaConfig(
32+
/**
33+
* Command line options that affect scan results. These are used when matching stored results.
34+
*/
35+
@OrtPluginOption(defaultValue = "-J,-l")
36+
val additionalOptions: List<String>,
37+
38+
/**
39+
* The scanner name pattern used when looking up scan results from storage.
40+
*/
41+
override val regScannerName: String?,
42+
43+
/**
44+
* The minimum version of scan results to use from storage.
45+
*/
46+
override val minVersion: String?,
47+
48+
/**
49+
* The maximum version of scan results to use from storage.
50+
*/
51+
override val maxVersion: String?,
52+
53+
/**
54+
* The configuration string for identifying matching scan results in storage.
55+
*/
56+
override val configuration: String?,
57+
58+
/**
59+
* Whether to read scan results from storage.
60+
*/
61+
@OrtPluginOption(defaultValue = "false")
62+
val readFromStorage: Boolean,
63+
64+
/**
65+
* Whether to write scan results to storage.
66+
*/
67+
@OrtPluginOption(defaultValue = "false")
68+
val writeToStorage: Boolean
69+
) : ScannerMatcherCriteria{
70+
71+
/**
72+
* Number of CPU processes Nomossa should use.
73+
*/
74+
@OrtPluginOption(defaultValue = "2")
75+
val cpuCount: Int = 2
76+
77+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (C) 2025 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.plugins.scanners.nomos
21+
22+
import java.time.Instant
23+
import org.ossreviewtoolkit.model.LicenseFinding
24+
import org.ossreviewtoolkit.model.ScanSummary
25+
import org.ossreviewtoolkit.model.TextLocation
26+
27+
import org.apache.logging.log4j.kotlin.logger
28+
import org.ossreviewtoolkit.utils.spdx.*
29+
30+
import org.ossreviewtoolkit.model.Issue
31+
32+
33+
fun NomossaResult.toScanSummary(startTime: Instant, endTime: Instant): ScanSummary {
34+
val licenseFindings = results.flatMap { fileResult ->
35+
fileResult.licenses.map { rawLicense ->
36+
37+
val safeLicense = if (rawLicense.matches(Regex("^[A-Za-z0-9.\\-+]+$"))) {
38+
rawLicense
39+
} else {
40+
"LicenseRef-Nomossa-${rawLicense.replace(Regex("[^A-Za-z0-9.+-]"), "-")}" //License not found
41+
}
42+
43+
LicenseFinding(
44+
license = safeLicense, // use raw string
45+
location = TextLocation(
46+
path = fileResult.file,
47+
startLine = 1,
48+
endLine = 1
49+
)
50+
)
51+
}
52+
}.toSortedSet(
53+
compareBy(
54+
{ it.license.toString() },
55+
{ it.location.path },
56+
{ it.location.startLine }
57+
)
58+
)
59+
60+
return ScanSummary(
61+
startTime = startTime,
62+
endTime = endTime,
63+
licenseFindings = licenseFindings,
64+
issues = emptyList(), //no SPDX parsing issues anymore
65+
copyrightFindings = sortedSetOf()
66+
)
67+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (C) 2025 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.plugins.scanners.nomos
21+
22+
import com.fasterxml.jackson.databind.ObjectMapper
23+
import com.fasterxml.jackson.databind.SerializationFeature
24+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
25+
import com.fasterxml.jackson.module.kotlin.readValue
26+
import com.fasterxml.jackson.databind.DeserializationFeature
27+
28+
data class NomossaResult(
29+
val results: List<NomossaFileResult>
30+
)
31+
32+
data class NomossaFileResult(
33+
val file: String,
34+
val licenses: List<String>
35+
)
36+
37+
private val nomossaMapper: ObjectMapper = jacksonObjectMapper().apply {
38+
findAndRegisterModules()
39+
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
40+
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
41+
}
42+
43+
/**
44+
* Parses the JSON result string returned by Nomossa into a [NomossaResult] object.
45+
*/
46+
fun parseNomossaResult(result: String): NomossaResult =
47+
nomossaMapper.readValue(result)

0 commit comments

Comments
 (0)