Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,9 @@ data class EvaluatedFinding(
val endLine: Int,
val scanResult: EvaluatedScanResult,
@JsonInclude(JsonInclude.Include.NON_EMPTY)
val pathExcludes: List<PathExclude>
val pathExcludes: List<PathExclude>,

// If the finding is excluded by the presence of a [PathInclude], this property is true.
@JsonInclude(JsonInclude.Include.NON_EMPTY)
val isExcludedByPathIncludes: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import org.ossreviewtoolkit.model.config.RepositoryConfiguration
import org.ossreviewtoolkit.model.config.RuleViolationResolution
import org.ossreviewtoolkit.model.config.ScopeExclude
import org.ossreviewtoolkit.model.config.VulnerabilityResolution
import org.ossreviewtoolkit.model.config.PathInclude
import org.ossreviewtoolkit.model.mapperConfig
import org.ossreviewtoolkit.reporter.Reporter
import org.ossreviewtoolkit.reporter.ReporterInput
Expand Down Expand Up @@ -91,6 +92,7 @@ import org.ossreviewtoolkit.reporter.Statistics
*/
data class EvaluatedModel(
val pathExcludes: List<PathExclude>,
val pathIncludes: List<PathInclude>,
val scopeExcludes: List<ScopeExclude>,
val copyrights: List<CopyrightStatement>,
val licenses: List<LicenseId>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import org.ossreviewtoolkit.model.config.Resolutions
import org.ossreviewtoolkit.model.config.RuleViolationResolution
import org.ossreviewtoolkit.model.config.ScopeExclude
import org.ossreviewtoolkit.model.config.VulnerabilityResolution
import org.ossreviewtoolkit.model.config.PathInclude
import org.ossreviewtoolkit.model.licenses.LicenseView
import org.ossreviewtoolkit.model.toYaml
import org.ossreviewtoolkit.model.utils.FindingCurationMatcher
Expand All @@ -59,7 +60,7 @@ import org.ossreviewtoolkit.utils.spdx.calculatePackageVerificationCode
/**
* Maps the [reporter input][input] to an [EvaluatedModel].
*/
@Suppress("TooManyFunctions")
@Suppress("LargeClass", "TooManyFunctions")
internal class EvaluatedModelMapper(private val input: ReporterInput) {
private val packages = mutableMapOf<Identifier, EvaluatedPackage>()
private val paths = mutableListOf<EvaluatedPackagePath>()
Expand All @@ -71,6 +72,7 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
private val issues = mutableListOf<EvaluatedIssue>()
private val issueResolutions = mutableListOf<IssueResolution>()
private val pathExcludes = mutableListOf<PathExclude>()
private val pathIncludes = mutableListOf<PathInclude>()
private val scopeExcludes = mutableListOf<ScopeExclude>()
private val ruleViolations = mutableListOf<EvaluatedRuleViolation>()
private val ruleViolationResolutions = mutableListOf<RuleViolationResolution>()
Expand All @@ -84,6 +86,7 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
var id: Identifier,
var isExcluded: Boolean,
val pathExcludes: MutableList<PathExclude> = mutableListOf(),
val pathIncludes: MutableList<PathInclude> = mutableListOf(),
val scopeExcludes: MutableList<ScopeExclude> = mutableListOf()
)

Expand Down Expand Up @@ -134,6 +137,7 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {

return EvaluatedModel(
pathExcludes = pathExcludes,
pathIncludes = pathIncludes,
scopeExcludes = scopeExcludes,
issueResolutions = issueResolutions,
issues = issues,
Expand Down Expand Up @@ -169,21 +173,26 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {

input.ortResult.getProjects().forEach { project ->
val pathExcludes = input.ortResult.getExcludes().findPathExcludes(project, input.ortResult)
val pathIncludes = input.ortResult.getIncludes().findPathIncludes(project, input.ortResult)
val dependencies = input.ortResult.dependencyNavigator.projectDependencies(project)
if (pathExcludes.isEmpty()) {
if (pathExcludes.isEmpty() && pathIncludes.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a problem here: pathIncludes.isEmpty() can mean two things:

  • There are no path includes defined at all so this project is included.
  • There are path includes but none match this project so it is excluded.

I wonder if for the case that no path includes are defined at all, Includes should return a default path include which matches ** to make the usage unambiguous.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not too sure about this.

I also experimented with null to reflect the tri-state, and is the end I went with a separated boolean isExcludedByPathIncludes in EvaluatedFinding.

Maybe we can discuss this in the Community meeting.

Copy link
Member

@fviernau fviernau Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I right that semantics is as follows: A file is included, if it is not matched by any exclude OR if it is matched by at least one include and an arbitrary amount of excludes?

Copy link
Member

@mnonnenmacher mnonnenmacher Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, excludes always win. A file is included when: (there are no includes defined (which means include all) OR it is matched by an include) AND it is not matched by an exclude.

val info = packageExcludeInfo.getValue(project.id)
if (info.isExcluded) {
info.isExcluded = false
info.pathExcludes.clear()
info.scopeExcludes.clear()
}

info.pathIncludes.clear()
} else {
dependencies.forEach { id ->
val info = packageExcludeInfo.getOrPut(id) { PackageExcludeInfo(id, true) }

if (info.isExcluded) {
info.pathExcludes += pathExcludes
}

info.pathIncludes += pathIncludes
}
}

Expand Down Expand Up @@ -238,6 +247,14 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
.flatMap { it.pathExcludes }
}

private fun getPathIncludes(id: Identifier, provenance: Provenance): List<PathInclude> =
if (input.ortResult.isProject(id)) {
input.ortResult.getIncludes().paths
} else {
input.ortResult.getPackageConfigurations(id, provenance)
.flatMap { it.pathIncludes }
}

private fun addProject(project: Project) {
val scanResults = mutableListOf<EvaluatedScanResult>()
val detectedLicenses = mutableSetOf<LicenseId>()
Expand All @@ -246,7 +263,9 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
val issues = mutableListOf<EvaluatedIssue>()

val applicablePathExcludes = input.ortResult.getExcludes().findPathExcludes(project, input.ortResult)
val applicablePathIncludes = input.ortResult.getIncludes().findPathIncludes(project, input.ortResult)
val evaluatedPathExcludes = pathExcludes.addIfRequired(applicablePathExcludes)
pathIncludes.addIfRequired(applicablePathIncludes)

val evaluatedPackage = EvaluatedPackage(
id = project.id,
Expand Down Expand Up @@ -287,7 +306,8 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
findings.filter { it.type == EvaluatedFindingType.LICENSE }.mapNotNullTo(detectedLicenses) { it.license }

val includedDetectedLicenses = findings.filter {
it.type == EvaluatedFindingType.LICENSE && it.pathExcludes.isEmpty()
val isIncluded = pathIncludes.isEmpty() || pathIncludes.any { include -> include.matches(it.path) }
it.type == EvaluatedFindingType.LICENSE && it.pathExcludes.isEmpty() && isIncluded
}.mapNotNullTo(mutableSetOf()) { it.license }

detectedExcludedLicenses += detectedLicenses - includedDetectedLicenses
Expand Down Expand Up @@ -660,6 +680,7 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
findings: MutableList<EvaluatedFinding>
) {
val pathExcludes = getPathExcludes(id, scanResult.provenance)
val pathIncludes = getPathIncludes(id, scanResult.provenance)
val licenseFindingCurations = getLicenseFindingCurations(id, scanResult.provenance)
// Sort the curated findings here to avoid the need to sort in the web-app each time it is loaded.
val curatedFindings = curationsMatcher.applyAll(scanResult.summary.licenseFindings, licenseFindingCurations)
Expand All @@ -679,6 +700,16 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
val evaluatedPathExcludes = pathExcludes
.filter { it.matches(copyrightFinding.location.getRelativePathToRoot(id)) }
.let { [email protected](it) }
pathIncludes.let { [email protected](it) }

val evaluatedPathIncludes = if (pathIncludes.isEmpty() || pathIncludes.any { include ->
include.matches(copyrightFinding.location.getRelativePathToRoot(id))
}
) {
emptyList()
} else {
pathIncludes
}

findings += EvaluatedFinding(
type = EvaluatedFindingType.COPYRIGHT,
Expand All @@ -688,7 +719,8 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
startLine = copyrightFinding.location.startLine,
endLine = copyrightFinding.location.endLine,
scanResult = evaluatedScanResult,
pathExcludes = evaluatedPathExcludes
pathExcludes = evaluatedPathExcludes,
isExcludedByPathIncludes = evaluatedPathExcludes.isEmpty() && evaluatedPathIncludes.isNotEmpty()
)
}

Expand All @@ -699,6 +731,18 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
.filter { it.matches(licenseFinding.location.getRelativePathToRoot(id)) }
.let { [email protected](it) }

pathIncludes.let { [email protected](it) }

val evaluatedPathIncludes = if (pathIncludes.isEmpty() || pathIncludes.any {
include ->
include.matches(licenseFinding.location.getRelativePathToRoot(id))
}
) {
emptyList()
} else {
pathIncludes
}

findings += EvaluatedFinding(
type = EvaluatedFindingType.LICENSE,
license = actualLicense,
Expand All @@ -707,7 +751,8 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
startLine = licenseFinding.location.startLine,
endLine = licenseFinding.location.endLine,
scanResult = evaluatedScanResult,
pathExcludes = evaluatedPathExcludes
pathExcludes = evaluatedPathExcludes,
isExcludedByPathIncludes = evaluatedPathExcludes.isEmpty() && evaluatedPathIncludes.isNotEmpty()
)
}
}
Expand Down Expand Up @@ -772,6 +817,12 @@ internal class EvaluatedModelMapper(private val input: ReporterInput) {
)
}

return copy(config = config.copy(excludes = excludes, resolutions = resolutions))
val includes = with(config.includes) {
copy(
paths = paths.map { pathIncludes.addIfRequired(it) }
)
}

return copy(config = config.copy(excludes = excludes, includes = includes, resolutions = resolutions))
}
}