Skip to content

Commit

Permalink
Make mill-build folder opt in via import $mill.build (#2527)
Browse files Browse the repository at this point in the history
Fixes #2461

For now it remains hard-coded as `mill-build/`, for simplicity. Given
the `mill-` prefix, it's unlikely to collide with user-defined folders.
Making it configurable would open another can of worms where the `import
$meta.foo` can now collide with an `object foo`, which is very
unintuitive.

For now, we just parse the script files one additional time in
`MillBuildBootstrap.scala`. This is a bit wasteful, but is probably fast
enough for now, and we're already pretty sloppy parsing everything twice
in `MillBuildRootModule#scriptSources` and
`MillBuildRootModule#parseBuildFiles`, so parsing things three times
isn't the end of the world. We can look into optimizing it in future if
necessary
  • Loading branch information
lihaoyi authored May 18, 2023
1 parent 7411b9a commit 099cc3e
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 22 deletions.
3 changes: 2 additions & 1 deletion example/misc/4-mill-build-folder/build.sc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import $meta._
import mill._, scalalib._
import scalatags.Text.all._

Expand All @@ -20,7 +21,7 @@ object foo extends RootModule with ScalaModule {
// file and it's `import $file` and `$ivy` are a shorthand syntax for defining
// a Mill `ScalaModule`, with sources and `ivyDeps` and so on, which is
// compiled and executed to perform your build. This module lives in
// `mill-build/`.
// `mill-build/`, and can be enabled via the `import $meta._` statement above.

/** See Also: mill-build/build.sc */

Expand Down
1 change: 1 addition & 0 deletions integration/failure/invalid-meta-module/repo/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import $meta._
1 change: 1 addition & 0 deletions integration/feature/editing/repo/build.sc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import $meta._
import mill._, scalalib._
import scalatags.Text.all._

Expand Down
1 change: 1 addition & 0 deletions integration/feature/editing/repo/mill-build/build.sc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import $meta._
import mill._, scalalib._

object millbuild extends MillBuildRootModule{
Expand Down
39 changes: 26 additions & 13 deletions integration/feature/editing/test/src/MultiLevelBuildTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ object MultiLevelBuildTests extends IntegrationTestSuite {
for (depth <- Range(0, n))
yield {
val path = wsRoot / "out" / Seq.fill(depth)("mill-build") / "mill-runner-state.json"
upickle.default.read[RunnerState.Frame.Logged](os.read(path)) -> path
if (os.exists(path)) upickle.default.read[RunnerState.Frame.Logged](os.read(path)) -> path
else RunnerState.Frame.Logged(Map(), Seq(), Seq(), Map(), None, Seq(), 0) -> path
}
}

Expand All @@ -69,14 +70,18 @@ object MultiLevelBuildTests extends IntegrationTestSuite {
}
}

def evalCheckErr(expected: String*) = {
def evalCheckErr(expectedSnippets: String*) = {
// Wipe out stale state files to make sure they don't get picked up when
// Mill aborts early and fails to generate a new one
os.walk(wsRoot / "out").filter(_.last == "mill-runner-state.json").foreach(os.remove(_))

val res = evalStdout("foo.run")
assert(res.isSuccess == false)
// Prepend a "\n" to allow callsites to use "\n" to test for start of
// line, even though the first line doesn't have a "\n" at the start
val err = "\n" + res.err
for (e <- expected) {
assert(err.contains(e))
for (expected <- expectedSnippets) {
assert(err.contains(expected))
}
}

Expand Down Expand Up @@ -189,17 +194,23 @@ object MultiLevelBuildTests extends IntegrationTestSuite {
"\n1 targets failed",
"\ngenerateScriptSources build.sc"
)
checkWatchedFiles(Nil, buildPaths, buildPaths2, buildPaths3)
checkChangedClassloaders(null, null, false, false)
checkWatchedFiles(Nil, buildPaths, Nil, Nil)
// When one of the meta-builds still has parse errors, all classloaders
// remain null, because none of the meta-builds can evaluate. Only once
// all of them parse successfully do we get a new set of classloaders for
// every level of the meta-build
checkChangedClassloaders(null, null, null, null)

fixParseError(wsRoot / "build.sc")
causeParseError(wsRoot / "mill-build" / "build.sc")
evalCheckErr(
"\n1 targets failed",
"\ngenerateScriptSources mill-build/build.sc"
)
checkWatchedFiles(Nil, Nil, buildPaths2, buildPaths3)
checkChangedClassloaders(null, null, null, false)
checkWatchedFiles(Nil, Nil, buildPaths2, Nil)
checkChangedClassloaders(null, null, null, null)

fixParseError(wsRoot / "mill-build" / "build.sc")
causeParseError(wsRoot / "mill-build" / "mill-build" / "build.sc")
evalCheckErr(
"\n1 targets failed",
Expand All @@ -209,25 +220,27 @@ object MultiLevelBuildTests extends IntegrationTestSuite {
checkChangedClassloaders(null, null, null, null)

fixParseError(wsRoot / "mill-build" / "mill-build" / "build.sc")
causeParseError(wsRoot / "mill-build" / "build.sc")
evalCheckErr(
"\n1 targets failed",
"\ngenerateScriptSources mill-build/build.sc"
)
checkWatchedFiles(Nil, Nil, buildPaths2, buildPaths3)
checkChangedClassloaders(null, null, null, true)
checkWatchedFiles(Nil, Nil, buildPaths2, Nil)
checkChangedClassloaders(null, null, null, null)

fixParseError(wsRoot / "mill-build" / "build.sc")
causeParseError(wsRoot / "build.sc")
evalCheckErr(
"\n1 targets failed",
"\ngenerateScriptSources build.sc"
)
checkWatchedFiles(Nil, buildPaths, buildPaths2, buildPaths3)
checkChangedClassloaders(null, null, true, false)
checkWatchedFiles(Nil, buildPaths, Nil, Nil)
checkChangedClassloaders(null, null, null, null)

fixParseError(wsRoot / "build.sc")
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>!")
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3)
checkChangedClassloaders(null, true, false, false)
checkChangedClassloaders(null, true, true, true)
}

test("compileErrorEdits") {
Expand Down
10 changes: 8 additions & 2 deletions runner/src/mill/runner/FileImportGraph.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ case class FileImportGraph(
repos: Seq[(String, os.Path)],
ivyDeps: Set[String],
importGraphEdges: Map[os.Path, Seq[os.Path]],
errors: Seq[String]
errors: Seq[String],
millImport: Boolean
)

/**
Expand All @@ -33,6 +34,7 @@ object FileImportGraph {
val seenRepo = mutable.ListBuffer.empty[(String, os.Path)]
val importGraphEdges = mutable.Map.empty[os.Path, Seq[os.Path]]
val errors = mutable.Buffer.empty[String]
var millImport = false

def walkScripts(s: os.Path): Unit = {
importGraphEdges(s) = Nil
Expand Down Expand Up @@ -84,6 +86,9 @@ object FileImportGraph {
case ImportTree(Seq(("$ivy", _), rest @ _*), mapping, start, end) =>
seenIvy.addAll(mapping.map(_._1))
(start, "_root_._", end)
case ImportTree(Seq(("$meta", _), rest @ _*), mapping, start, end) =>
millImport = true
(start, "_root_._", end)
case ImportTree(Seq(("$file", _), rest @ _*), mapping, start, end) =>
val nextPaths = mapping.map { case (lhs, rhs) => nextPathFor(s, rest.map(_._1) :+ lhs) }

Expand Down Expand Up @@ -113,7 +118,8 @@ object FileImportGraph {
seenRepo.toSeq,
seenIvy.toSet,
importGraphEdges.toMap,
errors.toSeq
errors.toSeq,
millImport
)
}

Expand Down
19 changes: 13 additions & 6 deletions runner/src/mill/runner/MillBuildBootstrap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,20 @@ class MillBuildBootstrap(
val prevOuterFrameOpt = prevRunnerState.frames.lift(depth - 1)

val nestedRunnerState =
if (!os.exists(recRoot(projectRoot, depth) / "build.sc")) {
if (depth == 0) {
RunnerState(None, Nil, Some("build.sc file not found. Are you in a Mill project folder?"))
} else {
if (depth == 0) {
if (os.exists(recRoot(projectRoot, depth) / "build.sc")) evaluateRec(depth + 1)
else {
val msg = "build.sc file not found. Are you in a Mill project folder?"
RunnerState(None, Nil, Some(msg))
}
} else {
val parsedScriptFiles = FileImportGraph.parseBuildFiles(
projectRoot,
recRoot(projectRoot, depth) / os.up
)

if (parsedScriptFiles.millImport) evaluateRec(depth + 1)
else {
val bootstrapModule =
new MillBuildRootModule.BootstrapModule(
projectRoot,
Expand All @@ -83,8 +92,6 @@ class MillBuildBootstrap(
)
RunnerState(Some(bootstrapModule), Nil, None)
}
} else {
evaluateRec(depth + 1)
}

val res = if (nestedRunnerState.errorOpt.isDefined) {
Expand Down

0 comments on commit 099cc3e

Please sign in to comment.