Skip to content

Commit

Permalink
Detect colliding cross module values (#2984)
Browse files Browse the repository at this point in the history
Also add an implicit `ToSegements[os.SubPath]` to accept `os.SubPath` as
values in cross modules.

Fix #2835

Pull request: #2984
  • Loading branch information
lefou authored Jan 23, 2024
1 parent 8902251 commit 5793eee
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 23 deletions.
7 changes: 7 additions & 0 deletions integration/failure/cross-collisions/repo/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import mill._

object foo extends Cross[Foo](
(Seq("a", "b"), Seq("c")),
(Seq("a"), Seq("b", "c"))
)
trait Foo extends Cross.Module2[Seq[String], Seq[String]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package mill.integration

import utest._

object CrossCollisionsTests extends IntegrationTestSuite {
val tests: Tests = Tests {
initWorkspace()

test("detect-collision") {
val res = evalStdout("resolve", "foo._")
assert(!res.isSuccess)
assert(
res.err.contains(
"Cross module millbuild.build#foo contains colliding cross values: List(List(a, b), List(c)) and List(List(a), List(b, c))"
)
)
}
}
}
8 changes: 6 additions & 2 deletions main/api/src/mill/api/MillException.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@ package mill.api
*/
class MillException(msg: String) extends Exception(msg)

class BuildScriptException(msg: String)
extends MillException("Build script contains errors:\n" + msg)
class BuildScriptException(msg: String, script: Option[String])
extends MillException(
script.map(_ + ": ").getOrElse("") + "Build script contains errors:\n" + msg
) {
def this(msg: String) = this(msg, None)
}
57 changes: 36 additions & 21 deletions main/define/src/mill/define/Cross.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package mill.define

import mill.api.Lazy
import mill.api.{BuildScriptException, Lazy}

import language.experimental.macros
import scala.collection.mutable
import scala.reflect.ClassTag
import scala.reflect.macros.blackbox

Expand Down Expand Up @@ -111,6 +112,7 @@ object Cross {
implicit object ShortToPathSegment extends ToSegments[Short](v => List(v.toString))
implicit object ByteToPathSegment extends ToSegments[Byte](v => List(v.toString))
implicit object BooleanToPathSegment extends ToSegments[Boolean](v => List(v.toString))
implicit object SubPathToPathSegment extends ToSegments[os.SubPath](v => v.segments.toList)
implicit def SeqToPathSegment[T: ToSegments]: ToSegments[Seq[T]] = new ToSegments[Seq[T]](
_.flatMap(implicitly[ToSegments[T]].convert).toList
)
Expand Down Expand Up @@ -286,28 +288,41 @@ class Cross[M <: Cross.Module[_]](factories: Cross.Factory[M]*)(implicit
def cls: Class[_]
}

val items: List[Item] = for {
factory <- factories.toList
(crossSegments0, (crossValues0, (cls0, make))) <-
factory.crossSegmentsList.zip(factory.crossValuesListLists.zip(factory.makeList))
} yield {
val relPath = ctx.segment.pathSegments
val module0 = new Lazy(() =>
make(
ctx
.withSegments(ctx.segments ++ Seq(ctx.segment))
.withMillSourcePath(ctx.millSourcePath / relPath)
.withSegment(Segment.Cross(crossSegments0))
.withCrossValues(factories.flatMap(_.crossValuesRaw))
.withEnclosingModule(this)
val items: List[Item] = {
val seen = mutable.Map[Seq[String], Seq[Any]]()
for {
factory <- factories.toList
(crossSegments0, (crossValues0, (cls0, make))) <-
factory.crossSegmentsList.zip(factory.crossValuesListLists.zip(factory.makeList))
} yield {
seen.get(crossSegments0) match {
case None => // no collision
case Some(other) => // collision
throw new BuildScriptException(
s"Cross module ${ctx.enclosing} contains colliding cross values: ${other} and ${crossValues0}",
Option(ctx.fileName).filter(_.nonEmpty)
)
}
val relPath = ctx.segment.pathSegments
val module0 = new Lazy(() =>
make(
ctx
.withSegments(ctx.segments ++ Seq(ctx.segment))
.withMillSourcePath(ctx.millSourcePath / relPath)
.withSegment(Segment.Cross(crossSegments0))
.withCrossValues(factories.flatMap(_.crossValuesRaw))
.withEnclosingModule(this)
)
)
)

new Item {
def crossValues = crossValues0.toList
def crossSegments = crossSegments0.toList
def module = module0
def cls = cls0
val item = new Item {
def crossValues = crossValues0.toList
def crossSegments = crossSegments0.toList
def module = module0
def cls = cls0
}
seen.update(crossSegments0, crossValues0)
item
}
}

Expand Down

0 comments on commit 5793eee

Please sign in to comment.