Skip to content

Instantly share code, notes, and snippets.

@japgolly
Last active September 24, 2015 18:16
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save japgolly/43cd72758e322e327767 to your computer and use it in GitHub Desktop.
Save japgolly/43cd72758e322e327767 to your computer and use it in GitHub Desktop.
SBT JVM/JS DependencyLib
object Dependencies {
object Scala {
private val mm = scalaItself(version)
def version = "2.11.7"
val compiler = mm("scala-compiler")
val library = mm("scala-library")
val reflect = mm("scala-reflect")
val macroDef = reflect ++ (compiler % "provided")
}
// JVM dependency family with JS fork
object Scalaz {
private val mm = MultiModule.jvmAndJsFork("org.scalaz", "7.1.3")("com.github.japgolly.fork.scalaz")
val core = mm("scalaz-core")
val effect = mm("scalaz-effect") ++ core
val concurrent = mm("scalaz-concurrent") ++ effect
}
// JS-only dependency family
object React {
private val mm = MultiModule.js("com.github.japgolly.scalajs-react", "0.9.1")
val core = mm("core")
val test = mm("test")
val scalaz = mm("ext-scalaz71") ++ Scalaz.effect
val monocle = mm("ext-monocle") ++ Monocle.core
val extra = mm("extra")
val most = core ++ scalaz ++ monocle ++ extra
}
// JVM-only dependency family
object SLF4J {
private val mm = MultiModule.java("org.slf4j", "1.7.12")
val api = mm("slf4j-api")
val jcl = mm("jcl-over-slf4j")
}
// JVM dependency with JS fork
val parboiled = jvmAndJsFork("org.parboiled", "parboiled", "2.1.0")("com.github.japgolly.fork.parboiled")
// JVM & JS cross-compiled dependencies
val boopickle = jvmAndJs("me.chrons", "boopickle", "1.1.0")
val μTest = jvmAndJs("com.lihaoyi", "utest", "0.3.1")
// JVM-only dependencies
val okHttp = jvmOnly("com.squareup.okhttp" % "okhttp" % "1.5.4")
val httpCore = jvmOnly("org.apache.httpcomponents" % "httpcore" % "4.3.2")
val javaMail = jvmOnly("com.sun.mail" % "javax.mail" % "1.5.2")
val jodaTime = jvmOnly("joda-time" % "joda-time" % "2.3") ++
jvmOnly("org.joda" % "joda-convert" % "1.2")
}
def crossProject(dir: String) =
CrossProject(dir + "-jvm", dir + "-js", file(dir), CrossType.Full).settings(name := dir)
lazy val webappBase =
crossProject("webapp-base")
.depsForBoth(
// Compilation will fail if a dependency used here isn't available for BOTH JVM & JS
μPickle ++ Monocle.macros ++ shapeless ++ Nyaya.core ++ parboiled ++ boopickle ++
testScope(μTest)
)
.depsForJvm(
// Compilation will fail if a dependency used here isn't available for JVM
SLF4J.api ++
providedScope(logback ++ jodaTime)
)
.configureBoth(useMacroParadise)
lazy val webappBaseJvm = webappBase.jvm
lazy val webappBaseJs = webappBase.js
import sbt._
import scala.languageFeature._
import org.scalajs.sbtplugin.ScalaJSPlugin
import ScalaJSPlugin.autoImport._
object DependencyLib {
sealed trait HasDialect
sealed trait HasJvm extends HasDialect
sealed trait HasJs extends HasDialect
type HasBoth = HasJvm with HasJs
sealed abstract class Dialect[D <: HasDialect](val name: String)
case object JVM extends Dialect[HasJvm]("jvm")
case object JS extends Dialect[HasJs] ("js")
type AnyDialect = Dialect[_ <: HasDialect]
object GlobalMod {
// This was a little hack I used to apply a modification to ALL dependencies
// (Usually to exclude a transitive dependency)
def apply[D <: HasDialect](d: Dep[D]): Dep[D] = d
}
case class ModDepScope(scope: String) extends AnyVal {
def apply[D <: HasDialect](d: Dep[D]): Dep[D] =
d % scope
}
/**
* Type class witnessing the least upper bound of a pair of types and providing conversions from each to their common
* supertype.
*
* @author Miles Sabin
*/
trait Lub[-A, -B, Out] extends Serializable {
def left(a : A): Out
def right(b : B): Out
}
object Lub {
implicit def lub[T] = new Lub[T, T, T] {
def left(a : T): T = a
def right(b : T): T = b
}
}
// -------------------------------------------------------------------------------------------------------------------
object Dep {
val empty = new Dep[Nothing](Map.empty)
def apply[D <: HasDialect](dialect: Dialect[D], m: ModuleID): Dep[D] =
GlobalMod(new Dep[D](empty.ids.updated(dialect, Seq(m))))
}
class Dep[D <: HasDialect] private[Dep](val ids: Map[AnyDialect, Seq[ModuleID]]) extends AnyVal {
def widen[E >: D <: HasDialect]: Dep[E] =
new Dep(ids)
def modAll(f: ModuleID => ModuleID): Dep[D] =
new Dep(Dep.empty.ids ++ ids.toStream.map(p => (p._1, p._2 map f)))
private def concat[R <: HasDialect](that: Dep[_ <: HasDialect]): Dep[R] =
new Dep(that.ids.foldLeft(ids) { case (q, (k, v)) =>
q.updated(k, q.get(k).fold(v)(_ ++ v))
})
def mergeDialects[E <: HasDialect](that: Dep[E]): Dep[D with E] =
concat[D with E](that)
def ++[E <: HasDialect, R <: HasDialect](that: Dep[E])(implicit lub: Lub[D, E, R]): Dep[R] =
concat[R](that)
def apply(d: AnyDialect): Seq[ModuleID] =
ids.getOrElse(d, Seq.empty)
def %(revision: String) = modAll(_ % revision)
def exclude(org: String, name: String) = modAll(_ exclude(org, name))
}
def jvmOnly(m: ModuleID): Dep[HasJvm] =
Dep(JVM, m)
def jsOnly(m: ModuleID): Dep[HasJs] =
Dep(JS, m)
def jvmAndJs(group: String, name: String, ver: String): Dep[HasBoth] =
Dep(JVM, group %% name % ver) mergeDialects Dep(JS, group %%%! name % ver)
def jvmAndJsFork(jvmGroup: String, name: String, ver: String)(jsGroup: String, jsVerSuffix: String = ""): Dep[HasBoth] =
Dep(JVM, jvmGroup %% name % ver) mergeDialects Dep(JS, jsGroup %%%! name % (ver + jsVerSuffix))
/*
// Test
val jvm : Dep[HasJvm] = ???
val js : Dep[HasJs] = ???
val both: Dep[HasBoth] = ???
val bb: Dep[HasBoth] = both ++ both
val bj: Dep[HasJvm] = jvm ++ both
val jb: Dep[HasJvm] = both ++ jvm
val jj: Dep[HasJvm] = jvm ++ jvm
val fail1 = jvm + js // should fail
*/
// -------------------------------------------------------------------------------------------------------------------
def scalaItself(ver: String): MultiModule[HasBoth] =
new MultiModule[HasBoth](n => {
val m = "org.scala-lang" % n % ver
Dep(JVM, m) mergeDialects Dep(JS, m)
})
object MultiModule {
def apply[D <: HasDialect](d: Dialect[D], f: String => ModuleID) =
new MultiModule[D](n => Dep[D](d, f(n)))
def java(group: String, ver: String): MultiModule[HasJvm] =
MultiModule(JVM, group % _ % ver)
def scala(group: String, ver: String): MultiModule[HasJvm] =
MultiModule(JVM, group %% _ % ver)
def js(group: String, ver: String): MultiModule[HasJs] =
MultiModule(JS, group %%%! _ % ver)
def jvmAndJs(group: String, ver: String): MultiModule[HasBoth] =
scala(group, ver) mergeDialects js(group, ver)
def jvmAndJsFork(jvmGroup: String, ver: String)(jsGroup: String, jsVerSuffix: String = ""): MultiModule[HasBoth] =
scala(jvmGroup, ver) mergeDialects js(jsGroup, ver + jsVerSuffix)
}
class MultiModule[D <: HasDialect](val f: String => Dep[D]) extends AnyVal {
def apply(name: String) = f(name)
def mergeDialects[E <: HasDialect](that: MultiModule[E]): MultiModule[D with E] =
new MultiModule[D with E](name => this(name).mergeDialects[E](that(name)))
}
}
object Fns {
implicit class CrossProjectExt(val p: CrossProject) extends AnyVal {
def configureBoth(fs: (Project => Project)*): CrossProject =
fs.foldLeft(p)((q,f) => q.jvmConfigure(f).jsConfigure(f))
def configureJvm(fs: (Project => Project)*): CrossProject =
fs.foldLeft(p)((q,f) => q.jvmConfigure(f))
def configureJs(fs: (Project => Project)*): CrossProject =
fs.foldLeft(p)((q,f) => q.jsConfigure(f))
def depsForBoth(deps: Dep[HasBoth]): CrossProject =
depsForJvm(deps.widen).depsForJs(deps.widen)
def depsForJvm(deps: Dep[HasJvm]): CrossProject =
p.jvmSettings(libraryDependencies ++= deps(JVM))
def depsForJs(deps: Dep[HasJs]): CrossProject =
p.jsSettings(libraryDependencies ++= deps(JS))
def aggregateJvm(refs: sbt.ProjectReference*): CrossProject =
p.jvmConfigure(_.aggregate(refs: _*))
def aggregateJs(refs: sbt.ProjectReference*): CrossProject =
p.jsConfigure(_.aggregate(refs: _*))
}
implicit class ProjectExt(val p: Project) extends AnyVal {
def deps(deps: Dep[HasJvm]): Project =
p.settings(libraryDependencies ++= deps(JVM))
def depsForJs(deps: Dep[HasJs]): Project =
p.settings(libraryDependencies ++= deps(JS))
}
def depScope(s: String): ModDepScope = ModDepScope(s)
def depScope(c: Configuration): ModDepScope = depScope(c.name)
def testScope = depScope("test")
def providedScope = depScope("provided")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment