summaryrefslogtreecommitdiff
path: root/scalalib/src/Dep.scala
blob: 714fa21ebeea0915c650a3d0b63321d95dd0c9a6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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)
}