summaryrefslogtreecommitdiff
path: root/scalalib/src/Dep.scala
blob: a6c77bfcf8ea536f6bdc94f670e34db9f1763070 (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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.value + 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.map{case (k, v) => (coursier.Organization(k), coursier.ModuleName(v))}
    )
  )
  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 = coursier.ModuleName(artifactName(binaryVersion, fullVersion, platformSuffix))
      )
    )
  def withConfiguration(configuration: String): Dep = copy(
    dep = dep.copy(configuration = coursier.core.Configuration(configuration))
  )
  def optional(optional: Boolean = true): Dep = copy(
    dep = dep.copy(optional = optional)
  )

  /**
    * 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 = coursier.core.Configuration("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 = coursier.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(coursier.Organization(org), coursier.ModuleName(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)
}