Skip to content

Commit

Permalink
Add support for Dotty projects (#397)
Browse files Browse the repository at this point in the history
* Abstract over the scala compiler organization

* Support using a locally published compiler

Publishing locally with sbt means publishing ivy-style, which uses
a different naming convention than maven, we now handle both cases.

* Add minimal support for Dotty projects

* Rewrite scalalib.Dep, introduce scalalib.CrossVersion

Instead of Dep being a trait with three cases (Java/Scala/Point), it is
now a case class where the cross field is an instance of the
CrossVersion trait which has three cases (Constant/Binary/Full). This is
more versatile since it allows for non-empty constant suffixes which
will be used to implement withDottyCompat in the next commit. It's
also a cleaner separation of concerns. We also deduplicate various
pieces of codes that computed the artifact name: this is now always handled in
Dep and CrossVersion.

* Add simple way to use Scala 2 deps in a Dotty project

This is similar to the withDottyCompat method in the sbt-dotty plugin.

* Turn off the Dotty test on Java >= 9
  • Loading branch information
smarter authored and lihaoyi committed Aug 5, 2018
1 parent 66c133a commit b982496
Show file tree
Hide file tree
Showing 14 changed files with 250 additions and 162 deletions.
2 changes: 1 addition & 1 deletion docs/pages/2 - Configuring Mill.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ object foo extends ScalaModule {
ivy"com.lihaoyi::upickle:0.5.1",
ivy"com.lihaoyi::pprint:0.5.2",
ivy"com.lihaoyi::fansi:0.2.4",
ivy"org.scala-lang:scala-reflect:${scalaVersion()}"
ivy"${scalaOrganization()}:scala-reflect:${scalaVersion()}"
)
}
```
Expand Down
2 changes: 1 addition & 1 deletion integration/test/resources/acyclic/build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class AcyclicModule(val crossScalaVersion: String) extends CrossSbtModule with P
)

def ivyDeps = Agg(
ivy"org.scala-lang:scala-compiler:${scalaVersion()}"
ivy"${scalaOrganization()}:scala-compiler:${scalaVersion()}"
)
object test extends Tests{
def forkWorkingDir = ammonite.ops.pwd / 'target / 'workspace / 'acyclic
Expand Down
8 changes: 4 additions & 4 deletions integration/test/resources/ammonite/build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class TerminalModule(val crossScalaVersion: String) extends AmmModule{
ivy"com.lihaoyi::fansi:0.2.4"
)
def compileIvyDeps = Agg(
ivy"org.scala-lang:scala-reflect:$crossScalaVersion"
ivy"${scalaOrganization()}:scala-reflect:$crossScalaVersion"
)

object test extends Tests
Expand All @@ -49,7 +49,7 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){
ivy"com.lihaoyi::fansi:0.2.4"
)
def compileIvyDeps = Agg(
ivy"org.scala-lang:scala-reflect:$crossScalaVersion"
ivy"${scalaOrganization()}:scala-reflect:$crossScalaVersion"
)

}
Expand All @@ -75,8 +75,8 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){
class InterpModule(val crossScalaVersion: String) extends AmmModule{
def moduleDeps = Seq(ops(), amm.util(), amm.runtime())
def ivyDeps = Agg(
ivy"org.scala-lang:scala-compiler:$crossScalaVersion",
ivy"org.scala-lang:scala-reflect:$crossScalaVersion",
ivy"${scalaOrganization()}:scala-compiler:$crossScalaVersion",
ivy"${scalaOrganization()}:scala-reflect:$crossScalaVersion",
ivy"com.lihaoyi::scalaparse:1.0.0",
ivy"org.javassist:javassist:3.21.0-GA"
)
Expand Down
4 changes: 2 additions & 2 deletions integration/test/resources/play-json/build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ abstract class PlayJson(val platformSegment: String) extends PlayJsonModule {
)

def ivyDeps = Agg(
ivy"org.scala-lang:scala-reflect:${scalaVersion()}",
ivy"${scalaOrganization()}:scala-reflect:${scalaVersion()}",
ivy"org.typelevel::macro-compat::1.1.1"
)

private val macroParadise = ivy"org.scalamacros:::paradise:2.1.0"

def compileIvyDeps = Agg(
macroParadise,
ivy"org.scala-lang:scala-compiler:${scalaVersion()}"
ivy"${scalaOrganization()}:scala-compiler:${scalaVersion()}"
)

def scalacPluginIvyDeps = Agg(macroParadise)
Expand Down
4 changes: 2 additions & 2 deletions integration/test/resources/upickle/build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ trait UpickleModule extends CrossSbtModule with PublishModule{
)
def compileIvyDeps = Agg(
ivy"com.lihaoyi::acyclic:0.1.5",
ivy"org.scala-lang:scala-reflect:${scalaVersion()}",
ivy"org.scala-lang:scala-compiler:${scalaVersion()}"
ivy"${scalaOrganization()}:scala-reflect:${scalaVersion()}",
ivy"${scalaOrganization()}:scala-compiler:${scalaVersion()}"
)
def ivyDeps = Agg(
ivy"com.lihaoyi::sourcecode::0.1.3"
Expand Down
1 change: 1 addition & 0 deletions scalajslib/src/mill/scalajslib/ScalaJSModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>

trait Tests extends TestScalaJSModule {
override def scalaWorker = outer.scalaWorker
override def scalaOrganization = outer.scalaOrganization()
override def scalaVersion = outer.scalaVersion()
override def scalaJSVersion = outer.scalaJSVersion()
override def moduleDeps = Seq(outer)
Expand Down
148 changes: 94 additions & 54 deletions scalalib/src/mill/scalalib/Dep.scala
Original file line number Diff line number Diff line change
@@ -1,32 +1,60 @@
package mill.scalalib
import mill.util.JsonFormatters._
import upickle.default.{macroRW, ReadWriter => RW}
sealed trait Dep {
def configure(attributes: coursier.Attributes): Dep
def force: Boolean
def forceVersion(): Dep = this match {
case dep : Dep.Java => dep.copy(force = true)
case dep : Dep.Scala => dep.copy(force = true)
case dep : Dep.Point => dep.copy(force = true)
}
def exclude(exclusions: (String, String)*): Dep = this match {
case dep : Dep.Java => dep.copy(dep = dep.dep.copy(exclusions = dep.dep.exclusions ++ exclusions))
case dep : Dep.Scala => dep.copy(dep = dep.dep.copy(exclusions = dep.dep.exclusions ++ exclusions))
case dep : Dep.Point => dep.copy(dep = dep.dep.copy(exclusions = dep.dep.exclusions ++ exclusions))

import CrossVersion._

case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
import Dep.isDotty

def artifactName(binaryVersion: String, fullVersion: String, platformSuffix: String) = {
val suffix = cross.suffixString(binaryVersion, fullVersion, platformSuffix)
dep.module.name + suffix
}
def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes))
def forceVersion(): Dep = copy(force = true)
def exclude(exclusions: (String, String)*) = copy(dep = dep.copy(exclusions = dep.exclusions ++ exclusions))
def excludeOrg(organizations: String*): Dep = exclude(organizations.map(_ -> "*"): _*)
def excludeName(names: String*): Dep = exclude(names.map("*" -> _): _*)
def withConfiguration(configuration: String): Dep = this match {
case dep : Dep.Java => dep.copy(dep = dep.dep.copy(configuration = configuration))
case dep : Dep.Scala => dep.copy(dep = dep.dep.copy(configuration = configuration))
case dep : Dep.Point => dep.copy(dep = dep.dep.copy(configuration = configuration))
}
def toDependency(binaryVersion: String, fullVersion: String, platformSuffix: String) =
dep.copy(module = dep.module.copy(name = artifactName(binaryVersion, fullVersion, platformSuffix)))
def withConfiguration(configuration: String): Dep = copy(dep = dep.copy(configuration = configuration))

/**
* If scalaVersion is a Dotty version, replace the cross-version suffix
* by the Scala 2.x version that the Dotty version is retro-compatible with,
* otherwise do nothing.
*
* This setting is useful when your build contains dependencies that have only
* been published with Scala 2.x, if you have:
* {{{
* def ivyDeps = Agg(ivy"a::b:c")
* }}}
* you can replace it by:
* {{{
* def ivyDeps = Agg(ivy"a::b:c".withDottyCompat(scalaVersion()))
* }}}
* This will have no effect when compiling with Scala 2.x, but when compiling
* with Dotty this will change the cross-version to a Scala 2.x one. This
* works because Dotty is currently retro-compatible with Scala 2.x.
*/
def withDottyCompat(scalaVersion: String): Dep =
cross match {
case cross: Binary if isDotty(scalaVersion) =>
copy(cross = Constant(value = "_2.12", platformed = cross.platformed))
case _ =>
this
}
}
object Dep{

object Dep {

val DefaultConfiguration = "default(compile)"

implicit def parse(signature: String) = {
def isDotty(scalaVersion: String) =
scalaVersion.startsWith("0.")

implicit def parse(signature: String): Dep = {
val parts = signature.split(';')
val module = parts.head
val attributes = parts.tail.foldLeft(coursier.Attributes()) { (as, s) =>
Expand All @@ -37,48 +65,60 @@ object Dep{
}
}
(module.split(':') match {
case Array(a, b, c) => Dep.Java(a, b, c, cross = false, force = false)
case Array(a, b, "", c) => Dep.Java(a, b, c, cross = true, force = false)
case Array(a, "", b, c) => Dep.Scala(a, b, c, cross = false, force = false)
case Array(a, "", b, "", c) => Dep.Scala(a, b, c, cross = true, force = false)
case Array(a, "", "", b, c) => Dep.Point(a, b, c, cross = false, force = false)
case Array(a, "", "", b, "", c) => Dep.Point(a, b, c, cross = true, force = false)
case Array(a, b, c) => Dep(a, b, c, cross = empty(platformed = false))
case Array(a, b, "", c) => Dep(a, b, c, cross = empty(platformed = true))
case Array(a, "", b, c) => Dep(a, b, c, cross = Binary(platformed = false))
case Array(a, "", b, "", c) => Dep(a, b, c, cross = Binary(platformed = true))
case Array(a, "", "", b, c) => Dep(a, b, c, cross = Full(platformed = false))
case Array(a, "", "", b, "", c) => Dep(a, b, c, cross = Full(platformed = true))
case _ => throw new Exception(s"Unable to parse signature: [$signature]")
}).configure(attributes = attributes)
}
def apply(org: String, name: String, version: String, cross: Boolean): Dep = {
this(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross)
}
case class Java(dep: coursier.Dependency, cross: Boolean, force: Boolean) extends Dep {
def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes))
def apply(org: String, name: String, version: String, cross: CrossVersion, force: Boolean = false): Dep = {
apply(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force)
}
object Java{
implicit def rw: RW[Java] = macroRW
def apply(org: String, name: String, version: String, cross: Boolean, force: Boolean): Dep = {
Java(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force)
implicit def rw: RW[Dep] = macroRW
}

sealed trait CrossVersion {
/** If true, the cross-version suffix should start with a platform suffix if it exists */
def platformed: Boolean

def isBinary: Boolean =
this.isInstanceOf[Binary]
def isConstant: Boolean =
this.isInstanceOf[Constant]
def isFull: Boolean =
this.isInstanceOf[Full]

/** The string that should be appended to the module name to get the artifact name */
def suffixString(binaryVersion: String, fullVersion: String, platformSuffix: String): String = {
val firstSuffix = if (platformed) platformSuffix else ""
this match {
case cross: Constant =>
s"${firstSuffix}${cross.value}"
case cross: Binary =>
s"${firstSuffix}_${binaryVersion}"
case cross: Full =>
s"${firstSuffix}_${fullVersion}"
}
}
implicit def default(dep: coursier.Dependency): Dep = new Java(dep, false, false)
def apply(dep: coursier.Dependency, cross: Boolean) = Scala(dep, cross, false)
case class Scala(dep: coursier.Dependency, cross: Boolean, force: Boolean) extends Dep {
def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes))
}
object Scala{
implicit def rw: RW[Scala] = macroRW
def apply(org: String, name: String, version: String, cross: Boolean, force: Boolean): Dep = {
Scala(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force)
}
}
object CrossVersion {
case class Constant(value: String, platformed: Boolean) extends CrossVersion
object Constant {
implicit def rw: RW[Constant] = macroRW
}
case class Point(dep: coursier.Dependency, cross: Boolean, force: Boolean) extends Dep {
def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes))
case class Binary(platformed: Boolean) extends CrossVersion
object Binary {
implicit def rw: RW[Binary] = macroRW
}
object Point{
implicit def rw: RW[Point] = macroRW
def apply(org: String, name: String, version: String, cross: Boolean, force: Boolean): Dep = {
Point(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force)
}
case class Full(platformed: Boolean) extends CrossVersion
object Full {
implicit def rw: RW[Full] = macroRW
}
implicit def rw = RW.merge[Dep](
Java.rw, Scala.rw, Point.rw
)

def empty(platformed: Boolean) = Constant(value = "", platformed)

implicit def rw: RW[CrossVersion] = RW.merge(Constant.rw, Binary.rw, Full.rw)
}
71 changes: 28 additions & 43 deletions scalalib/src/mill/scalalib/Lib.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import javax.tools.ToolProvider
import ammonite.ops._
import ammonite.util.Util
import coursier.{Cache, Dependency, Fetch, Repository, Resolution}
import Dep.isDotty
import mill.Agg
import mill.eval.{PathRef, Result}
import mill.modules.Jvm
Expand Down Expand Up @@ -56,58 +57,37 @@ object Lib{

private val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r
private val MinorSnapshotVersion = raw"""(\d+)\.(\d+)\.([1-9]\d*)-SNAPSHOT""".r
private val DottyVersion = raw"""0\.(\d+)\.(\d+).*""".r

def scalaBinaryVersion(scalaVersion: String) = {
scalaVersion match {
case ReleaseVersion(major, minor, _) => s"$major.$minor"
case MinorSnapshotVersion(major, minor, _) => s"$major.$minor"
case DottyVersion(minor, _) => s"0.$minor"
case _ => scalaVersion
}
}

def grepJar(classPath: Agg[Path], s: String) = {
def grepJar(classPath: Agg[Path], name: String, version: String) = {
val mavenStylePath = s"$name-$version.jar"
val ivyStylePath = s"$version/$name.jar"

classPath
.find(_.toString.endsWith(s))
.getOrElse(throw new Exception("Cannot find " + s))
.toIO
.find(p => p.toString.endsWith(mavenStylePath) || p.toString.endsWith(ivyStylePath))
.getOrElse(throw new Exception(s"Cannot find $mavenStylePath or $ivyStylePath"))
}


def depToDependencyJava(dep: Dep, platformSuffix: String = ""): Dependency = {
dep match {
case Dep.Java(dep, cross, force) =>
dep.copy(
module = dep.module.copy(
name =
dep.module.name +
(if (!cross) "" else platformSuffix)
)
)
}
assert(dep.cross.isConstant, s"Not a Java dependency: $dep")
depToDependency(dep, "", platformSuffix)
}
def depToDependency(dep: Dep, scalaVersion: String, platformSuffix: String = ""): Dependency =
dep match {
case d: Dep.Java => depToDependencyJava(dep)
case Dep.Scala(dep, cross, force) =>
dep.copy(
module = dep.module.copy(
name =
dep.module.name +
(if (!cross) "" else platformSuffix) +
"_" + scalaBinaryVersion(scalaVersion)
)
)
case Dep.Point(dep, cross, force) =>
dep.copy(
module = dep.module.copy(
name =
dep.module.name +
(if (!cross) "" else platformSuffix) +
"_" + scalaVersion
)
)
}

def depToDependency(dep: Dep, scalaVersion: String, platformSuffix: String = ""): Dependency =
dep.toDependency(
binaryVersion = scalaBinaryVersion(scalaVersion),
fullVersion = scalaVersion,
platformSuffix = platformSuffix
)

def resolveDependenciesMetadata(repositories: Seq[Repository],
depToDependency: Dep => coursier.Dependency,
Expand Down Expand Up @@ -142,12 +122,17 @@ object Lib{
mapDependencies
)
}
def scalaCompilerIvyDeps(scalaVersion: String) = Agg[Dep](
ivy"org.scala-lang:scala-compiler:$scalaVersion".forceVersion(),
ivy"org.scala-lang:scala-reflect:$scalaVersion".forceVersion()
)
def scalaRuntimeIvyDeps(scalaVersion: String) = Agg[Dep](
ivy"org.scala-lang:scala-library:$scalaVersion".forceVersion()
def scalaCompilerIvyDeps(scalaOrganization: String, scalaVersion: String) =
if (isDotty(scalaVersion))
Agg(ivy"$scalaOrganization::dotty-compiler:$scalaVersion".forceVersion())
else
Agg(
ivy"$scalaOrganization:scala-compiler:$scalaVersion".forceVersion(),
ivy"$scalaOrganization:scala-reflect:$scalaVersion".forceVersion()
)

def scalaRuntimeIvyDeps(scalaOrganization: String, scalaVersion: String) = Agg[Dep](
ivy"$scalaOrganization:scala-library:$scalaVersion".forceVersion()
)

def listClassFiles(base: Path): Iterator[String] = {
Expand Down
Loading

0 comments on commit b982496

Please sign in to comment.