Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate reporter to new plugin API #9182

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cli/src/funTest/kotlin/ExamplesFunTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ import org.ossreviewtoolkit.model.config.SendMailConfiguration
import org.ossreviewtoolkit.model.licenses.LicenseClassifications
import org.ossreviewtoolkit.model.readValue
import org.ossreviewtoolkit.notifier.Notifier
import org.ossreviewtoolkit.plugins.api.PluginConfig
import org.ossreviewtoolkit.reporter.HowToFixTextProvider
import org.ossreviewtoolkit.reporter.Reporter
import org.ossreviewtoolkit.reporter.ReporterFactory
import org.ossreviewtoolkit.reporter.ReporterInput
import org.ossreviewtoolkit.utils.ort.ORT_PACKAGE_CURATIONS_FILENAME
import org.ossreviewtoolkit.utils.ort.ORT_REPO_CONFIG_FILENAME
Expand Down Expand Up @@ -125,12 +126,12 @@ class ExamplesFunTest : StringSpec({
}

"The Asciidoctor PDF theme file is a valid" {
val reporter = Reporter.ALL.getValue("PdfTemplate")
val reporter = ReporterFactory.ALL.getValue("PdfTemplate")
val outputDir = tempdir()

takeExampleFile("asciidoctor-pdf-theme.yml")

val report = reporter.generateReport(
val report = reporter.create(PluginConfig()).generateReport(
ReporterInput(OrtResult.EMPTY),
outputDir,
PluginConfiguration(
Expand Down
2 changes: 1 addition & 1 deletion integrations/completions/ort-completion.fish
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ complete -c ort -f -n __fish_use_subcommand -a report -d 'Present Analyzer, Scan
## Options for report
complete -c ort -n "__fish_seen_subcommand_from report" -l ort-file -s i -r -F -d 'The ORT result file to use.'
complete -c ort -n "__fish_seen_subcommand_from report" -l output-dir -s o -r -F -d 'The output directory to store the generated reports in.'
complete -c ort -n "__fish_seen_subcommand_from report" -l report-formats -s f -r -d 'A comma-separated list of report formats to generate, any of [AOSD2, CtrlXAutomation, CycloneDx, DocBookTemplate, EvaluatedModel, FossId, FossIdSnippet, GitLabLicenseModel, HtmlTemplate, ManPageTemplate, Opossum, PdfTemplate, PlainTextTemplate, SpdxDocument, StaticHtml, TrustSource, WebApp].'
complete -c ort -n "__fish_seen_subcommand_from report" -l report-formats -s f -r -d 'A comma-separated list of report formats to generate, any of [AOSD2, CtrlXAutomation, CycloneDX, DocBookTemplate, EvaluatedModel, FossID, FossIdSnippet, GitLabLicenseModel, HtmlTemplate, ManPageTemplate, Opossum, PdfTemplate, PlainTextTemplate, SpdxDocument, StaticHTML, TrustSource, WebApp].'
complete -c ort -n "__fish_seen_subcommand_from report" -l copyright-garbage-file -r -F -d 'A file containing copyright statements which are marked as garbage. This can make the output inconsistent with the evaluator output but is useful when testing copyright garbage.'
complete -c ort -n "__fish_seen_subcommand_from report" -l custom-license-texts-dir -r -F -d 'A directory which maps custom license IDs to license texts. It should contain one text file per license with the license ID as the filename. A custom license text is used only if its ID has a \'LicenseRef-\' prefix and if the respective license text is not known by ORT.'
complete -c ort -n "__fish_seen_subcommand_from report" -l how-to-fix-text-provider-script -r -F -d 'The path to a Kotlin script which returns an instance of a \'HowToFixTextProvider\'. That provider injects how-to-fix texts in Markdown format for ORT issues.'
Expand Down
14 changes: 12 additions & 2 deletions plugins/api/src/main/kotlin/OrtPluginOption.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,15 @@ annotation class OrtPluginOption(
/**
* The default value of the option.
*/
val defaultValue: String
)
val defaultValue: String = NO_DEFAULT_VALUE,

/**
* A list of alternative names for the option. This can be used to make renaming options backward-compatible.
* Aliases are tried in the order they are defined until a value is found.
*/
val aliases: Array<String> = []
) {
companion object {
const val NO_DEFAULT_VALUE = "[NO_DEFAULT_VALUE]"
}
}
5 changes: 5 additions & 0 deletions plugins/api/src/main/kotlin/PluginDescriptor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ data class PluginOption(
*/
val defaultValue: String?,

/**
* A list of alternative names for the option.
*/
val aliases: List<String>,

/**
* Whether the option is required.
*/
Expand Down
21 changes: 12 additions & 9 deletions plugins/commands/reporter/src/main/kotlin/ReporterCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import org.ossreviewtoolkit.model.licenses.orEmpty
import org.ossreviewtoolkit.model.readValue
import org.ossreviewtoolkit.model.readValueOrDefault
import org.ossreviewtoolkit.model.utils.DefaultResolutionProvider
import org.ossreviewtoolkit.plugins.api.PluginConfig
import org.ossreviewtoolkit.plugins.commands.api.OrtCommand
import org.ossreviewtoolkit.plugins.commands.api.utils.configurationGroup
import org.ossreviewtoolkit.plugins.commands.api.utils.inputGroup
Expand All @@ -63,7 +64,7 @@ import org.ossreviewtoolkit.plugins.packageconfigurationproviders.api.SimplePack
import org.ossreviewtoolkit.plugins.packageconfigurationproviders.dir.DirPackageConfigurationProvider
import org.ossreviewtoolkit.reporter.DefaultLicenseTextProvider
import org.ossreviewtoolkit.reporter.HowToFixTextProvider
import org.ossreviewtoolkit.reporter.Reporter
import org.ossreviewtoolkit.reporter.ReporterFactory
import org.ossreviewtoolkit.reporter.ReporterInput
import org.ossreviewtoolkit.utils.common.collectMessages
import org.ossreviewtoolkit.utils.common.expandTilde
Expand Down Expand Up @@ -103,9 +104,10 @@ class ReporterCommand : OrtCommand(

private val reportFormats by option(
"--report-formats", "-f",
help = "A comma-separated list of report formats to generate, any of ${Reporter.ALL.keys}."
help = "A comma-separated list of report formats to generate, any of ${ReporterFactory.ALL.keys}."
).convert { name ->
Reporter.ALL[name] ?: throw BadParameterValue("Report formats must be one or more of ${Reporter.ALL.keys}.")
ReporterFactory.ALL[name]
?: throw BadParameterValue("Report formats must be one or more of ${ReporterFactory.ALL.keys}.")
}.split(",").required().outputGroup()

private val copyrightGarbageFile by option(
Expand Down Expand Up @@ -191,8 +193,8 @@ class ReporterCommand : OrtCommand(
"format, and the value is an arbitrary key-value pair. For example: " +
"-O PlainTextTemplate=template.id=NOTICE_SUMMARY"
).splitPair().convert { (format, option) ->
require(format in Reporter.ALL.keys) {
"Report formats must be one or more of ${Reporter.ALL.keys}."
require(format in ReporterFactory.ALL.keys) {
"Report formats must be one or more of ${ReporterFactory.ALL.keys}."
}

format to Pair(option.substringBefore("="), option.substringAfter("=", ""))
Expand Down Expand Up @@ -288,11 +290,12 @@ class ReporterCommand : OrtCommand(
reportFormats.map { reporter ->
async {
val threadName = Thread.currentThread().name
echo("Generating the '${reporter.type}' report in thread '$threadName'...")
echo("Generating the '${reporter.descriptor.id}' report in thread '$threadName'...")

reporter to measureTimedValue {
val options = reportConfigMap[reporter.type] ?: PluginConfiguration.EMPTY
reporter.generateReport(input, outputDir, options)
val options = reportConfigMap[reporter.descriptor.id] ?: PluginConfiguration.EMPTY
reporter.create(PluginConfig(options.options, options.secrets))
.generateReport(input, outputDir, options)
}
}
}.awaitAll()
Expand All @@ -302,7 +305,7 @@ class ReporterCommand : OrtCommand(
var failureCount = 0

reportDurationMap.value.forEach { (reporter, timedValue) ->
val name = reporter.type
val name = reporter.descriptor.id
val fileResults = timedValue.value

fileResults.forEach { fileResult ->
Expand Down
6 changes: 6 additions & 0 deletions plugins/compiler/src/main/kotlin/JsonSpecGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.google.devtools.ksp.processing.Dependencies

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.addAll
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.encodeToStream
Expand Down Expand Up @@ -51,6 +52,11 @@ class JsonSpecGenerator(private val codeGenerator: CodeGenerator) {
put("type", it.type.name)
put("description", it.description)
put("default", it.defaultValue)

putJsonArray("aliases") {
addAll(it.aliases)
}

put("isRequired", it.isRequired)
}
}
Expand Down
58 changes: 43 additions & 15 deletions plugins/compiler/src/main/kotlin/PluginFactoryGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,26 @@ class PluginFactoryGenerator(private val codeGenerator: CodeGenerator) {
pluginOptions.forEach { option ->
add(" ${option.name} = ")

fun readOption(name: String) =
when (option.type) {
PluginOptionType.BOOLEAN -> add("config.options[%S]?.toBooleanStrict()", name)
PluginOptionType.INTEGER -> add("config.options[%S]?.toInt()", name)
PluginOptionType.LONG -> add("config.options[%S]?.toLong()", name)
PluginOptionType.SECRET -> add("config.secrets[%S]?.let { %T(it) }", name, Secret::class)
PluginOptionType.STRING -> add("config.options[%S]", name)
PluginOptionType.STRING_LIST -> add(
"config.options[%S]?.split(\",\")?.map { it.trim() }",
name
)
}

// Add code to read the option from the options or secrets maps based on its type.
when (option.type) {
PluginOptionType.BOOLEAN -> add("config.options[%S]?.toBooleanStrict()", option.name)
PluginOptionType.INTEGER -> add("config.options[%S]?.toInt()", option.name)
PluginOptionType.LONG -> add("config.options[%S]?.toLong()", option.name)
PluginOptionType.SECRET -> add("config.secrets[%S]?.let { %T(it) }", option.name, Secret::class)
PluginOptionType.STRING -> add("config.options[%S]", option.name)
PluginOptionType.STRING_LIST -> add(
"config.options[%S]?.split(\",\")?.map { it.trim() }",
option.name
)
readOption(option.name)

// Add code to handle aliases.
option.aliases.forEach { alias ->
add(" ?: ")
readOption(alias)
}

// Add the default value if present.
Expand All @@ -130,7 +139,15 @@ class PluginFactoryGenerator(private val codeGenerator: CodeGenerator) {
PluginOptionType.LONG -> add(" ?: %LL", defaultValue.toLong())
PluginOptionType.SECRET -> add(" ?: %T(%S)", Secret::class, defaultValue)
PluginOptionType.STRING -> add(" ?: %S", defaultValue)
PluginOptionType.STRING_LIST -> add(" ?: %S", defaultValue)
PluginOptionType.STRING_LIST -> {
add(" ?: listOf(")

defaultValue.split(",").forEach { value ->
add("%S,", value.trim())
}

add(")")
}
}
}

Expand Down Expand Up @@ -175,16 +192,27 @@ class PluginFactoryGenerator(private val codeGenerator: CodeGenerator) {
| description = %S,
| type = %T.%L,
| defaultValue = %S,
| isRequired = %L
| ),
|
| aliases = listOf(
""".trimMargin(),
PluginOption::class,
it.name,
it.description,
PluginOptionType::class,
it.type.name,
it.defaultValue,
it.defaultValue
)

it.aliases.forEach { alias ->
add(" %S,", alias)
}

add(
"""
| ),
| isRequired = %L
| ),
|
""".trimMargin(),
it.isRequired
)
}
Expand Down
3 changes: 2 additions & 1 deletion plugins/compiler/src/main/kotlin/PluginSpecFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,14 @@ class PluginSpecFactory {
)
}

val defaultValue = annotation?.defaultValue
val defaultValue = annotation?.defaultValue?.takeIf { it != OrtPluginOption.NO_DEFAULT_VALUE }

PluginOption(
name = param.name?.asString().orEmpty(),
description = prop.docString?.trim().orEmpty(),
type = type,
defaultValue = defaultValue,
aliases = annotation?.aliases?.asList().orEmpty(),
isRequired = !paramType.isMarkedNullable && defaultValue == null
)
}
Expand Down
4 changes: 3 additions & 1 deletion plugins/reporters/aosd/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 @@ -29,6 +29,8 @@ dependencies {
api(projects.model)
api(projects.reporter)

ksp(projects.reporter)

implementation(projects.utils.spdxUtils)

implementation(libs.kotlinx.serialization.core)
Expand Down
13 changes: 10 additions & 3 deletions plugins/reporters/aosd/src/main/kotlin/Aosd2Reporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,21 @@ import org.ossreviewtoolkit.model.RemoteArtifact
import org.ossreviewtoolkit.model.RepositoryProvenance
import org.ossreviewtoolkit.model.config.PluginConfiguration
import org.ossreviewtoolkit.model.licenses.LicenseView
import org.ossreviewtoolkit.plugins.api.OrtPlugin
import org.ossreviewtoolkit.plugins.api.PluginDescriptor
import org.ossreviewtoolkit.plugins.reporters.aosd.AOSD2.ExternalDependency
import org.ossreviewtoolkit.reporter.Reporter
import org.ossreviewtoolkit.reporter.ReporterFactory
import org.ossreviewtoolkit.reporter.ReporterInput
import org.ossreviewtoolkit.utils.spdx.SpdxLicense

class Aosd2Reporter : Reporter {
override val type = "AOSD2"

@OrtPlugin(
id = "AOSD2",
displayName = "Audi Open Source Diagnostics 2 Reporter",
description = "A reporter for the Audi Open Source Diagnostics 2 (AOSD2) format.",
factory = ReporterFactory::class
)
class Aosd2Reporter(override val descriptor: PluginDescriptor = Aosd2ReporterFactory.descriptor) : Reporter {
override fun generateReport(
input: ReporterInput,
outputDir: File,
Expand Down

This file was deleted.

4 changes: 3 additions & 1 deletion plugins/reporters/asciidoc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@

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

dependencies {
api(projects.reporter)

ksp(projects.reporter)

implementation(projects.model)
implementation(projects.plugins.reporters.freemarkerReporter)
implementation(projects.utils.commonUtils)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import org.ossreviewtoolkit.utils.ort.createOrtTempDir
* [3]: https://github.com/asciidoctor/asciidoctorj
* [4]: https://docs.asciidoctor.org/asciidoctor/latest/convert/available
*/
open class AsciiDocTemplateReporter(private val backend: String, override val type: String) : Reporter {
abstract class AsciiDocTemplateReporter : Reporter {
companion object {
private const val ASCII_DOC_FILE_PREFIX = "AsciiDoc_"
private const val ASCII_DOC_FILE_EXTENSION = "adoc"
Expand All @@ -53,6 +53,8 @@ open class AsciiDocTemplateReporter(private val backend: String, override val ty
private const val DEFECT_TEMPLATE_ID = "defect_report"
}

protected abstract val backend: String

private val templateProcessor = FreemarkerTemplateProcessor(
ASCII_DOC_TEMPLATE_DIRECTORY,
ASCII_DOC_FILE_PREFIX,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,23 @@

package org.ossreviewtoolkit.plugins.reporters.asciidoc

import org.ossreviewtoolkit.plugins.api.OrtPlugin
import org.ossreviewtoolkit.plugins.api.PluginDescriptor
import org.ossreviewtoolkit.reporter.Reporter
import org.ossreviewtoolkit.reporter.ReporterFactory

/**
* A [Reporter] that creates [DocBook][1] files from [Apache Freemarker][2] templates.
*
* [1]: https://docbook.org
* [2]: https://freemarker.apache.org
*/
class DocBookTemplateReporter : AsciiDocTemplateReporter("docbook", "DocBookTemplate")
@OrtPlugin(
displayName = "DocBook Template Reporter",
description = "Generates DocBook files from Apache Freemarker templates.",
factory = ReporterFactory::class
)
class DocBookTemplateReporter(override val descriptor: PluginDescriptor = DocBookTemplateReporterFactory.descriptor) :
AsciiDocTemplateReporter() {
override val backend = "docbook"
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,22 @@

package org.ossreviewtoolkit.plugins.reporters.asciidoc

import org.ossreviewtoolkit.plugins.api.OrtPlugin
import org.ossreviewtoolkit.plugins.api.PluginDescriptor
import org.ossreviewtoolkit.reporter.Reporter
import org.ossreviewtoolkit.reporter.ReporterFactory

/**
* A [Reporter] that creates HTML files from [Apache Freemarker][1] templates.
*
* [1]: https://freemarker.apache.org
*/
class HtmlTemplateReporter : AsciiDocTemplateReporter("html", "HtmlTemplate")
@OrtPlugin(
displayName = "HTML Template Reporter",
description = "Generates HTML files from Apache Freemarker templates.",
factory = ReporterFactory::class
)
class HtmlTemplateReporter(override val descriptor: PluginDescriptor = HtmlTemplateReporterFactory.descriptor) :
AsciiDocTemplateReporter() {
override val backend = "html"
}
Loading
Loading