Skip to content

Sarif reporting#1660

Open
matejdro wants to merge 17 commits into
autonomousapps:mainfrom
matejdro:sarif-reporting
Open

Sarif reporting#1660
matejdro wants to merge 17 commits into
autonomousapps:mainfrom
matejdro:sarif-reporting

Conversation

@matejdro

@matejdro matejdro commented Mar 27, 2026

Copy link
Copy Markdown

PR adds an ability for the plugin to also export the report in the standard .sarif format, in addition to the txt.

Would appreciate feedback on some things:

  • For the best experience, issues in sarif file should have line numbers attached, but no such information exists in the DAGP. I have attempted to infer those line numbers by reading through the build.gradle(.kts) file and just string matching of the advice's coordinates. Is that okay? Could we come up with a better way to do this?
  • I have only enabled this in the buildHealth task, but not the projectHealth. The issue is that individual projects do not have access to the reporting config and thus could not enable this. If necessary, I could add public fun reporting(action: Action<ReportingHandler>) to the DependencyAnalysisSubExtension and add sarif output to the projectHealth
  • Any hints on how can I add tests for this? From what I saw, there are no existing tests checking report output, so I don't have any template to follow.

(This PR is based on the 3.5.1 due to #1653, as it makes testing easier for me. When the code is final, I can resolve conflicts and merge latest version in)

this fixes #1094

@autonomousapps autonomousapps left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for this PR. I have not done a thorough review because I don't have time at the moment, but I'm very open to seeing this progress and eventually get merged.

We ought to be able to resolve the issue with projectHealth not having access to the reporting handler. I think for this to be considered feature-complete, it would have to work for both buildHealth and projectHealth.

The build file scanning may have some issues, but I haven't thoroughly inspected it. As you know, the plugin's analysis uses Gradle's APIs to find dependencies—it doesn't actually scan build scripts unless it's trying to rewrite them. So that'll have to be done on a best-effort basis (which is what I think you're doing).

Comment thread src/main/kotlin/com/autonomousapps/internal/transform/StandardTransform.kt Outdated
@autonomousapps autonomousapps added the enhancement New feature or request label Apr 2, 2026
@autonomousapps

Copy link
Copy Markdown
Owner

I had a thought about this and I think I do not want to modify the Advice class. It's already quite complicated and I think that adding more properties to it is the wrong direction. In particular, it means you have to have the nullable line number available at construction time, which really complicates things. Instead, I propose that the report-generating task in the project (not buildHealth, to be clear) optionally* iterate over the advice and write out a new output with a new model that includes the advice and the line number. That new class might look like this:

*this should be opt-in, based on user preference.

public data class SarifAdvice( // name TBD
  advice: Advice,
  lineNumber: Int?,
)

And then we could emit a sorted set of SarifAdvice in json form to disk. That could then be further processed by projectHealth and buildHealth.

There are already examples in the project of configuring something in the root and having that value picked up safely in subprojects. If you need pointers, please let me know.

@matejdro

Copy link
Copy Markdown
Author

So you would want to generate yet another json next to the regular advice json just for an extra property and then consume that json in addition to the regular json? Sounds a pretty complicated thing to do.

Or did I miss the intention here?

@autonomousapps

Copy link
Copy Markdown
Owner

So you would want to generate yet another json next to the regular advice json just for an extra property and then consume that json in addition to the regular json? Sounds a pretty complicated thing to do.

Or did I miss the intention here?

Yes, that's correct. And also that feature should be opt-in, based on user preference (as expressed in the reporting DSL).

In my opinion, this is actually easier than what you've spiked here. json is cheap.

@matejdro

matejdro commented Apr 20, 2026

Copy link
Copy Markdown
Author

Updated. now dependency filtering task will also emit final-sourced-advice.json after filtering. If this is okay, I can continue with the implementation of the per-project report.

There is also an issue of reporting configuration: reportingHandler is a singleton in the GlobalDslService, which means that, currently, projects cannot each set their own reporting configuration (changing it in one project would override the config in all of them). Are there any negative side effects if I make this not-singleton?

@matejdro matejdro requested a review from autonomousapps April 20, 2026 08:34
@autonomousapps

Copy link
Copy Markdown
Owner

There is also an issue of reporting configuration: reportingHandler is a singleton in the GlobalDslService, which means that, currently, projects cannot each set their own reporting configuration (changing it in one project would override the config in all of them). Are there any negative side effects if I make this not-singleton?

I think it should remain a singleton. I don't see the benefit of allowing per-project variation here. Do you?

@matejdro

Copy link
Copy Markdown
Author

Yeah, then I should revert the reporting handler being accessible from project - it would only be configurable from the root project.

This is an unusual UX for the Gradle project, though. It is generally expected, that every project's configuration is only affecting that project, not anything else (especially after Isolated Projects). Configuration for root project affecting other project is a bit unexpected. But I guess DAGP has already set a precedent for this with bundles being set only on root, but applying everywhere.

matejdro added 4 commits May 29, 2026 08:17
# Conflicts:
#	README.asciidoc
#	src/main/kotlin/com/autonomousapps/internal/artifacts/DagpArtifacts.kt
#	src/main/kotlin/com/autonomousapps/internal/utils/utils.kt
#	src/main/kotlin/com/autonomousapps/subplugin/ProjectPlugin.kt
#	src/main/kotlin/com/autonomousapps/subplugin/RootPlugin.kt
This reverts commit aa4e1da. It was decided that this should only be set on the top level.

val sarif: SarifSchema210

private val advicePrinter = AdvicePrinter(dslKind, ProjectType.JVM, dependencyMap, useTypesafeProjectAccessors)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

ProjectType appears to be unused in AdvicePrinter? So I just hardcoded it to the JVM for now. Is a future use expected?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Huh. I added that when I added the first burst of KMP support. I can't say now whether not using it is an oversight, or leaving it in as an unused constructor is the oversight.

@matejdro

Copy link
Copy Markdown
Author

Updated to the latest main, reverted per-project sarif settings and added sarif reporting to the per-project tasks. I think it's ready for the next review now.

@autonomousapps autonomousapps left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Didn't finish review, left a bunch of comments. Please double-check all your changes and let me know when you're resolved all the weird formatting inconsistencies and breakages.

Comment thread src/main/kotlin/com/autonomousapps/internal/artifacts/DagpArtifacts.kt Outdated
public fun ofAdd(
coordinates: Coordinates,
toConfiguration: String,
declarationLineNumber: Int? = null

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

vestige from an earlier version of this PR, I think?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reverted

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I think probably all the changes in this file could be reverted.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reverted

@JsonClass(generateAdapter = false)
public data class SourcedAdvice(
val advice: Advice,
val buildFileDeclarationLineNumber: Int? = null,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Once we're creating an instance of this type, can the line number be null?

@matejdro matejdro Jun 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, because line number is a guess at best. Sometimes, we will not know the line number at all (if nothing matches in the build file). In this case, it is null.

val warning: Warning = Warning.empty(),
/** True if there is any advice in a category for which the user has declared they want the build to fail. */
val shouldFail: Boolean = false,
val projectBuildFile: String? = null,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

should this be nullable?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You are right. It was nullable in the previous version, but in the new separate-class version it's always filled.

Comment on lines +1189 to +1193
t.sarifOutput.set(
dagpExtension.reportingHandler.sarifReport.flatMap { sarifReportEnabled ->
if (sarifReportEnabled) paths.sarifReportPath else null
}
)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Set the output unconditionally.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

Comment on lines +1244 to +1246
if (dagpExtension.reportingHandler.sarifReport.get()) {
sourcedProjectHealthPublisher.publish(filterAdviceTask.flatMap { it.sourcedOutput })
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Unconditional.

@matejdro matejdro Jun 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I think this one needs to stay conditional. Publish output always needs to be present, otherwise the build will fail. And, when sarif report is disabled, sourcedOutput would not return any outputs. So, we must disable publishing in this case, to avoid the empty publish scenario.

Nevermind, we can just not-read the resolver side. Removed the condition.

project = project,
artifactDescription = DagpArtifacts.Kind.PROJECT_HEALTH,
)
private val sourcedAdviceResolver = interProjectResolver(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

alphabetical

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sorted

Comment on lines +139 to +143
t.sarifOutput.set(
dagpExtension.reportingHandler.sarifReport.flatMap { sarifReportEnabled ->
if (sarifReportEnabled) paths.sarifReportPath else null
}
)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Unconditional

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

Comment on lines +156 to +169
tasks.register("generateWorkPlan", GenerateWorkPlan::class.java) { t ->
tasks.register(
"generateWorkP it.sarifReport.set(generateBuildHealthTask.flatMap { it.sarifOutput })\nlan",
GenerateWorkPlan::class.java
) { t ->

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

??

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Looks like a copy paste error. Reverted.

@matejdro

matejdro commented Jun 5, 2026

Copy link
Copy Markdown
Author

I think that should be all weird indentations fixed. Sorry about that.

@matejdro matejdro requested a review from autonomousapps June 5, 2026 06:17
@autonomousapps

Copy link
Copy Markdown
Owner

I think that should be all weird indentations fixed. Sorry about that.

Thanks for addressing! I'll continue my review soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generate output for project/build health in machine-readable format

2 participants