Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save matmannion/9fb0789c628d8df9860a37ec10ef539d to your computer and use it in GitHub Desktop.
Save matmannion/9fb0789c628d8df9860a37ec10ef539d to your computer and use it in GitHub Desktop.
package snyk
import net.virtualvoid.sbt.graph.DependencyGraphKeys.moduleGraph
import net.virtualvoid.sbt.graph.{ DependencyGraphPlugin, ModuleId }
import sbt._, Keys._
import sjsonnew.shaded.scalajson.ast._
import sjsonnew.support.scalajson.unsafe.PrettyPrinter
object SnykSbtPlugin extends AutoPlugin {
val ConfigBlacklist: Set[String] =
Set("windows", "universal", "universal-docs", "debian", "rpm", "universal-src", "docker", "linux")
case class SnykModuleInfo(version: String, configurations: Set[String])
case class SnykProjectData(
projectId: String,
modules: Map[String, SnykModuleInfo],
dependencies: Map[String, Set[String]]
) {
def merge(otherModules: Map[String, SnykModuleInfo], otherDeps: Map[String, Set[String]]): SnykProjectData = {
val mergedModules = otherModules.foldLeft(modules) {
case (acc, (moduleName, moduleInfo)) =>
acc.get(moduleName) match {
case Some(existing) =>
acc + (moduleName -> SnykModuleInfo(
existing.version,
existing.configurations ++ moduleInfo.configurations
))
case None =>
acc + (moduleName -> moduleInfo)
}
}
val mergedDeps = dependencies ++ otherDeps
SnykProjectData(projectId, mergedModules, mergedDeps)
}
}
override def requires = sbt.plugins.JvmPlugin && DependencyGraphPlugin
override def trigger = allRequirements
object autoImport {
lazy val snykExtractProjectData = taskKey[SnykProjectData]("Extracts the dependency information for each project")
lazy val snykRenderTree = taskKey[Unit]("Renders the dependency information for all projects")
}
import autoImport._
override lazy val globalSettings = Seq(snykRenderTree := Def.taskDyn {
val allProjs = buildStructure.value.allProjectRefs
val filter = ScopeFilter(inProjects(allProjs: _*))
Def.task {
val allProjectDatas = snykExtractProjectData.all(filter).value
println("Snyk Output Start")
println(PrettyPrinter(JObject(allProjectDatas.map {
case SnykProjectData(projectId, modules, deps) =>
projectId -> JObject(
Map(
"modules" -> JObject(
modules.mapValues { moduleInfo =>
JObject(
Map(
"version" -> JString(moduleInfo.version),
"configurations" -> JArray(moduleInfo.configurations.toVector.map(JString))
)
)
}
),
"dependencies" -> JObject(
deps.mapValues(vs => JArray(vs.toVector.map(JString)))
)
)
)
}.toMap).toUnsafe))
println("Snyk Output End")
}
}.value)
override lazy val projectSettings = Seq(
snykExtractProjectData := Def.taskDyn {
def formatModuleId(m: ModuleId) = s"${m.organisation}:${m.name}"
val thisProjectId = formatModuleId((Compile / moduleGraph).value.roots.head.id)
val thisProjectConfigs = thisProject.value.configurations.filterNot { c =>
ConfigBlacklist.contains(c.name)
}
val filter = ScopeFilter(configurations = inConfigurations(thisProjectConfigs: _*))
val configAndModuleGraph = Def.task {
val graph = moduleGraph.value
val configName = configuration.value.name
configName -> graph
}
Def.task {
val graphs = configAndModuleGraph.all(filter).value
graphs.foldLeft(SnykProjectData(thisProjectId, Map.empty, Map.empty)) {
case (projectData, (configName, graph)) =>
val modules = graph.modules.flatMap {
case (moduleId, module) =>
if (module.isUsed) Some(formatModuleId(moduleId) -> SnykModuleInfo(moduleId.version, Set(configName)))
else None
}
val depMap = graph.dependencyMap
val dependencies = graph.modules.flatMap {
case (moduleId, module) =>
if (module.isUsed)
depMap.get(moduleId).map { deps =>
formatModuleId(moduleId) -> deps.collect {
case dep if dep.isUsed => formatModuleId(dep.id)
}.toSet
} else None
}
projectData.merge(modules, dependencies)
}
}
}.value
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment