I made an sbt plugin and you should use it
- 
	
what it does
 - 
	
how it works
 - 
	
how to write an sbt plugin
 
sbt-explicit-dependencies
Helps you find:
- libraries that you depend on accidentally
 - dependencies that you don't need
 
Example
Undeclared dependencies
// in your build.sbt ...
libraryDependencies +=
  "org.typelevel" %% "cats-effect" % "1.0.0"// somewhere in your src/main/scala ...
import cats.effect.IO
import cats.data.NonEmptyList
val foo: IO[Unit] = IO {
  println(NonEmptyList.of(1, 2, 3))
}Example
Unused dependencies
// in your build.sbt ...
libraryDependencies +=
  "org.scalatest" %% "scalatest" % "3.0.5"demo
How it works
declared
actual
undeclared compile dependencies
How it works
declared
actual
unused compile dependencies
Declared compile deps
- 
	
libraryDependencies
 - filter out test deps, compiler plugins, etc.
 
Actual compile deps
How do we work out what libraries our project actually needs in order to compile?
Actual compile deps
First idea: bytecode analysis + jar traversal
import cats.data._
object Foo {
  val valid = Validated.valid("hello")
}
Constant pool: ... #23 = Utf8 cats/data/Validated$ #24 = Class #23 // cats/data/Validated$ ...
Actual compile deps
First idea: bytecode analysis + jar traversal
- compile the project
 - extract all class references from classfiles
 - extract list of classfiles from each library on classpath
 - cross-reference 2. and 3.
 
Actual compile deps
First idea: bytecode analysis + jar traversal
- compile the project
 - extract all class references from classfiles
 - extract list of classfiles from each library on classpath
 - cross-reference 2. and 3.
 
TERRIBLE IDEA
Actual compile deps
sbt:example> inspect compile
[info] Task: xsbti.compile.CompileAnalysis
[info] Description:
[info]  Compiles sources.
compile analysis? 🤔
Actual compile deps
printActualDeps := {
  compile.in(Compile).value
    .asInstanceOf[sbt.internal.inc.Analysis]
    .relations
    .allLibraryDeps
    .foreach(println)
}sbt:example> printActualDeps /Users/chris/.ivy2/cache/org.typelevel/cats-effect_2.12/jars/cats-effect_2.12-0.10.1.jar /Users/chris/.ivy2/cache/org.typelevel/cats-core_2.12/jars/cats-core_2.12-1.2.0.jar /Users/chris/.ivy2/cache/com.chuusai/shapeless_2.12/bundles/shapeless_2.12-2.3.3.jar /Users/chris/.sbt/boot/scala-2.12.6/lib/scala-library.jar /Users/chris/.ivy2/cache/org.http4s/http4s-blaze-server_2.12/jars/http4s-blaze-server_2.12-0.18.16.jar /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/rt.jar /Users/chris/.ivy2/cache/com.google.guava/guava/bundles/guava-26.0-jre.jar
Writing an sbt plugin
- Hello world
 - Testing
 - Publishing
 - Cross-building
 
sbt-hello-world
- Make a normal sbt project
	
- build.sbt
 - project/build.properties
 - src/main/scala
 
 - Enable the "SbtPlugin" sbt plugin
 
// no need to add anything to project/plugins.sbt 
enablePlugins(SbtPlugin)
scalaVersion := "2.12.8"
organization := "chris"
libraryDependencies ++= Seq(
  // whatever libraries you need ...
)
// src/main/scala/hello/HelloPlugin.scala
package hello
import sbt._
object HelloPlugin extends AutoPlugin {
  object autoImport {
    val sayHello = taskKey[Unit]("say hello")
  }
  // if you want your plugin to be enable automatically
  override def trigger = allRequirements
  override def projectSettings = Seq(
    autoImport.sayHello := {
      println("Hello world!")
    }
  )
}
Publish your plugin locally:
Add it to another sbt project:
$ sbt publishLocal
// project/plugins.sbt
addSbtPlugin("chris" % "sbt-hello-world" % "0.1.0-SNAPSHOT")And try it out:
sbt:sbt-example> sayHello Hello world!
Testing your plugin
- Unit tests
	
- ScalaTest or whatever
 - Separate pure logic from sbt-specific stuff
 
 - Integration tests
 
Publishing your plugin
... is really easy!
// project/plugins.sbt
addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.4")
addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.9")// build.sbt
organization := "com.foo.bar"
description := "my amazing plugin"
licenses += ("Apache-2.0", url("..."))
publishMavenStyle := false
bintrayRepository := "my-sbt-plugins"
bintrayOrganization in bintray := NonePublishing your plugin
- Create a Bintray repo
 - Publish your plugin to it (sbt release)
 - Get your plugin added to the community repo
 
Cross-building
sbt 0.13.x and 1.x are very similar,
but lots of classes got moved around
Cross-building
Type aliases in sbt version-specific package objects
package object explicitdeps {
  type Logger = sbt.util.Logger
  type ModuleID = sbt.librarymanagement.ModuleID
  type Binary = sbt.librarymanagement.Binary
  type Analysis = sbt.internal.inc.Analysis
  ...
}
package object explicitdeps {
  type Logger = sbt.Logger
  type ModuleID = sbt.ModuleID
  type Binary = sbt.CrossVersion.Binary
  type Analysis = sbt.inc.Analysis
  ...
}
src/main/scala-sbt-1.0/...
src/main/scala-sbt-0.13/...
everything else goes
in src/main/scala
Cross-building
Shared code must be valid in both 2.12.x and 2.10.x
package object explicitdeps {
  ...
  implicit class NodeSeqOps(nodeSeq: scala.xml.NodeSeq) {
    def \@(attributeName: String): String = 
      (nodeSeq \ ("@" + attributeName)).text
  }
}
src/main/scala-sbt-0.13/...
Add shims in version-specific package object:
Conclusion
- use my plugin
 - and build your own!
 

LambdAle CFP opens soon!
sbt-explicit-dependencies
By Chris Birchall
sbt-explicit-dependencies
- 2,386