Skip to content

Commit

Permalink
feat(ctrlx-reporter): Allow license filtering based on classifications
Browse files Browse the repository at this point in the history
Signed-off-by: Nicolas Nobelis <[email protected]>
  • Loading branch information
nnobelis committed Jan 30, 2025
1 parent 92f0f05 commit 66a45e1
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,14 @@ import org.ossreviewtoolkit.model.RootDependencyIndex
import org.ossreviewtoolkit.model.Scope
import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.model.licenses.LicenseCategorization
import org.ossreviewtoolkit.model.licenses.LicenseCategory
import org.ossreviewtoolkit.model.licenses.LicenseClassifications
import org.ossreviewtoolkit.plugins.reporters.ctrlx.CtrlXAutomationReporter.Companion.REPORT_FILENAME
import org.ossreviewtoolkit.reporter.ORT_RESULT
import org.ossreviewtoolkit.reporter.ReporterInput
import org.ossreviewtoolkit.utils.ort.createOrtTempDir
import org.ossreviewtoolkit.utils.spdx.SpdxSingleLicenseExpression
import org.ossreviewtoolkit.utils.spdx.toSpdx
import org.ossreviewtoolkit.utils.test.getAssetFile

Expand Down Expand Up @@ -87,6 +91,35 @@ class CtrlXAutomationReporterFunTest : StringSpec({
}
}
}

"The reporter should only include licenses with the given category" {
val category = "include-in-disclosure-document"
val categorizations = listOf(
LicenseCategorization(
SpdxSingleLicenseExpression.parse("MIT"),
setOf(category)
)
)
val categories = listOf(LicenseCategory(category))
val input = createReporterInput().copy(
licenseClassifications = LicenseClassifications(
categories = categories,
categorizations = categorizations
),
licenseCategoriesToInclude = listOf(category)
)
val reporter = CtrlXAutomationReporter()
val outputDir = createOrtTempDir("ctrlx-automation-reporter-test")

val reporterResult = reporter.generateReport(input, outputDir)

validateReport(reporterResult) {
components.shouldNotBeNull {
this shouldHaveSize 1
first().name shouldBe "package2"
}
}
}
})

private fun validateReport(reporterResult: List<Result<File>>, validate: FossInfo.() -> Unit) {
Expand Down
63 changes: 39 additions & 24 deletions plugins/reporters/ctrlx/src/main/kotlin/CtrlXAutomationReporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ class CtrlXAutomationReporter(override val descriptor: PluginDescriptor = CtrlXA

override fun generateReport(input: ReporterInput, outputDir: File): List<Result<File>> {
val packages = input.ortResult.getPackages(omitExcluded = true)
val components = packages.mapTo(mutableListOf()) { (pkg, _) ->

val components = packages.mapNotNullTo(mutableListOf()) { (pkg, _) ->
val qualifiedName = when (pkg.id.type) {
// At least for NPM packages, CtrlX requires the component name to be prefixed with the scope name,
// separated with a slash. Other package managers might require similar handling, but there seems to be
Expand All @@ -67,31 +68,45 @@ class CtrlXAutomationReporter(override val descriptor: PluginDescriptor = CtrlXA
}

val resolvedLicenseInfo = input.licenseInfoResolver.resolveLicenseInfo(pkg.id).filterExcluded()
val copyrights = resolvedLicenseInfo.getCopyrights().joinToString("\n").takeUnless { it.isEmpty() }
val effectiveLicense = resolvedLicenseInfo.effectiveLicense(
LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED,
input.ortResult.getPackageLicenseChoices(pkg.id),
input.ortResult.getRepositoryLicenseChoices()
val filteredResolvedLicenseInfo = resolvedLicenseInfo.filterNoCategorizedLicenses(
input.licenseClassifications,
input.licenseCategoriesToInclude
)
val licenses = effectiveLicense?.decompose()?.map {
val name = it.toString()
val spdxId = SpdxLicense.forId(name)?.id
val text = input.licenseTextProvider.getLicenseText(name)
License(name = name, spdx = spdxId, text = text.orEmpty())
}

// The specification requires at least one license.
val componentLicenses = licenses.orEmpty().ifEmpty { listOf(LICENSE_NOASSERTION) }

Component(
name = qualifiedName,
version = pkg.id.version,
homepage = pkg.homepageUrl.takeUnless { it.isEmpty() },
copyright = copyrights?.let { CopyrightInformation(it) },
licenses = componentLicenses,
usage = if (pkg.isModified) Usage.Modified else Usage.AsIs
// TODO: Map the PackageLinkage to an IntegrationMechanism.
)
// If the license was removed by the classification filter, we don't output it in the report (otherwise it
// would be included with NOASSERTION (see underneath).
if (filteredResolvedLicenseInfo.licenses.isEmpty() && resolvedLicenseInfo.licenses.isNotEmpty()) {
null
} else {
val copyrights = filteredResolvedLicenseInfo.getCopyrights().joinToString("\n").takeUnless {
it.isEmpty()
}

val effectiveLicense = filteredResolvedLicenseInfo.effectiveLicense(
LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED,
input.ortResult.getPackageLicenseChoices(pkg.id),
input.ortResult.getRepositoryLicenseChoices()
)
val licenses = effectiveLicense?.decompose()?.map {
val name = it.toString()
val spdxId = SpdxLicense.forId(name)?.id
val text = input.licenseTextProvider.getLicenseText(name)
License(name = name, spdx = spdxId, text = text.orEmpty())
}

// The specification requires at least one license.
val componentLicenses = licenses.orEmpty().ifEmpty { listOf(LICENSE_NOASSERTION) }

Component(
name = qualifiedName,
version = pkg.id.version,
homepage = pkg.homepageUrl.takeUnless { it.isEmpty() },
copyright = copyrights?.let { CopyrightInformation(it) },
licenses = componentLicenses,
usage = if (pkg.isModified) Usage.Modified else Usage.AsIs
// TODO: Map the PackageLinkage to an IntegrationMechanism.
)
}
}

val reportFileResult = runCatching {
Expand Down

0 comments on commit 66a45e1

Please sign in to comment.