Merge pull request #137 from jrudolph/w/update-to-sbt-1.0

Ongoing progress on migrating to sbt 1.0
This commit is contained in:
Johannes Rudolph 2017-10-24 18:33:55 +02:00 committed by GitHub
commit 2178b71c62
31 changed files with 450 additions and 275 deletions

View File

@ -2,4 +2,4 @@ sudo: false
language: scala
jdk: oraclejdk7
script:
- sbt test scripted
- sbt ";^test ;^scripted"

View File

@ -4,23 +4,20 @@
Visualize your project's dependencies.
## Preliminaries
The plugin works best with sbt >= 0.13.6. See the [compatibility notes](#compatibility-notes) to use this plugin with an older version of sbt.
## Usage Instructions
Since sbt-dependency-graph is an informational tool rather than one that changes your build, you will more than likely wish to
sbt-dependency-graph is an informational tool rather than one that changes your build, so you will more than likely wish to
install it as a [global plugin] so that you can use it in any SBT project without the need to explicitly add it to each one. To do
this, add the plugin dependency to `~/.sbt/0.13/plugins/plugins.sbt`:
this, add the plugin dependency to `~/.sbt/0.13/plugins/plugins.sbt` for sbt 0.13 or `~/.sbt/1.0/plugins/plugins.sbt` for sbt 1.0:
```scala
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.0")
```
To add the plugin only to a single project, put this line into `project/plugins.sbt` of your project, instead.
This plugin is an auto-plugin which will be automatically enabled starting from sbt 0.13.5.
The plugin currently supports sbt versions >= 0.13.6 and sbt 1.0.x. For versions supporting older versions of sbt see
the notes of version [0.8.2](https://github.com/jrudolph/sbt-dependency-graph/tree/v0.8.2#compatibility-notes).
## Main Tasks
@ -66,20 +63,6 @@ dependencyDotFile := file("dependencies.dot") //render dot file to `./dependenci
* [#19]: There's an unfixed bug with graph generation for particular layouts. Workaround:
Use `dependency-tree` instead of `dependency-graph`.
* [#39]: When using sbt-dependency-graph with sbt < 0.13.6.
## Compatibility notes
* sbt < 0.13.6: The plugin will fall back on the old ivy report XML backend which suffers from [#39].
* sbt < 0.13.5: Old versions of sbt have no `AutoPlugin` support, you need to add
```scala
net.virtualvoid.sbt.graph.DependencyGraphSettings.graphSettings
```
to your `build.sbt` or (`~/.sbt/0.13/user.sbt` for global configuration) to enable the plugin.
* sbt <= 0.12.x: Old versions of sbt are not actively supported any more. Please use the old version from the [0.7 branch](https://github.com/jrudolph/sbt-dependency-graph/tree/0.7).
## License
@ -87,5 +70,4 @@ Published under the [Apache License 2.0](http://en.wikipedia.org/wiki/Apache_lic
[global plugin]: http://www.scala-sbt.org/0.13/tutorial/Using-Plugins.html#Global+plugins
[global build configuration]: http://www.scala-sbt.org/0.13/docs/Global-Settings.html
[#19]: https://github.com/jrudolph/sbt-dependency-graph/issues/19
[#39]: https://github.com/jrudolph/sbt-dependency-graph/issues/39
[#19]: https://github.com/jrudolph/sbt-dependency-graph/issues/19

View File

@ -1,9 +1,30 @@
ScriptedPlugin.scriptedSettings
ScriptedPlugin.scriptedLaunchOpts += s"-Dproject.version=${version.value}"
libraryDependencies += "com.github.mdr" %% "ascii-graphs" % "0.0.3"
libraryDependencies ++= {
println(s"Evaluated ${sbtVersion in pluginCrossBuild value}")
if ((sbtVersion in pluginCrossBuild).value startsWith "0.13")
Seq("com.github.mdr" %% "ascii-graphs" % "0.0.3")
else
Nil
}
libraryDependencies += "org.specs2" %% "specs2" % "2.3.11" % "test"
scalacOptions ++= Seq("-deprecation", "-unchecked")
libraryDependencies += "org.specs2" %% "specs2-core" % "3.9.5" % Test
ScalariformSupport.formatSettings
libraryDependencies += Defaults.sbtPluginExtra(
"com.dwijnand" % "sbt-compat" % "1.1.0",
(sbtBinaryVersion in pluginCrossBuild).value,
(scalaBinaryVersion in update).value
)
crossSbtVersions := Seq("1.0.2", "0.13.16")
scalacOptions ++= Seq(
"-deprecation",
"-encoding", "UTF-8",
"-feature",
"-unchecked"
)
ScalariformSupport.formatSettings

View File

@ -4,8 +4,6 @@ name := "sbt-dependency-graph"
organization := "net.virtual-void"
version := "0.8.3-SNAPSHOT"
homepage := Some(url("http://github.com/jrudolph/sbt-dependency-graph"))
licenses in GlobalScope += "Apache License 2.0" -> url("https://github.com/jrudolph/sbt-dependency-graph/raw/master/LICENSE")

View File

@ -1,16 +0,0 @@
import java.net.URL
object Helpers {
def generatePomExtra(scmUrl: String, scmConnection: String,
developerId: String, developerName: String): xml.NodeSeq =
<scm>
<url>{ scmUrl }</url>
<connection>{ scmConnection }</connection>
</scm>
<developers>
<developer>
<id>{ developerId }</id>
<name>{ developerName }</name>
</developer>
</developers>
}

View File

@ -1 +1 @@
sbt.version=0.13.8
sbt.version=0.13.16

View File

@ -1 +1 @@
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")

View File

@ -1,3 +1,4 @@
libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value
addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.2.1")
addSbtPlugin("com.dwijnand" % "sbt-dynver" % "2.0.0")

View File

@ -1,7 +1,9 @@
publishTo <<= version { v: String =>
publishTo := {
val nexus = "https://oss.sonatype.org/"
if (v.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots")
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
Some {
if (version.value.trim.contains("+")) "snapshots" at nexus + "content/repositories/snapshots"
else "releases" at nexus + "service/local/staging/deploy/maven2"
}
}
publishMavenStyle := true
@ -10,9 +12,20 @@ publishArtifact in Test := false
pomIncludeRepository := { _ => false }
pomExtra :=
Helpers.generatePomExtra("git@github.com:jrudolph/sbt-dependency-graph.git",
"scm:git:git@github.com:jrudolph/sbt-dependency-graph.git",
"jrudolph", "Johannes Rudolph")
scmInfo := Some(
ScmInfo(
browseUrl = url("https://github.com/jrudolph/sbt-dependency-graph"),
connection = "scm:git:git@github.com:jrudolph/sbt-dependency-graph.git"
)
)
developers := List(
Developer(
"jrudolph",
"Johannes Rudolph",
"johannes.rudolph@gmail.com",
url("https://virtual-void.net")
)
)
useGpg := true

View File

@ -0,0 +1,3 @@
package net.virtualvoid.sbt.graph
trait ModuleGraphProtocolCompat

View File

@ -18,6 +18,8 @@ package net.virtualvoid.sbt.graph
package rendering
import com.github.mdr.ascii.layout._
import net.virtualvoid.sbt.graph.DependencyGraphKeys._
import sbt.Keys._
object AsciiGraph {
def asciiGraph(graph: ModuleGraph): String =
@ -35,4 +37,24 @@ object AsciiGraph {
val edges = moduleGraph.edges.toList.map { case (from, to) (renderVertex(moduleGraph.module(from)), renderVertex(moduleGraph.module(to))) }
Graph(vertices, edges)
}
def asciiGraphSetttings = Seq[sbt.Def.Setting[_]](
DependencyGraphKeys.asciiGraph := asciiGraph(moduleGraph.value),
dependencyGraph := {
val force = DependencyGraphSettings.shouldForceParser.parsed
val log = streams.value.log
if (force || moduleGraph.value.nodes.size < 15) {
log.info(rendering.AsciiGraph.asciiGraph(moduleGraph.value))
log.info("\n\n")
log.info("Note: The old tree layout is still available by using `dependency-tree`")
}
log.info(rendering.AsciiTree.asciiTree(moduleGraph.value))
if (!force) {
log.info("\n")
log.info("Note: The graph was estimated to be too big to display (> 15 nodes). Use `sbt 'dependency-graph --force'` (with the single quotes) to force graph display.")
}
}
)
}

View File

@ -0,0 +1,81 @@
package sbt
package dependencygraph
import scala.language.implicitConversions
import java.util.concurrent.TimeUnit
import Keys._
import Def.Initialize
import CrossVersion._
import scala.concurrent.duration.FiniteDuration
object DependencyGraphSbtCompat {
object Implicits {
implicit def convertConfig(config: sbt.Configuration): String = config.toString
implicit class RichUpdateConfiguration(val updateConfig: UpdateConfiguration) extends AnyVal {
def withMissingOk(missingOk: Boolean): UpdateConfiguration =
updateConfig.copy(missingOk = missingOk)
}
}
/**
* This is copied directly from https://github.com/sbt/sbt/blob/2952a2b9b672c5402b824ad2d2076243eb643598/main/src/main/scala/sbt/Defaults.scala#L1471-L1523
* and then changed to update the UpdateConfiguration to ignore missing artifacts.
*/
def updateTask(task: TaskKey[_]): Initialize[Task[UpdateReport]] = Def.task {
val depsUpdated = transitiveUpdate.value.exists(!_.stats.cached)
val isRoot = executionRoots.value contains resolvedScoped.value
val forceUpdate = forceUpdatePeriod.value
val s = streams.value
val fullUpdateOutput = s.cacheDirectory / "out"
val forceUpdateByTime = forceUpdate match {
case None => false
case Some(period) =>
val elapsedDuration = new FiniteDuration(System.currentTimeMillis() - fullUpdateOutput.lastModified(), TimeUnit.MILLISECONDS)
fullUpdateOutput.exists() && elapsedDuration > period
}
val scalaProvider = appConfiguration.value.provider.scalaProvider
// Only substitute unmanaged jars for managed jars when the major.minor parts of the versions the same for:
// the resolved Scala version and the scalaHome version: compatible (weakly- no qualifier checked)
// the resolved Scala version and the declared scalaVersion: assume the user intended scalaHome to override anything with scalaVersion
def subUnmanaged(subVersion: String, jars: Seq[File]) = (sv: String) =>
(partialVersion(sv), partialVersion(subVersion), partialVersion(scalaVersion.value)) match {
case (Some(res), Some(sh), _) if res == sh => jars
case (Some(res), _, Some(decl)) if res == decl => jars
case _ => Nil
}
val subScalaJars: String => Seq[File] = Defaults.unmanagedScalaInstanceOnly.value match {
case Some(si) => subUnmanaged(si.version, si.jars)
case None => sv => if (scalaProvider.version == sv) scalaProvider.jars else Nil
}
val transform: UpdateReport => UpdateReport = r => sbt.Classpaths.substituteScalaFiles(scalaOrganization.value, r)(subScalaJars)
val uwConfig = (unresolvedWarningConfiguration in update).value
val show = Reference.display(thisProjectRef.value)
val st = state.value
val logicalClock = LogicalClock(st.hashCode)
val depDir = dependencyCacheDirectory.value
val uc0 = (updateConfiguration in task).value
val ms = publishMavenStyle.value
val cw = compatibilityWarningOptions.value
// Normally, log would capture log messages at all levels.
// Ivy logs are treated specially using sbt.UpdateConfiguration.logging.
// This code bumps up the sbt.UpdateConfiguration.logging to Full when logLevel is Debug.
import UpdateLogging.{ Full, DownloadOnly, Default }
val uc = (logLevel in update).?.value orElse st.get(logLevel.key) match {
case Some(Level.Debug) if uc0.logging == Default => uc0.copy(logging = Full)
case Some(x) if uc0.logging == Default => uc0.copy(logging = DownloadOnly)
case _ => uc0
}
val ewo =
if (executionRoots.value exists { _.key == evicted.key }) EvictionWarningOptions.empty
else (evictionWarningOptions in update).value
sbt.Classpaths.cachedUpdate(s.cacheDirectory / updateCacheName.value, show, ivyModule.value, uc, transform,
skip = (skip in update).value, force = isRoot || forceUpdateByTime, depsUpdated = depsUpdated,
uwConfig = uwConfig, logicalClock = logicalClock, depDir = Some(depDir),
ewo = ewo, mavenStyle = ms, compatWarning = cw, log = s.log)
}
}

View File

@ -0,0 +1,25 @@
package net.virtualvoid.sbt.graph
import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, File }
import java.util.Base64
import sbinary.{ Format, JavaInput, JavaOutput }
import sjsonnew.{ Builder, Unbuilder }
trait ModuleGraphProtocolCompat {
implicit def sjsonNewAndShinyTransformAndTranspileAdapterFactoryModuleImplementation[T](implicit format: Format[T]): sjsonnew.JsonFormat[T] =
new sjsonnew.JsonFormat[T] {
// note, how this is simpler to write than to learn any sjonnew protocol syntax
def write[J](obj: T, builder: Builder[J]): Unit = {
val baos = new ByteArrayOutputStream()
format.writes(new JavaOutput(baos), obj)
val str = Base64.getEncoder.encodeToString(baos.toByteArray)
builder.writeString(str)
}
def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T = {
val str = unbuilder.readString(jsOpt.get)
val bais = new ByteArrayInputStream(Base64.getDecoder.decode(str))
format.reads(new JavaInput(bais))
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2017 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph
package rendering
object AsciiGraph {
def asciiGraphSetttings = Seq.empty[sbt.Def.Setting[_]]
}

View File

@ -0,0 +1,64 @@
package sbt
package dependencygraph
import Keys._
import Def.Initialize
import CrossVersion.partialVersion
import sbt.internal.LibraryManagement
object DependencyGraphSbtCompat {
object Implicits
// https://github.com/sbt/sbt/blob/4ce4fb72bde3b8acfaf526b79d32ca1463bc687b/main/src/main/scala/sbt/Defaults.scala#L2298 adapted
// to allow customization of UpdateConfiguration
def updateTask(task: TaskKey[_]): Initialize[Task[UpdateReport]] = Def.task {
val depsUpdated = transitiveUpdate.value.exists(!_.stats.cached)
val isRoot = executionRoots.value contains resolvedScoped.value
val s = streams.value
val scalaProvider = appConfiguration.value.provider.scalaProvider
// Only substitute unmanaged jars for managed jars when the major.minor parts of the versions the same for:
// the resolved Scala version and the scalaHome version: compatible (weakly- no qualifier checked)
// the resolved Scala version and the declared scalaVersion: assume the user intended scalaHome to override anything with scalaVersion
def subUnmanaged(subVersion: String, jars: Seq[File]) = (sv: String)
(partialVersion(sv), partialVersion(subVersion), partialVersion(scalaVersion.value)) match {
case (Some(res), Some(sh), _) if res == sh jars
case (Some(res), _, Some(decl)) if res == decl jars
case _ Nil
}
val subScalaJars: String Seq[File] = SbtAccess.unmanagedScalaInstanceOnly.value match {
case Some(si) subUnmanaged(si.version, si.allJars)
case None sv if (scalaProvider.version == sv) scalaProvider.jars else Nil
}
val transform: UpdateReport UpdateReport =
r Classpaths.substituteScalaFiles(scalaOrganization.value, r)(subScalaJars)
val evictionOptions = Def.taskDyn {
if (executionRoots.value.exists(_.key == evicted.key))
Def.task(EvictionWarningOptions.empty)
else Def.task((evictionWarningOptions in update).value)
}.value
LibraryManagement.cachedUpdate(
// LM API
lm = dependencyResolution.value,
// Ivy-free ModuleDescriptor
module = ivyModule.value,
s.cacheStoreFactory.sub(updateCacheName.value),
Reference.display(thisProjectRef.value),
(updateConfiguration in task).value,
transform = transform,
skip = (skip in update).value,
force = isRoot,
depsUpdated = transitiveUpdate.value.exists(!_.stats.cached),
uwConfig = (unresolvedWarningConfiguration in update).value,
ewo = evictionOptions,
mavenStyle = publishMavenStyle.value,
compatWarning = compatibilityWarningOptions.value,
log = s.log
)
}
}

View File

@ -16,152 +16,131 @@
package net.virtualvoid.sbt.graph
import scala.language.reflectiveCalls
import sbt._
import Keys._
import CrossVersion._
import sbt.complete.Parser
import org.apache.ivy.core.resolve.ResolveOptions
import net.virtualvoid.sbt.graph.backend.{ IvyReport, SbtUpdateReport }
import net.virtualvoid.sbt.graph.rendering.DagreHTML
import net.virtualvoid.sbt.graph.rendering.{ AsciiGraph, DagreHTML }
import net.virtualvoid.sbt.graph.util.IOUtil
import internal.librarymanagement._
import librarymanagement._
import sbt.dependencygraph.DependencyGraphSbtCompat
import sbt.dependencygraph.DependencyGraphSbtCompat.Implicits._
object DependencyGraphSettings {
import DependencyGraphKeys._
import ModuleGraphProtocol._
def graphSettings = Seq(
ivyReportFunction <<= ivyReportFunctionTask,
updateConfiguration in ignoreMissingUpdate <<= updateConfiguration(config new UpdateConfiguration(config.retrieve, true, config.logging)),
ignoreMissingUpdateT,
filterScalaLibrary in Global := true) ++ Seq(Compile, Test, IntegrationTest, Runtime, Provided, Optional).flatMap(ivyReportForConfig)
def graphSettings = baseSettings ++ reportSettings
def baseSettings = Seq(
ivyReportFunction := ivyReportFunctionTask.value,
updateConfiguration in ignoreMissingUpdate := updateConfiguration.value.withMissingOk(true),
ignoreMissingUpdate := DependencyGraphSbtCompat.updateTask(ignoreMissingUpdate).value,
filterScalaLibrary in Global := true)
def reportSettings =
Seq(Compile, Test, IntegrationTest, Runtime, Provided, Optional).flatMap(ivyReportForConfig)
def ivyReportForConfig(config: Configuration) = inConfig(config)(Seq(
ivyReport <<= ivyReportFunction map (_(config.toString)) dependsOn (ignoreMissingUpdate),
crossProjectId <<= (scalaVersion, scalaBinaryVersion, projectID)((sV, sBV, id) CrossVersion(sV, sBV)(id)),
moduleGraphSbt <<= moduleGraphSbtTask,
moduleGraphIvyReport <<= moduleGraphIvyReportTask,
moduleGraph <<= (sbtVersion, moduleGraphSbt, moduleGraphIvyReport) { (version, graphSbt, graphIvy)
version match {
case Version(0, 13, x, _) if x >= 6 graphSbt
case _ graphIvy
ivyReport := { Def.task { ivyReportFunction.value.apply(config.toString) } dependsOn (ignoreMissingUpdate) }.value,
crossProjectId := sbt.CrossVersion(scalaVersion.value, scalaBinaryVersion.value)(projectID.value),
moduleGraphSbt :=
ignoreMissingUpdate.value.configuration(configuration.value).map(report SbtUpdateReport.fromConfigurationReport(report, crossProjectId.value)).getOrElse(ModuleGraph.empty),
moduleGraphIvyReport := IvyReport.fromReportFile(absoluteReportPath(ivyReport.value)),
moduleGraph := {
sbtVersion.value match {
case Version(0, 13, x, _) if x >= 6 moduleGraphSbt.value
case Version(1, _, _, _) moduleGraphSbt.value
}
},
moduleGraph <<= (scalaVersion, moduleGraph, filterScalaLibrary) map { (scalaV, graph, filter)
if (filter) GraphTransformations.ignoreScalaLibrary(scalaV, graph)
else graph
},
moduleGraphStore <<= moduleGraph storeAs moduleGraphStore triggeredBy moduleGraph,
asciiGraph <<= moduleGraph map rendering.AsciiGraph.asciiGraph,
dependencyGraph <<= InputTask(shouldForceParser) { force
(force, moduleGraph, streams) map { (force, graph, streams)
if (force || graph.nodes.size < 15) {
streams.log.info(rendering.AsciiGraph.asciiGraph(graph))
streams.log.info("\n\n")
streams.log.info("Note: The old tree layout is still available by using `dependency-tree`")
} else {
streams.log.info(rendering.AsciiTree.asciiTree(graph))
moduleGraph := {
// FIXME: remove busywork
val scalaVersion = Keys.scalaVersion.value
val moduleGraph = DependencyGraphKeys.moduleGraph.value
if (!force) {
streams.log.info("\n")
streams.log.info("Note: The graph was estimated to be too big to display (> 15 nodes). Use `sbt 'dependency-graph --force'` (with the single quotes) to force graph display.")
}
}
}
if (filterScalaLibrary.value) GraphTransformations.ignoreScalaLibrary(scalaVersion, moduleGraph)
else moduleGraph
},
asciiTree <<= moduleGraph map rendering.AsciiTree.asciiTree,
dependencyTree <<= print(asciiTree),
dependencyGraphMLFile <<= target / "dependencies-%s.graphml".format(config.toString),
dependencyGraphML <<= dependencyGraphMLTask,
dependencyDotFile <<= target / "dependencies-%s.dot".format(config.toString),
dependencyDotString <<= dependencyDotStringTask,
dependencyDot <<= writeToFile(dependencyDotString, dependencyDotFile),
dependencyBrowseGraphTarget <<= target / "browse-dependency-graph",
dependencyBrowseGraphHTML <<= browseGraphHTMLTask,
dependencyBrowseGraph <<= (dependencyBrowseGraphHTML, streams).map { (uri, streams)
streams.log.info("Opening in browser...")
moduleGraphStore := (moduleGraph storeAs moduleGraphStore triggeredBy moduleGraph).value,
asciiTree := rendering.AsciiTree.asciiTree(moduleGraph.value),
dependencyTree := print(asciiTree).value,
dependencyGraphMLFile := { target.value / "dependencies-%s.graphml".format(config.toString) },
dependencyGraphML := dependencyGraphMLTask.value,
dependencyDotFile := { target.value / "dependencies-%s.dot".format(config.toString) },
dependencyDotString := rendering.DOT.dotGraph(moduleGraph.value, dependencyDotHeader.value, dependencyDotNodeLabel.value, rendering.DOT.AngleBrackets),
dependencyDot := writeToFile(dependencyDotString, dependencyDotFile).value,
dependencyBrowseGraphTarget := { target.value / "browse-dependency-graph" },
dependencyBrowseGraphHTML := browseGraphHTMLTask.value,
dependencyBrowseGraph := {
val uri = dependencyBrowseGraphHTML.value
streams.value.log.info("Opening in browser...")
java.awt.Desktop.getDesktop.browse(uri)
uri
},
dependencyList <<= printFromGraph(rendering.FlatList.render(_, _.id.idString)),
dependencyStats <<= printFromGraph(rendering.Statistics.renderModuleStatsList),
dependencyDotHeader := """digraph "dependency-graph" {
| graph[rankdir="LR"]
| edge [
| arrowtail="none"
| ]""".stripMargin,
dependencyList := printFromGraph(rendering.FlatList.render(_, _.id.idString)).value,
dependencyStats := printFromGraph(rendering.Statistics.renderModuleStatsList).value,
dependencyDotHeader :=
"""|digraph "dependency-graph" {
| graph[rankdir="LR"]
| edge [
| arrowtail="none"
| ]""".stripMargin,
dependencyDotNodeLabel := { (organisation: String, name: String, version: String)
"""%s<BR/><B>%s</B><BR/>%s""".format(organisation, name, version)
},
whatDependsOn <<= InputTask(artifactIdParser) { module
(module, streams, moduleGraph) map { (module, streams, graph)
streams.log.info(rendering.AsciiTree.asciiTree(GraphTransformations.reverseGraphStartingAt(graph, module)))
}
whatDependsOn := {
val module = artifactIdParser.parsed
streams.value.log.info(rendering.AsciiTree.asciiTree(GraphTransformations.reverseGraphStartingAt(moduleGraph.value, module)))
},
licenseInfo <<= (moduleGraph, streams) map showLicenseInfo))
licenseInfo := showLicenseInfo(moduleGraph.value, streams.value)) ++ AsciiGraph.asciiGraphSetttings)
def ivyReportFunctionTask =
(sbtVersion, target, projectID, ivyModule, appConfiguration, streams) map { (sbtV, target, projectID, ivyModule, config, streams)
sbtV match {
case Version(0, min, fix, _) if min > 12 || (min == 12 && fix >= 3)
(c: String) file("%s/resolution-cache/reports/%s-%s-%s.xml".format(target, projectID.organization, crossName(ivyModule), c))
case Version(0, min, fix, _) if min == 12 && fix >= 1 && fix < 3
ivyModule.withModule(streams.log) { (i, moduleDesc, _)
val id = ResolveOptions.getDefaultResolveId(moduleDesc)
(c: String) file("%s/resolution-cache/reports/%s/%s-resolved.xml" format (target, id, c))
}
case _
val home = config.provider.scalaProvider.launcher.ivyHome
(c: String) file("%s/cache/%s-%s-%s.xml" format (home, projectID.organization, crossName(ivyModule), c))
}
def ivyReportFunctionTask = Def.task {
val crossTarget = Keys.crossTarget.value
val projectID = Keys.projectID.value
val ivyModule = Keys.ivyModule.value
(config: String) {
val org = projectID.organization
val name = crossName(ivyModule)
file(s"${crossTarget}/resolution-cache/reports/$org-$name-$config.xml")
}
def moduleGraphIvyReportTask = ivyReport map (absoluteReportPath.andThen(IvyReport.fromReportFile))
def moduleGraphSbtTask =
(ignoreMissingUpdate, crossProjectId, configuration) map { (update, root, config)
update.configuration(config.name).map(report SbtUpdateReport.fromConfigurationReport(report, root)).getOrElse(ModuleGraph.empty)
}
def printAsciiGraphTask =
(streams, asciiGraph) map (_.log.info(_))
}
def dependencyGraphMLTask =
(moduleGraph, dependencyGraphMLFile, streams) map { (graph, resultFile, streams)
rendering.GraphML.saveAsGraphML(graph, resultFile.getAbsolutePath)
streams.log.info("Wrote dependency graph to '%s'" format resultFile)
Def.task {
val resultFile = dependencyGraphMLFile.value
rendering.GraphML.saveAsGraphML(moduleGraph.value, resultFile.getAbsolutePath)
streams.value.log.info("Wrote dependency graph to '%s'" format resultFile)
resultFile
}
def dependencyDotStringTask =
(moduleGraph, dependencyDotHeader, dependencyDotNodeLabel).map {
(graph, dotHead, nodeLabel) rendering.DOT.dotGraph(graph, dotHead, nodeLabel, rendering.DOT.AngleBrackets)
}
def browseGraphHTMLTask =
(moduleGraph, dependencyDotHeader, dependencyDotNodeLabel, dependencyBrowseGraphTarget, streams).map { (graph, dotHead, nodeLabel, target, streams)
val dotGraph = rendering.DOT.dotGraph(graph, dotHead, nodeLabel, rendering.DOT.LabelTypeHtml)
val link = DagreHTML.createLink(dotGraph, target)
streams.log.info(s"HTML graph written to $link")
Def.task {
val dotGraph = rendering.DOT.dotGraph(moduleGraph.value, dependencyDotHeader.value, dependencyDotNodeLabel.value, rendering.DOT.LabelTypeHtml)
val link = DagreHTML.createLink(dotGraph, target.value)
streams.value.log.info(s"HTML graph written to $link")
link
}
def writeToFile(dataTask: TaskKey[String], fileTask: SettingKey[File]) =
(dataTask, fileTask, streams).map { (data, outFile, streams)
IOUtil.writeToFile(data, outFile)
Def.task {
val outFile = fileTask.value
IOUtil.writeToFile(dataTask.value, outFile)
streams.log.info("Wrote dependency graph to '%s'" format outFile)
streams.value.log.info("Wrote dependency graph to '%s'" format outFile)
outFile
}
def absoluteReportPath = (file: File) file.getAbsolutePath
def print(key: TaskKey[String]) =
(streams, key) map (_.log.info(_))
Def.task { streams.value.log.info(key.value) }
def printFromGraph(f: ModuleGraph String) =
(streams, moduleGraph) map ((streams, graph) streams.log.info(f(graph)))
Def.task { streams.value.log.info(f(moduleGraph.value)) }
def showLicenseInfo(graph: ModuleGraph, streams: TaskStreams) {
val output =
@ -180,7 +159,7 @@ object DependencyGraphSettings {
(Space ~> token("--force")).?.map(_.isDefined)
}
val artifactIdParser: Initialize[State Parser[ModuleId]] =
val artifactIdParser: Def.Initialize[State Parser[ModuleId]] =
resolvedScoped { ctx
(state: State)
val graph = loadFromContext(moduleGraphStore, ctx, state) getOrElse ModuleGraph(Nil, Nil)
@ -216,34 +195,4 @@ object DependencyGraphSettings {
case _ None
}
}
/**
* This is copied directly from sbt/main/Defaults.java and then changed to update the UpdateConfiguration
* to ignore missing artifacts.
*/
def ignoreMissingUpdateT =
ignoreMissingUpdate <<= Def.task {
val depsUpdated = transitiveUpdate.value.exists(!_.stats.cached)
val isRoot = executionRoots.value contains resolvedScoped.value
val s = streams.value
val scalaProvider = appConfiguration.value.provider.scalaProvider
// Only substitute unmanaged jars for managed jars when the major.minor parts of the versions the same for:
// the resolved Scala version and the scalaHome version: compatible (weakly- no qualifier checked)
// the resolved Scala version and the declared scalaVersion: assume the user intended scalaHome to override anything with scalaVersion
def subUnmanaged(subVersion: String, jars: Seq[File]) = (sv: String)
(partialVersion(sv), partialVersion(subVersion), partialVersion(scalaVersion.value)) match {
case (Some(res), Some(sh), _) if res == sh jars
case (Some(res), _, Some(decl)) if res == decl jars
case _ Nil
}
val subScalaJars: String Seq[File] = SbtAccess.unmanagedScalaInstanceOnly.value match {
case Some(si) subUnmanaged(si.version, si.jars)
case None sv if (scalaProvider.version == sv) scalaProvider.jars else Nil
}
val transform: UpdateReport UpdateReport = r Classpaths.substituteScalaFiles(scalaOrganization.value, r)(subScalaJars)
val show = Reference.display(thisProjectRef.value)
Classpaths.cachedUpdate(s.cacheDirectory, show, ivyModule.value, (updateConfiguration in ignoreMissingUpdate).value, transform, skip = (skip in update).value, force = isRoot, depsUpdated = depsUpdated, log = s.log)
}
}

View File

@ -17,6 +17,9 @@
package net.virtualvoid.sbt.graph
package backend
import scala.language.implicitConversions
import scala.language.reflectiveCalls
import sbt._
object SbtUpdateReport {

View File

@ -18,7 +18,9 @@ package net.virtualvoid.sbt.graph
import java.io.File
import scala.collection.mutable.{ MultiMap, HashMap, Set }
import sbinary.Format
import scala.collection.mutable.{ HashMap, MultiMap, Set }
case class ModuleId(organisation: String,
name: String,
@ -65,8 +67,9 @@ case class ModuleGraph(nodes: Seq[Module], edges: Seq[Edge]) {
nodes.filter(n !edges.exists(_._2 == n.id)).sortBy(_.id.idString)
}
import sbinary.{ Format, DefaultProtocol }
object ModuleGraphProtocol extends DefaultProtocol {
object ModuleGraphProtocol extends ModuleGraphProtocolCompat {
import sbinary.DefaultProtocol._
implicit def seqFormat[T: Format]: Format[Seq[T]] = wrap[Seq[T], List[T]](_.toList, _.toSeq)
implicit val ModuleIdFormat: Format[ModuleId] = asProduct3(ModuleId)(ModuleId.unapply(_).get)
implicit val ModuleFormat: Format[Module] = asProduct6(Module)(Module.unapply(_).get)

View File

@ -5,6 +5,8 @@
*/
package net.virtualvoid.sbt.graph.util
import sbt.dependencygraph.SbtAccess
object AsciiTreeLayout {
// [info] foo
// [info] +-bar
@ -52,7 +54,7 @@ object AsciiTreeLayout {
}
def defaultColumnSize: Int = {
val termWidth = sbt.SbtAccess.getTerminalWidth
val termWidth = SbtAccess.getTerminalWidth
if (termWidth > 20) termWidth - 8
else 80 // ignore termWidth
}

View File

@ -14,11 +14,13 @@
* limitations under the License.
*/
package sbt
package sbt.dependencygraph
import sbt.Defaults
/** Accessors to private[sbt] symbols. */
object SbtAccess {
val unmanagedScalaInstanceOnly = Defaults.unmanagedScalaInstanceOnly
def getTerminalWidth: Int = JLine.usingTerminal(_.getWidth)
def getTerminalWidth: Int = sbt.internal.util.JLine.usingTerminal(_.getWidth)
}

View File

@ -1 +0,0 @@
sbt.version=0.13.6

View File

@ -5,7 +5,9 @@ libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % "1.0.7"
)
TaskKey[Unit]("check") <<= (ivyReport in Test, asciiTree in Test) map { (report, graph) =>
TaskKey[Unit]("check") := {
val report = (ivyReport in Test).value
val graph = (asciiTree in Test).value
def sanitize(str: String): String = str.split('\n').drop(1).map(_.trim).mkString("\n")
val expectedGraph =
"""default:default-e95e05_2.9.2:0.1-SNAPSHOT [S]

View File

@ -6,14 +6,17 @@ libraryDependencies ++= Seq(
"com.codahale" % "jerkson_2.9.1" % "0.5.0"
)
TaskKey[Unit]("check") <<= (ivyReport in Test, asciiTree in Test) map { (report, graph) =>
TaskKey[Unit]("check") := {
val report = (ivyReport in Test).value
val graph = (asciiTree in Test).value
def sanitize(str: String): String = str.split('\n').drop(1).map(_.trim).mkString("\n")
val expectedGraph =
"""default:default-dbc48d_2.9.2:0.1-SNAPSHOT [S]
| +-com.codahale:jerkson_2.9.1:0.5.0 [S]
| +-org.codehaus.jackson:jackson-core-asl:1.9.13
| +-org.codehaus.jackson:jackson-mapper-asl:1.9.13
| +-org.codehaus.jackson:jackson-core-asl:1.9.13
| +-org.codehaus.jackson:jackson-core-asl:1.9.11
| +-org.codehaus.jackson:jackson-mapper-asl:1.9.11
| +-org.codehaus.jackson:jackson-core-asl:1.9.11
| """.stripMargin
IO.writeLines(file("/tmp/blib"), sanitize(graph).split("\n"))
IO.writeLines(file("/tmp/blub"), sanitize(expectedGraph).split("\n"))

View File

@ -1 +1 @@
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.3-SNAPSHOT")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version"))

View File

@ -3,7 +3,10 @@ scalaVersion := "2.9.2"
libraryDependencies +=
"at.blub" % "blib" % "1.2.3" % "test"
TaskKey[Unit]("check") <<= (ivyReport in Test, asciiTree in Test) map { (report, graph) =>
TaskKey[Unit]("check") := {
val report = (ivyReport in Test).value
val graph = (asciiTree in Test).value
def sanitize(str: String): String = str.split('\n').drop(1).mkString("\n")
val expectedGraph =
"""default:default-91180e_2.9.2:0.1-SNAPSHOT

View File

@ -0,0 +1,65 @@
import collection.mutable.ListBuffer
import net.virtualvoid.sbt.graph.DependencyGraphKeys.dependencyDot
import scala.collection.mutable.ListBuffer
def defaultSettings =
Seq(scalaVersion := "2.9.2")
lazy val justATransiviteDependencyEndpointProject =
Project("just-a-transitive-dependency-endpoint", file("a"))
.settings(defaultSettings: _*)
lazy val justATransitiveDependencyProject =
Project("just-a-transitive-dependency", file("b"))
.settings(defaultSettings: _*)
.dependsOn(justATransiviteDependencyEndpointProject)
lazy val justADependencyProject =
Project("just-a-dependency", file("c"))
.settings(defaultSettings: _*)
lazy val test_project =
Project("test-dot-file-generation", file("d"))
.settings(defaultSettings: _*)
.settings(
TaskKey[Unit]("check") := {
val dotFile = (dependencyDot in Compile).value
val expectedGraph =
"""digraph "dependency-graph" {
| graph[rankdir="LR"]
| edge [
| arrowtail="none"
| ]
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT"[label=<test-dot-file-generation<BR/><B>test-dot-file-generation_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"[label=<just-a-transitive-dependency<BR/><B>just-a-transitive-dependency_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT"[label=<just-a-transitive-dependency-endpoint<BR/><B>just-a-transitive-dependency-endpoint_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"[label=<just-a-dependency<BR/><B>just-a-dependency_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"
| "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT"
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"
|}
""".stripMargin
val graph : String = scala.io.Source.fromFile(dotFile.getAbsolutePath).mkString
val errors = compareByLine(graph, expectedGraph)
require(errors.isEmpty , errors.mkString("\n"))
()
}
)
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
def compareByLine(got : String, expected : String) : Seq[String] = {
val errors = ListBuffer[String]()
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach { case((got_line : String, expected_line : String), i : Int) =>
if(got_line != expected_line) {
errors.append(
"""not matching lines at line %s
|expected: %s
|got: %s
|""".stripMargin.format(i,expected_line, got_line))
}
}
errors
}

View File

@ -1,68 +0,0 @@
import collection.mutable.ListBuffer
import sbt._
import sbt.Keys._
import net.virtualvoid.sbt.graph.DependencyGraphKeys._
object Build extends sbt.Build {
def defaultSettings =
Seq(scalaVersion := "2.9.2")
lazy val justATransiviteDependencyEndpointProject =
Project("just-a-transitive-dependency-endpoint", file("a"))
.settings(defaultSettings: _*)
lazy val justATransitiveDependencyProject =
Project("just-a-transitive-dependency", file("b"))
.settings(defaultSettings: _*)
.dependsOn(justATransiviteDependencyEndpointProject)
lazy val justADependencyProject =
Project("just-a-dependency", file("c"))
.settings(defaultSettings: _*)
lazy val test_project =
Project("test-dot-file-generation", file("d"))
.settings(defaultSettings: _*)
.settings(
TaskKey[Unit]("check") <<= (dependencyDot in Compile) map { (dotFile) =>
val expectedGraph =
"""digraph "dependency-graph" {
| graph[rankdir="LR"]
| edge [
| arrowtail="none"
| ]
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT"[label=<test-dot-file-generation<BR/><B>test-dot-file-generation_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"[label=<just-a-transitive-dependency<BR/><B>just-a-transitive-dependency_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT"[label=<just-a-transitive-dependency-endpoint<BR/><B>just-a-transitive-dependency-endpoint_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"[label=<just-a-dependency<BR/><B>just-a-dependency_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"
| "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT"
| "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"
|}
""".stripMargin
val graph : String = scala.io.Source.fromFile(dotFile.getAbsolutePath).mkString
val errors = compareByLine(graph, expectedGraph)
require(errors.isEmpty , errors.mkString("\n"))
()
}
)
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
def compareByLine(got : String, expected : String) : Seq[String] = {
val errors = ListBuffer[String]()
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach { case((got_line : String, expected_line : String), i : Int) =>
if(got_line != expected_line) {
errors.append(
"""not matching lines at line %s
|expected: %s
|got: %s
|""".stripMargin.format(i,expected_line, got_line))
}
}
errors
}
}