package mill.scalalib
import mill.util.JsonFormatters._
import upickle.default.{macroRW, ReadWriter => RW}
import CrossVersion._
case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
import mill.scalalib.api.Util.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 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 {
val DefaultConfiguration = "default(compile)"
implicit def parse(signature: String): Dep = {
val parts = signature.split(';')
val module = parts.head
val attributes = parts.tail.foldLeft(coursier.Attributes()) { (as, s) =>
s.split('=') match {
case Array("classifier", v) => as.copy(classifier = v)
case Array(k, v) => throw new Exception(s"Unrecognized attribute: [$s]")
case _ => throw new Exception(s"Unable to parse attribute specifier: [$s]")
}
}
(module.split(':') match {
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: CrossVersion, force: Boolean = false): Dep = {
apply(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}"
}
}
}
object CrossVersion {
case class Constant(value: String, platformed: Boolean) extends CrossVersion
object Constant {
implicit def rw: RW[Constant] = macroRW
}
case class Binary(platformed: Boolean) extends CrossVersion
object Binary {
implicit def rw: RW[Binary] = macroRW
}
case class Full(platformed: Boolean) extends CrossVersion
object Full {
implicit def rw: RW[Full] = macroRW
}
def empty(platformed: Boolean) = Constant(value = "", platformed)
implicit def rw: RW[CrossVersion] = RW.merge(Constant.rw, Binary.rw, Full.rw)
}