diff --git a/src/main/kotlin/slack/cli/sarif/MergeSarifReports.kt b/src/main/kotlin/slack/cli/sarif/MergeSarifReports.kt index 18495bf..2e44691 100644 --- a/src/main/kotlin/slack/cli/sarif/MergeSarifReports.kt +++ b/src/main/kotlin/slack/cli/sarif/MergeSarifReports.kt @@ -16,15 +16,23 @@ package slack.cli.sarif import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.required -import com.github.ajalt.clikt.parameters.types.file +import com.github.ajalt.clikt.parameters.types.path import com.google.auto.service.AutoService import io.github.detekt.sarif4k.SarifSchema210 import io.github.detekt.sarif4k.SarifSerializer -import java.io.File +import java.nio.file.Path import kotlin.io.path.absolutePathString +import kotlin.io.path.createParentDirectories +import kotlin.io.path.deleteIfExists +import kotlin.io.path.exists +import kotlin.io.path.readText +import kotlin.io.path.relativeTo +import kotlin.io.path.writeText import kotlin.system.exitProcess import slack.cli.CommandFactory import slack.cli.projectDirOption @@ -41,8 +49,10 @@ public class MergeSarifReports : CliktCommand(help = DESCRIPTION) { } private val projectDir by projectDirOption() - private val outputFile: File by option("--output-file").file().required() - private val filePrefix by option("--file-prefix").required() + private val outputFile by option("--output-file").path().required() + private val filePrefix by option("--file-prefix") + private val argFiles by + argument("--files").path(mustExist = true, canBeDir = false, mustBeReadable = true).multiple() private val verbose by option("--verbose", "-v").flag() private val remapSrcRoots by option( @@ -75,13 +85,11 @@ public class MergeSarifReports : CliktCommand(help = DESCRIPTION) { } private fun prepareOutput() { - if (outputFile.exists()) { - outputFile.delete() - } - outputFile.parentFile?.mkdirs() + outputFile.deleteIfExists() + outputFile.createParentDirectories() } - private fun findBuildFiles(): List { + private fun findBuildFiles(): List { log("Finding build files in ${projectDir.toFile().canonicalFile}") val buildFiles = projectDir @@ -90,6 +98,7 @@ public class MergeSarifReports : CliktCommand(help = DESCRIPTION) { .walkTopDown() .skipBuildAndCacheDirs() .filter { it.name == "build.gradle.kts" } + .map { it.toPath() } .toList() log("${buildFiles.size} build files found") return buildFiles @@ -97,25 +106,41 @@ public class MergeSarifReports : CliktCommand(help = DESCRIPTION) { private fun String.prefixPathWith(prefix: String) = "$prefix/$this" - private fun findSarifFiles(): List { - // Find build files first, this gives us an easy hook to then go looking in build/reports dirs. - // Otherwise we don't have a way to easily exclude populated build dirs that would take forever. - val buildFiles = findBuildFiles() + private fun findSarifFiles(): List { + require(filePrefix != null || argFiles.isNotEmpty()) { + "Must specify either --file-prefix or pass files as arguments" + } - log("Finding sarif files") - return buildFiles - .asSequence() - .flatMap { buildFile -> - val reportsDir = File(buildFile.parentFile, "build/reports") - if (reportsDir.exists()) { - reportsDir.walkTopDown().filter { - it.isFile && it.extension == "sarif" && it.nameWithoutExtension.startsWith(filePrefix) + val files = mutableListOf() + + files += argFiles + + filePrefix?.let { prefix -> + // Find build files first, this gives us an easy hook to then go looking in build/reports + // dirs. + // Otherwise we don't have a way to easily exclude populated build dirs that would take + // forever. + val buildFiles = findBuildFiles() + + log("Finding sarif files") + files += + buildFiles.asSequence().flatMap { buildFile -> + val reportsDir = buildFile.parent.resolve("build/reports") + if (reportsDir.exists()) { + reportsDir + .toFile() + .walkTopDown() + .filter { + it.isFile && it.extension == "sarif" && it.nameWithoutExtension.startsWith(prefix) + } + .map { it.toPath() } + } else { + emptySequence() } - } else { - emptySequence() } - } - .toList() + } + + return files } /** @@ -150,16 +175,16 @@ public class MergeSarifReports : CliktCommand(help = DESCRIPTION) { * libraries/lib/src/main/java/com/example/app/LibActivity.kt * ``` */ - private fun SarifSchema210.remapSrcRoots(sarifFile: File): SarifSchema210 { - // /─────────────────────────────────┐ - // build/─────────────────────┐ │ - // reports/──────┐ │ │ - // ▼ ▼ ▼ - val module = sarifFile.parentFile.parentFile.parentFile - check(File(module, "build.gradle.kts").exists()) { + private fun SarifSchema210.remapSrcRoots(sarifFile: Path): SarifSchema210 { + // /─────────────────────────┐ + // build/─────────────────┐ │ + // reports/──────┐ │ │ + // ▼ ▼ ▼ + val module = sarifFile.parent.parent.parent + check(module.resolve("build.gradle.kts").exists()) { "Expected to find build.gradle.kts in $module" } - val modulePrefix = module.toRelativeString(projectDir.toFile()) + val modulePrefix = module.relativeTo(projectDir).toString() return copy( runs = runs.map { run -> @@ -239,7 +264,7 @@ public class MergeSarifReports : CliktCommand(help = DESCRIPTION) { ) } - private fun loadSarifs(inputs: List): List { + private fun loadSarifs(inputs: List): List { return inputs.map { sarifFile -> log("Parsing $sarifFile") val parsed = SarifSerializer.fromJson(sarifFile.readText()) @@ -256,7 +281,7 @@ public class MergeSarifReports : CliktCommand(help = DESCRIPTION) { } } - private fun merge(inputs: List) { + private fun merge(inputs: List) { log("Parsing ${inputs.size} sarif files") val sarifs = loadSarifs(inputs)