Last active
September 24, 2015 18:16
-
-
Save japgolly/43cd72758e322e327767 to your computer and use it in GitHub Desktop.
SBT JVM/JS DependencyLib
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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