Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
898576c
.
lihaoyi Sep 9, 2025
ce7e821
.
lihaoyi Sep 9, 2025
08f0952
wip
lihaoyi Sep 9, 2025
557d535
Revert "Revert "Allow running standalone single-file scripts using Mi…
lihaoyi Sep 9, 2025
05c20d4
Merge branch 'main' into script-files
lihaoyi Sep 10, 2025
60b859b
merge
lihaoyi Sep 11, 2025
8c036f2
Merge branch 'more-single-file' into single-file-projects
lihaoyi Sep 11, 2025
78489a3
wip
lihaoyi Sep 11, 2025
0b0350d
wip
lihaoyi Sep 11, 2025
d955074
wip
lihaoyi Sep 11, 2025
333bb63
.
lihaoyi Sep 11, 2025
87dca38
.
lihaoyi Sep 11, 2025
5892515
.
lihaoyi Sep 11, 2025
32888ba
.
lihaoyi Sep 11, 2025
7bdeaf1
.
lihaoyi Sep 11, 2025
d949c9e
.
lihaoyi Sep 11, 2025
4a9bae2
.
lihaoyi Sep 11, 2025
b3ab4f0
merge
lihaoyi Sep 12, 2025
e534612
wip
lihaoyi Sep 12, 2025
e280d36
wip
lihaoyi Sep 12, 2025
70f1b15
wip
lihaoyi Sep 12, 2025
62fc20a
wip
lihaoyi Sep 12, 2025
eca5053
wip
lihaoyi Sep 12, 2025
fb36045
wip
lihaoyi Sep 12, 2025
c4bf0cb
fix mima
lihaoyi Sep 13, 2025
bfb7fb2
get 5-single-file example tests working
lihaoyi Sep 13, 2025
bc5f349
.
lihaoyi Sep 13, 2025
bf21977
.
lihaoyi Sep 13, 2025
a522dd1
wip
lihaoyi Sep 13, 2025
4a4b88f
wip
lihaoyi Sep 13, 2025
2f585ff
.
lihaoyi Sep 14, 2025
8ed4878
.
lihaoyi Sep 14, 2025
99f572c
.
lihaoyi Sep 14, 2025
41cebad
.
lihaoyi Sep 14, 2025
5b5af9e
.
lihaoyi Sep 14, 2025
9c69e45
.
lihaoyi Sep 14, 2025
9540285
.
lihaoyi Sep 14, 2025
f8e9154
.
lihaoyi Sep 14, 2025
56e4d6b
.
lihaoyi Sep 14, 2025
1c04524
.
lihaoyi Sep 14, 2025
919932d
.
lihaoyi Sep 14, 2025
51de952
.
lihaoyi Sep 14, 2025
d02378f
.
lihaoyi Sep 14, 2025
f379678
.
lihaoyi Sep 14, 2025
7e53d82
Merge branch 'main' into single-file-projects
lihaoyi Sep 15, 2025
bdc8659
.
lihaoyi Sep 15, 2025
8c26040
.
lihaoyi Sep 15, 2025
92bbcdd
.
lihaoyi Sep 15, 2025
ad7f69d
.
lihaoyi Sep 15, 2025
c8721d3
.
lihaoyi Sep 15, 2025
9f54d6a
.
lihaoyi Sep 15, 2025
37abb37
.
lihaoyi Sep 15, 2025
929f4cd
.
lihaoyi Sep 15, 2025
3973b4a
.
lihaoyi Sep 15, 2025
1d495f4
first pass at package.mill.yaml format
lihaoyi Sep 15, 2025
bc3ee97
.
lihaoyi Sep 15, 2025
1f6d460
wip
lihaoyi Sep 15, 2025
f2b00c3
wip
lihaoyi Sep 15, 2025
536acbd
wip
lihaoyi Sep 15, 2025
bde96f6
wip
lihaoyi Sep 15, 2025
f9a9d4d
.
lihaoyi Sep 17, 2025
007931c
.
lihaoyi Sep 17, 2025
9318671
.
lihaoyi Sep 17, 2025
4d01f41
.
lihaoyi Sep 17, 2025
58194db
.
lihaoyi Sep 17, 2025
04324ce
config module non script
lihaoyi Sep 17, 2025
f324cae
mixed module deps
lihaoyi Sep 17, 2025
ee9ba7e
3-custom-class
lihaoyi Sep 17, 2025
2d9d73c
simple/2-module-deps
lihaoyi Sep 17, 2025
3019eeb
fmt
lihaoyi Sep 17, 2025
9e484fc
fmt
lihaoyi Sep 17, 2025
2a00270
fmt
lihaoyi Sep 17, 2025
2e152ce
fmt
lihaoyi Sep 17, 2025
5e5e982
fmt
lihaoyi Sep 17, 2025
ab92c18
fmt
lihaoyi Sep 17, 2025
8713c01
fmt
lihaoyi Sep 17, 2025
447892d
fmt
lihaoyi Sep 17, 2025
cfd1192
fmt
lihaoyi Sep 17, 2025
0408bee
fmt
lihaoyi Sep 18, 2025
457e429
.
lihaoyi Sep 18, 2025
9a944b9
.
lihaoyi Sep 18, 2025
88f0dd0
.
lihaoyi Sep 18, 2025
fe1a549
.
lihaoyi Sep 18, 2025
1b5c265
.
lihaoyi Sep 18, 2025
167d73c
.
lihaoyi Sep 18, 2025
9f58fc7
.
lihaoyi Sep 18, 2025
5600a08
.
lihaoyi Sep 18, 2025
40293a0
.
lihaoyi Sep 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/test-dist-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

set -eux

EXAMPLE=example/scalalib/basic/6-realistic
EXAMPLE=example/scalalib/basic/7-realistic

rm -rf $EXAMPLE/out

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package mill.contrib.scoverage

import coursier.Repository
import mill._
import mill.api.{PathRef}
import mill.api.PathRef
import mill.api.BuildCtx
import mill.api.{Result}
import mill.contrib.scoverage.api.ScoverageReportWorkerApi2.ReportType
Expand Down
3 changes: 2 additions & 1 deletion core/api/src/mill/api/ExternalModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ abstract class ExternalModule(implicit
millFile0
) {

assert(
private[mill] def allowNestedExternalModule = false
if (!allowNestedExternalModule) assert(
!" #".exists(millModuleEnclosing0.value.contains(_)),
"External modules must be at a top-level static path, not " + millModuleEnclosing0.value
)
Expand Down
1 change: 1 addition & 0 deletions core/api/src/mill/api/ModuleCtx.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ object ModuleCtx extends LowPriCtx {
trait Wrapper {
def moduleCtx: ModuleCtx
private[mill] def moduleLinearized: Seq[Class[?]]
private[mill] def buildOverrides: Map[String, ujson.Value] = Map()
}
private case class Impl(
enclosing: String,
Expand Down
19 changes: 16 additions & 3 deletions core/api/src/mill/api/internal/RootModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,37 @@ abstract class RootModule()(implicit
// Provided for IDEs to think that one is available and not show errors in
// build.mill/package.mill even though they can't see the codegen
def millDiscover: Discover = sys.error("RootModule#millDiscover must be overridden")

override def buildOverrides: Map[String, ujson.Value] = baseModuleInfo.buildOverrides
}

@internal
object RootModule {
class Info(
val projectRoot: os.Path,
val output: os.Path,
val topLevelProjectRoot: os.Path
val topLevelProjectRoot: os.Path,
@com.lihaoyi.unroll val buildOverrides: Map[String, ujson.Value] = Map()
) {

def this(
projectRoot0: String,
output0: String,
topLevelProjectRoot0: String
topLevelProjectRoot0: String,
headerData: String
) = this(
os.Path(projectRoot0),
os.Path(output0),
os.Path(topLevelProjectRoot0)
os.Path(topLevelProjectRoot0),
upickle.read[Map[String, ujson.Value]](headerData)
)

def this(
projectRoot0: String,
output0: String,
topLevelProjectRoot0: String
) = this(projectRoot0, output0, topLevelProjectRoot0, "{}")

implicit val millMiscInfo: Info = this
}

Expand Down
13 changes: 10 additions & 3 deletions core/constants/src/mill/constants/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
Expand Down Expand Up @@ -84,15 +86,20 @@ private static String throwBuildHeaderError(
+ ": " + line + "\n" + msg);
}

public static String readBuildHeader(java.nio.file.Path buildFile, String errorFileName) {
public static String readBuildHeader(Path buildFile, String errorFileName) {
return readBuildHeader(buildFile, errorFileName, false);
}

public static String readBuildHeader(
Path buildFile, String errorFileName, boolean allowNonBuild) {
try {
java.util.List<String> lines = java.nio.file.Files.readAllLines(buildFile);
java.util.List<String> lines = Files.readAllLines(buildFile);
boolean readingBuildHeader = true;
java.util.List<String> output = new ArrayList<>();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
if (!line.startsWith("//|")) readingBuildHeader = false;
else if (!buildFile.getFileName().toString().startsWith("build.")) {
else if (!allowNonBuild && !buildFile.getFileName().toString().startsWith("build.")) {
throwBuildHeaderError(
errorFileName,
i,
Expand Down
32 changes: 25 additions & 7 deletions core/eval/src/mill/eval/EvaluatorImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import mill.resolve.Resolve
final class EvaluatorImpl private[mill] (
private[mill] val allowPositionalCommandArgs: Boolean,
private[mill] val selectiveExecution: Boolean = false,
private val execution: Execution
private val execution: Execution,
scriptModuleResolver: (
String,
String => Option[mill.Module]
) => Option[Result[mill.api.ExternalModule]]
) extends Evaluator {

private[mill] def workspace = execution.workspace
Expand All @@ -40,9 +44,18 @@ final class EvaluatorImpl private[mill] (
def withBaseLogger(newBaseLogger: Logger): Evaluator = new EvaluatorImpl(
allowPositionalCommandArgs,
selectiveExecution,
execution.withBaseLogger(newBaseLogger)
execution.withBaseLogger(newBaseLogger),
scriptModuleResolver
)

private[mill] def resolveSingleModule(s: String): Option[mill.Module] = {
resolveModulesOrTasks(Seq(s), SelectMode.Multi)
.toOption
.toSeq
.flatten
.collectFirst { case Left(m) => m }
}

/**
* Takes query selector tokens and resolves them to a list of [[Segments]]
* representing concrete tasks or modules that match that selector
Expand All @@ -59,7 +72,8 @@ final class EvaluatorImpl private[mill] (
scriptArgs,
selectMode,
allowPositionalCommandArgs,
resolveToModuleTasks
resolveToModuleTasks,
scriptModuleResolver = scriptModuleResolver(_, resolveSingleModule)
)
}
}
Expand All @@ -75,7 +89,8 @@ final class EvaluatorImpl private[mill] (
scriptArgs,
selectMode,
allowPositionalCommandArgs,
resolveToModuleTasks
resolveToModuleTasks,
scriptModuleResolver = scriptModuleResolver(_, resolveSingleModule)
)
}
}
Expand All @@ -97,7 +112,8 @@ final class EvaluatorImpl private[mill] (
scriptArgs,
selectMode,
allowPositionalCommandArgs,
resolveToModuleTasks
resolveToModuleTasks,
scriptModuleResolver = scriptModuleResolver(_, resolveSingleModule)
)
}
}
Expand All @@ -115,7 +131,8 @@ final class EvaluatorImpl private[mill] (
scriptArgs,
selectMode,
allowPositionalCommandArgs,
resolveToModuleTasks
resolveToModuleTasks,
scriptModuleResolver = scriptModuleResolver(_, resolveSingleModule)
)
}
}
Expand Down Expand Up @@ -276,7 +293,8 @@ final class EvaluatorImpl private[mill] (
rootModule,
scriptArgs,
selectMode,
allowPositionalCommandArgs
allowPositionalCommandArgs,
scriptModuleResolver = scriptModuleResolver(_, resolveSingleModule)
)
}
}
Expand Down
3 changes: 0 additions & 3 deletions core/exec/src/mill/exec/Execution.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ private[mill] case class Execution(
exclusiveSystemStreams: SystemStreams,
getEvaluator: () => EvaluatorApi,
offline: Boolean,
headerData: String,
enableTicker: Boolean
) extends GroupExecution with AutoCloseable {

Expand All @@ -53,7 +52,6 @@ private[mill] case class Execution(
exclusiveSystemStreams: SystemStreams,
getEvaluator: () => EvaluatorApi,
offline: Boolean,
headerData: String,
enableTicker: Boolean
) = this(
baseLogger,
Expand All @@ -73,7 +71,6 @@ private[mill] case class Execution(
exclusiveSystemStreams,
getEvaluator,
offline,
headerData,
enableTicker
)

Expand Down
59 changes: 21 additions & 38 deletions core/exec/src/mill/exec/GroupExecution.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,42 +37,8 @@ trait GroupExecution {
def systemExit: ( /* reason */ String, /* exitCode */ Int) => Nothing
def exclusiveSystemStreams: SystemStreams
def getEvaluator: () => EvaluatorApi
def headerData: String
def offline: Boolean

lazy val parsedHeaderData: Map[String, ujson.Value] = {
import org.snakeyaml.engine.v2.api.{Load, LoadSettings}
val loaded = new Load(LoadSettings.builder().build()).loadFromString(headerData)
// recursively convert java data structure to ujson.Value
val envWithPwd = env ++ Seq(
"PWD" -> workspace.toString,
"PWD_URI" -> workspace.toURI.toString,
"MILL_VERSION" -> mill.constants.BuildInfo.millVersion,
"MILL_BIN_PLATFORM" -> mill.constants.BuildInfo.millBinPlatform
)
def rec(x: Any): ujson.Value = {
import scala.jdk.CollectionConverters._
x match {
case d: java.util.Date => ujson.Str(d.toString)
case s: String => ujson.Str(mill.constants.Util.interpolateEnvVars(s, envWithPwd.asJava))
case d: Double => ujson.Num(d)
case d: Int => ujson.Num(d)
case d: Long => ujson.Num(d)
case true => ujson.True
case false => ujson.False
case null => ujson.Null
case m: java.util.Map[Object, Object] =>
val scalaMap = m.asScala
ujson.Obj.from(scalaMap.map { case (k, v) => (k.toString, rec(v)) })
case l: java.util.List[Object] =>
val scalaList: collection.Seq[Object] = l.asScala
ujson.Arr.from(scalaList.map(rec))
}
}

rec(loaded).objOpt.getOrElse(Map.empty[String, ujson.Value]).toMap
}

lazy val constructorHashSignatures: Map[String, Seq[(String, Int)]] =
CodeSigUtils.constructorHashSignatures(codeSignatures)

Expand Down Expand Up @@ -125,11 +91,28 @@ trait GroupExecution {
terminal match {

case labelled: Task.Named[_] =>
labelled.ctx.segments.value match {
case Seq(Segment.Label(single)) if parsedHeaderData.contains(single) =>
val jsonData = parsedHeaderData(single)

// recursively convert java data structure to ujson.Value
val envWithPwd = env ++ Seq(
"PWD" -> workspace.toString,
"PWD_URI" -> workspace.toURI.toString,
"MILL_VERSION" -> mill.constants.BuildInfo.millVersion,
"MILL_BIN_PLATFORM" -> mill.constants.BuildInfo.millBinPlatform
)

labelled.ctx.segments.last.value match {
case single if labelled.ctx.enclosingModule.buildOverrides.contains(single) =>
val jsonData = labelled.ctx.enclosingModule.buildOverrides(single)

import collection.JavaConverters._
def rec(x: ujson.Value): ujson.Value = x match {
case ujson.Str(s) => mill.constants.Util.interpolateEnvVars(s, envWithPwd.asJava)
case ujson.Arr(xs) => ujson.Arr(xs.map(rec))
case ujson.Obj(kvs) => ujson.Obj.from(kvs.map((k, v) => (k, rec(v))))
case v => v
}
val (resultData, serializedPaths) = PathRef.withSerializedPaths {
upickle.read[Any](jsonData)(
upickle.read[Any](rec(jsonData))(
using labelled.readWriterOpt.get.asInstanceOf[upickle.Reader[Any]]
)
}
Expand Down
5 changes: 1 addition & 4 deletions core/internal/cli/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,5 @@ import millbuild.*
object `package` extends MillPublishScalaModule {
def moduleDeps = Seq(build.libs.util)

def mvnDeps = Seq(
Deps.millModuledefs,
Deps.mainargs
)
def mvnDeps = Seq(Deps.mainargs)
}
24 changes: 8 additions & 16 deletions core/resolve/src/mill/resolve/ParseArgs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,8 @@ private[mill] object ParseArgs {

/** Separator used in [[SelectMode.Separated]] mode to separate a task-args-tuple from the next target. */
val TaskSeparator = "+"

def apply(
scriptArgs: Seq[String],
selectMode: SelectMode
): Result[Seq[TasksWithParams]] = {

val MaskPattern = ("""\\+\Q""" + TaskSeparator + """\E""").r
val MaskPattern = ("""\\+\Q""" + TaskSeparator + """\E""").r
def separate(scriptArgs: Seq[String]) = {

/**
* Partition the arguments in groups using a separator.
Expand All @@ -41,16 +36,13 @@ private[mill] object ParseArgs {
r2.drop(1)
)
}
val parts: Seq[Seq[String]] = separated(Seq() /* start value */, scriptArgs)
val parsed: Seq[Result[TasksWithParams]] =
parts.map(extractAndValidate(_, selectMode == SelectMode.Multi))

val res1: Result[Seq[TasksWithParams]] = Result.sequence(parsed)

res1
separated(Seq() /* start value */, scriptArgs)
}
def apply(scriptArgs: Seq[String], selectMode: SelectMode): Seq[Result[TasksWithParams]] = {
separate(scriptArgs).map(extractAndValidate(_, selectMode == SelectMode.Multi))
}

private def extractAndValidate(
def extractAndValidate(
scriptArgs: Seq[String],
multiSelect: Boolean
): Result[TasksWithParams] = {
Expand Down Expand Up @@ -119,7 +111,7 @@ private[mill] object ParseArgs {
case (h, rest) => Segments(h +: rest)
}

P(simpleQuery ~ ("/" ~ simpleQuery.?).? ~ End).map {
P(simpleQuery ~ (("/" | ":") ~ simpleQuery.?).? ~ End).map {
case (q, None) => (None, Some(q))
case (q, Some(q2)) => (Some(q), q2)
}
Expand Down
Loading
Loading