SnykSbtPlugin-1.2x.scala 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. package snyk
  2. import net.virtualvoid.sbt.graph.DependencyGraphKeys.moduleGraph
  3. import net.virtualvoid.sbt.graph.{ DependencyGraphPlugin, ModuleId }
  4. import sbt._, Keys._
  5. import sjsonnew.shaded.scalajson.ast._
  6. import sjsonnew.support.scalajson.unsafe.PrettyPrinter
  7. object SnykSbtPlugin extends AutoPlugin {
  8. val ConfigBlacklist: Set[String] =
  9. Set("windows", "universal", "universal-docs", "debian", "rpm", "universal-src", "docker", "linux", "web-assets", "web-plugin", "web-assets-test", "scalafix")
  10. case class SnykModuleInfo(version: String, configurations: Set[String])
  11. case class SnykProjectData(
  12. projectId: String,
  13. modules: Map[String, SnykModuleInfo],
  14. dependencies: Map[String, Set[String]]
  15. ) {
  16. def merge(otherModules: Map[String, SnykModuleInfo], otherDeps: Map[String, Set[String]]): SnykProjectData = {
  17. val mergedModules = otherModules.foldLeft(modules) {
  18. case (acc, (moduleName, moduleInfo)) =>
  19. acc.get(moduleName) match {
  20. case Some(existing) =>
  21. acc + (moduleName -> SnykModuleInfo(
  22. existing.version,
  23. existing.configurations ++ moduleInfo.configurations
  24. ))
  25. case None =>
  26. acc + (moduleName -> moduleInfo)
  27. }
  28. }
  29. val mergedDeps = dependencies ++ otherDeps
  30. SnykProjectData(projectId, mergedModules, mergedDeps)
  31. }
  32. }
  33. override def requires = sbt.plugins.JvmPlugin && DependencyGraphPlugin
  34. override def trigger = allRequirements
  35. object autoImport {
  36. lazy val snykExtractProjectData = taskKey[SnykProjectData]("Extracts the dependency information for each project")
  37. lazy val snykRenderTree = taskKey[Unit]("Renders the dependency information for all projects")
  38. }
  39. import autoImport._
  40. override lazy val globalSettings = Seq(snykRenderTree := Def.taskDyn {
  41. val allProjs = buildStructure.value.allProjectRefs
  42. val filter = ScopeFilter(inProjects(allProjs: _*))
  43. Def.task {
  44. val allProjectDatas = snykExtractProjectData.all(filter).value
  45. println("Snyk Output Start")
  46. println(PrettyPrinter(JObject(allProjectDatas.map {
  47. case SnykProjectData(projectId, modules, deps) =>
  48. projectId -> JObject(
  49. Map(
  50. "modules" -> JObject(
  51. modules.mapValues { moduleInfo =>
  52. JObject(
  53. Map(
  54. "version" -> JString(moduleInfo.version),
  55. "configurations" -> JArray(moduleInfo.configurations.toVector.map(JString))
  56. )
  57. )
  58. }
  59. ),
  60. "dependencies" -> JObject(
  61. deps.mapValues(vs => JArray(vs.toVector.map(JString)))
  62. )
  63. )
  64. )
  65. }.toMap).toUnsafe))
  66. println("Snyk Output End")
  67. }
  68. }.value)
  69. override lazy val projectSettings = Seq(
  70. snykExtractProjectData := Def.taskDyn {
  71. def formatModuleId(m: ModuleId) = s"${m.organisation}:${m.name}"
  72. val thisProjectId = formatModuleId((Compile / moduleGraph).value.roots.head.id)
  73. val thisProjectConfigs = thisProject.value.configurations.filterNot { c =>
  74. ConfigBlacklist.contains(c.name)
  75. }
  76. val filter = ScopeFilter(configurations = inConfigurations(thisProjectConfigs: _*))
  77. val configAndModuleGraph = Def.task {
  78. val graph = moduleGraph.value
  79. val configName = configuration.value.name
  80. configName -> graph
  81. }
  82. Def.task {
  83. val graphs = configAndModuleGraph.all(filter).value
  84. graphs.foldLeft(SnykProjectData(thisProjectId, Map.empty, Map.empty)) {
  85. case (projectData, (configName, graph)) =>
  86. val modules = graph.modules.flatMap {
  87. case (moduleId, module) =>
  88. if (module.isUsed) Some(formatModuleId(moduleId) -> SnykModuleInfo(moduleId.version, Set(configName)))
  89. else None
  90. }
  91. val depMap = graph.dependencyMap
  92. val dependencies = graph.modules.flatMap {
  93. case (moduleId, module) =>
  94. if (module.isUsed)
  95. depMap.get(moduleId).map { deps =>
  96. formatModuleId(moduleId) -> deps.collect {
  97. case dep if dep.isUsed => formatModuleId(dep.id)
  98. }.toSet
  99. } else None
  100. }
  101. projectData.merge(modules, dependencies)
  102. }
  103. }
  104. }.value
  105. )
  106. }