diff --git a/blog/modules/ROOT/pages/1-java-compile.adoc b/blog/modules/ROOT/pages/1-java-compile.adoc
index 67102e933ae..a90a61afcdf 100644
--- a/blog/modules/ROOT/pages/1-java-compile.adoc
+++ b/blog/modules/ROOT/pages/1-java-compile.adoc
@@ -22,9 +22,9 @@ build tools add significant overhead over compiling Java directly:
|===
| *Mockito Core* | *Time* | *Compiler lines/s* | *Slowdown* | *Netty Common* | *Time* | *Compiler lines/s* | *Slowdown*
| *Javac Hot* | 0.36s | 115,600 | 1.0x | *Javac Hot* | 0.29s | 102,500 | 1.0x
-| *Javac Cold* | 1.29s | 32,200 | 4.4x | *Javac Cold* | 1.62s | 18,300 | 5.6x
-| *Mill* | 1.20s | 34,700 | 4.1x | *Mill* | 1.11s | 26,800 | 3.8x
-| *Gradle* | 4.41s | 9,400 | 15.2x | *Maven* | 4.89s | 6,100 | 16.9x
+| *Javac Cold* | 1.29s | 32,200 | 3.6x | *Javac Cold* | 1.62s | 18,300 | 5.6x
+| *Mill* | 1.20s | 34,700 | 3.3x | *Mill* | 1.11s | 26,800 | 3.8x
+| *Gradle* | 4.41s | 9,400 | 12.3x | *Maven* | 4.89s | 6,100 | 16.9x
|===
Although Mill does the best in these benchmarks among the build tools (Maven, Gradle, and Mill),
@@ -292,11 +292,11 @@ using the dependencies we already have on disk.
Tabulating this all together gives us the table we saw at the start of this page:
|===
-| Mockito Core | Time | Compiler lines/s | Slowdown | Netty Common | Time | Compiler lines/s | Slowdown
-| Javac Hot | 0.36s | 115,600 | 1.0x | Javac Hot | 0.29s | 102,500 | 1.0x
-| Javac Cold | 1.29s | 32,200 | 4.4x | Javac Cold | 1.62s | 18,300 | 5.6x
-| Mill | 1.20s | 34,700 | 4.1x | Mill | 1.11s | 26,800 | 3.8x
-| Gradle | 4.41s | 9,400 | 15.2x | Maven | 4.89s | 6,100 | 16.9x
+| *Mockito Core* | *Time* | *Compiler lines/s* | *Slowdown* | *Netty Common* | *Time* | *Compiler lines/s* | *Slowdown*
+| *Javac Hot* | 0.36s | 115,600 | 1.0x | *Javac Hot* | 0.29s | 102,500 | 1.0x
+| *Javac Cold* | 1.29s | 32,200 | 3.6x | *Javac Cold* | 1.62s | 18,300 | 5.6x
+| *Mill* | 1.20s | 34,700 | 3.3x | *Mill* | 1.11s | 26,800 | 3.8x
+| *Gradle* | 4.41s | 9,400 | 12.3x | *Maven* | 4.89s | 6,100 | 16.9x
|===
We explore the comparison between xref:mill:ROOT:comparisons/gradle.adoc[Gradle vs Mill]
diff --git a/bsp/src/mill/bsp/BspContext.scala b/bsp/src/mill/bsp/BspContext.scala
index 4818c3fbab4..f7828d0cb51 100644
--- a/bsp/src/mill/bsp/BspContext.scala
+++ b/bsp/src/mill/bsp/BspContext.scala
@@ -44,19 +44,21 @@ private[mill] class BspContext(
logStream: Option[PrintStream],
canReload: Boolean
): Either[String, BspServerHandle] = {
+ // avoid name collision
+ val outerStreams = streams
val log: Logger = new Logger {
override def colored: Boolean = false
override def systemStreams: SystemStreams = new SystemStreams(
- out = streams.out,
- err = streams.err,
+ out = outerStreams.out,
+ err = outerStreams.err,
in = DummyInputStream
)
- override def info(s: String): Unit = streams.err.println(s)
- override def error(s: String): Unit = streams.err.println(s)
- override def ticker(s: String): Unit = streams.err.println(s)
- override def setPromptDetail(key: Seq[String], s: String): Unit = streams.err.println(s)
- override def debug(s: String): Unit = streams.err.println(s)
+ override def info(s: String): Unit = outerStreams.err.println(s)
+ override def error(s: String): Unit = outerStreams.err.println(s)
+ override def ticker(s: String): Unit = outerStreams.err.println(s)
+ override def setPromptDetail(key: Seq[String], s: String): Unit = outerStreams.err.println(s)
+ override def debug(s: String): Unit = outerStreams.err.println(s)
override def debugEnabled: Boolean = true
diff --git a/bsp/src/mill/bsp/BspWorker.scala b/bsp/src/mill/bsp/BspWorker.scala
index 2675783790c..263abf5bcc3 100644
--- a/bsp/src/mill/bsp/BspWorker.scala
+++ b/bsp/src/mill/bsp/BspWorker.scala
@@ -37,7 +37,7 @@ private object BspWorker {
val resources = s"${Constants.serverName}-${mill.main.BuildInfo.millVersion}.resources"
val cpFile = workspace / Constants.bspDir / resources
if (!os.exists(cpFile)) return Left(
- "You need to run `mill mill.bsp.BSP/install` before you can use the BSP server"
+ "You need to run `mill mill.bsp/install` before you can use the BSP server"
)
// TODO: if outdated, we could regenerate the resource file and re-load the worker
diff --git a/bsp/src/mill/bsp/package.scala b/bsp/src/mill/bsp/package.scala
new file mode 100644
index 00000000000..7347fc3163c
--- /dev/null
+++ b/bsp/src/mill/bsp/package.scala
@@ -0,0 +1,5 @@
+package mill.bsp
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(BSP)
diff --git a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala
index 90afbea6551..e02a2e0bdd7 100644
--- a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala
+++ b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala
@@ -602,7 +602,7 @@ private class MillBuildServer(
val mainModule = new MainModule {
override implicit def millDiscover: Discover = Discover[this.type]
}
- val compileTargetName = (module.millModuleSegments ++ Label("compile")).render
+ val compileTargetName = (module.moduleSegments ++ Label("compile")).render
debug(s"about to clean: ${compileTargetName}")
val cleanTask = mainModule.clean(ev, Seq(compileTargetName): _*)
val cleanResult = evaluate(
@@ -623,7 +623,7 @@ private class MillBuildServer(
)
else {
val outPaths = ev.pathsResolver.resolveDest(
- module.millModuleSegments ++ Label("compile")
+ module.moduleSegments ++ Label("compile")
)
val outPathSeq = Seq(outPaths.dest, outPaths.meta, outPaths.log)
diff --git a/bsp/worker/src/mill/bsp/worker/State.scala b/bsp/worker/src/mill/bsp/worker/State.scala
index e3902bc2a6e..9bab41e236d 100644
--- a/bsp/worker/src/mill/bsp/worker/State.scala
+++ b/bsp/worker/src/mill/bsp/worker/State.scala
@@ -20,9 +20,9 @@ private class State(
modules.collect {
case m: BspModule =>
val uri = Utils.sanitizeUri(
- rootModule.millSourcePath /
+ rootModule.moduleDir /
m.millOuterCtx.foreign.fold(List.empty[String])(_.parts) /
- m.millModuleSegments.parts
+ m.moduleSegments.parts
)
(new BuildTargetIdentifier(uri), (m, eval))
diff --git a/build.mill b/build.mill
index 1ecf4d609c2..00ae0a2f51c 100644
--- a/build.mill
+++ b/build.mill
@@ -455,7 +455,8 @@ trait MillScalaModule extends ScalaModule with MillJavaModule with ScalafixModul
def scalacOptions =
super.scalacOptions() ++ Seq(
"-deprecation",
- "-P:acyclic:force",
+ // To better prepare the codebase for code migration to Mill 0.13, we need to have cycles
+ "-P:acyclic:warn",
"-feature",
"-Xlint:unused",
"-Xlint:adapted-args",
diff --git a/changelog.adoc b/changelog.adoc
index f9a9c75cb07..e2ea039b17e 100644
--- a/changelog.adoc
+++ b/changelog.adoc
@@ -13,6 +13,21 @@ _The changelog below is for tagged, stable releases. For unstable releases,
see the list at https://repo1.maven.org/maven2/com/lihaoyi/mill-dist_
+[#0-12-11]
+=== 0.12.11 - 2025-05-10
+:version: 0.12.11
+:milestone-name: 0.12.11
+:milestone: 114
+:prev-version: 0.12.10
+
+* fix: Use root qualifier in Discover macro ({link-pr}/5055[#5055])
+* Fix selective execution when multiple changes are made to one module under `--watch` ({link-pr}/5032[#5032])
+* Add ability to define `package` `ExternalModule`s that can be calle via just `pkg.path/` rather than `pkg.path.ObjectName/` ({link-pr}/4920[#4920])
+* Backport `javascriptlib` and `pythonlib` improvements from `main` branch ({link-pr}/4893[#4893])
+* Fix wildcard and type selector query bugs ({link-pr}/4862[#4862])
+* Lots of Mill `main` branch compatibility changes, adding the new name and deprecating the old one
+ ({link-pr}/4838[#4838], {link-pr}/4840[#4840], {link-pr}/4843[#4843], {link-pr}/5005[#5005])
+
[#0-12-10]
=== 0.12.10 - 2025-04-01
:version: 0.12.10
diff --git a/contrib/artifactory/src/mill/contrib/artifactory/package.scala b/contrib/artifactory/src/mill/contrib/artifactory/package.scala
new file mode 100644
index 00000000000..eba3b03b876
--- /dev/null
+++ b/contrib/artifactory/src/mill/contrib/artifactory/package.scala
@@ -0,0 +1,5 @@
+package mill.contrib.artifactory
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(ArtifactoryPublishModule)
diff --git a/contrib/bintray/src/mill/contrib/bintray/package.scala b/contrib/bintray/src/mill/contrib/bintray/package.scala
new file mode 100644
index 00000000000..8880fc83214
--- /dev/null
+++ b/contrib/bintray/src/mill/contrib/bintray/package.scala
@@ -0,0 +1,5 @@
+package mill.contrib.bintray
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(BintrayPublishModule)
diff --git a/contrib/bloop/src/mill/contrib/bloop/BloopImpl.scala b/contrib/bloop/src/mill/contrib/bloop/BloopImpl.scala
index 8959653fdc2..885a25132db 100644
--- a/contrib/bloop/src/mill/contrib/bloop/BloopImpl.scala
+++ b/contrib/bloop/src/mill/contrib/bloop/BloopImpl.scala
@@ -507,7 +507,7 @@ class BloopImpl(
BloopConfig.Project(
name = name(module),
- directory = module.millSourcePath.toNIO,
+ directory = module.moduleDir.toNIO,
workspaceDir = Some(wd.toNIO),
sources = mSources,
sourcesGlobs = None,
diff --git a/contrib/codeartifact/src/mill/contrib/codeartifact/package.scala b/contrib/codeartifact/src/mill/contrib/codeartifact/package.scala
new file mode 100644
index 00000000000..2902b54bbba
--- /dev/null
+++ b/contrib/codeartifact/src/mill/contrib/codeartifact/package.scala
@@ -0,0 +1,5 @@
+package mill.contrib.codeartifact
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(CodeartifactPublishModule)
diff --git a/contrib/flyway/src/mill/contrib/flyway/FlywayModule.scala b/contrib/flyway/src/mill/contrib/flyway/FlywayModule.scala
index e7963e261f1..c376443daae 100644
--- a/contrib/flyway/src/mill/contrib/flyway/FlywayModule.scala
+++ b/contrib/flyway/src/mill/contrib/flyway/FlywayModule.scala
@@ -74,7 +74,7 @@ trait FlywayModule extends JavaModule {
val out =
s"""Schema version: ${currentSchemaVersion}
|${MigrationInfoDumper.dumpToAsciiTable(info.all)}""".stripMargin
- Task.log.outputStream.println(out)
+ Task.log.streams.out.println(out)
out
}
}
diff --git a/contrib/gitlab/src/mill/contrib/gitlab/package.scala b/contrib/gitlab/src/mill/contrib/gitlab/package.scala
new file mode 100644
index 00000000000..aa992087b06
--- /dev/null
+++ b/contrib/gitlab/src/mill/contrib/gitlab/package.scala
@@ -0,0 +1,5 @@
+package mill.contrib.gitlab
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(GitlabPublishModule)
diff --git a/contrib/jmh/src/mill/contrib/jmh/JmhModule.scala b/contrib/jmh/src/mill/contrib/jmh/JmhModule.scala
index dd9c12d5e36..ffbddf3a8e3 100644
--- a/contrib/jmh/src/mill/contrib/jmh/JmhModule.scala
+++ b/contrib/jmh/src/mill/contrib/jmh/JmhModule.scala
@@ -44,7 +44,7 @@ trait JmhModule extends JavaModule {
Seq(compileGeneratedSources().path, resources),
mainArgs = args,
cwd = Task.ctx().dest,
- javaHome = zincWorker().javaHome().map(_.path),
+ javaHome = jvmWorker().javaHome().map(_.path),
stdin = os.Inherit,
stdout = os.Inherit
)
diff --git a/contrib/playlib/src/mill/playlib/PlayModule.scala b/contrib/playlib/src/mill/playlib/PlayModule.scala
index 591ec8c8be3..0be29c6190e 100644
--- a/contrib/playlib/src/mill/playlib/PlayModule.scala
+++ b/contrib/playlib/src/mill/playlib/PlayModule.scala
@@ -19,7 +19,7 @@ trait PlayApiModule extends Dependencies with Router with Server {
}
Agg(ivy"org.scalatestplus.play::scalatestplus-play::${scalatestPlusPlayVersion}")
}
- override def sources: Target[Seq[PathRef]] = Task.Sources { millSourcePath }
+ override def sources: Target[Seq[PathRef]] = Task.Sources(moduleDir)
}
def start(args: Task[Args] = Task.Anon(Args())) = Task.Command { run(args)() }
diff --git a/contrib/playlib/src/mill/playlib/Static.scala b/contrib/playlib/src/mill/playlib/Static.scala
index 9a640e38f0b..374fe34e35b 100644
--- a/contrib/playlib/src/mill/playlib/Static.scala
+++ b/contrib/playlib/src/mill/playlib/Static.scala
@@ -25,7 +25,7 @@ trait Static extends ScalaModule {
/**
* Directories to include assets from
*/
- def assetSources = Task.Sources { millSourcePath / assetsPath() }
+ def assetSources = Task.Sources { moduleDir / assetsPath() }
/*
Collected static assets for the project
diff --git a/contrib/scalapblib/src/mill/contrib/scalapblib/ScalaPBModule.scala b/contrib/scalapblib/src/mill/contrib/scalapblib/ScalaPBModule.scala
index 063cabdb3fe..7cbf698251c 100644
--- a/contrib/scalapblib/src/mill/contrib/scalapblib/ScalaPBModule.scala
+++ b/contrib/scalapblib/src/mill/contrib/scalapblib/ScalaPBModule.scala
@@ -53,7 +53,7 @@ trait ScalaPBModule extends ScalaModule {
def scalaPBProtocPath: T[Option[String]] = Task { None }
def scalaPBSources: T[Seq[PathRef]] = Task.Sources {
- millSourcePath / "protobuf"
+ moduleDir / "protobuf"
}
def scalaPBOptions: T[String] = Task {
diff --git a/contrib/scoverage/src/mill/contrib/scoverage/ScoverageModule.scala b/contrib/scoverage/src/mill/contrib/scoverage/ScoverageModule.scala
index 59fbc5183ea..c5b3301402e 100644
--- a/contrib/scoverage/src/mill/contrib/scoverage/ScoverageModule.scala
+++ b/contrib/scoverage/src/mill/contrib/scoverage/ScoverageModule.scala
@@ -1,11 +1,11 @@
package mill.contrib.scoverage
import coursier.Repository
-import mill._
+import mill.*
import mill.api.{Loose, PathRef, Result}
import mill.contrib.scoverage.api.ScoverageReportWorkerApi2.ReportType
import mill.main.BuildInfo
-import mill.scalalib.api.ZincWorkerUtil
+import mill.scalalib.api.JvmWorkerUtil
import mill.scalalib.{Dep, DepSyntax, JavaModule, ScalaModule}
/**
@@ -57,7 +57,7 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
*/
def scoverageVersion: T[String]
- private def isScala3: Task[Boolean] = Task.Anon { ZincWorkerUtil.isScala3(outer.scalaVersion()) }
+ private def isScala3: Task[Boolean] = Task.Anon { JvmWorkerUtil.isScala3(outer.scalaVersion()) }
def scoverageRuntimeDeps: T[Agg[Dep]] = Task {
if (isScala3()) {
@@ -101,7 +101,7 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
val millScalaVersion = BuildInfo.scalaVersion
// we need to resolve with same Scala version used for Mill, not the project Scala version
- val scalaBinVersion = ZincWorkerUtil.scalaBinaryVersion(millScalaVersion)
+ val scalaBinVersion = JvmWorkerUtil.scalaBinaryVersion(millScalaVersion)
// In Scoverage 2.x, the reporting API is no longer bundled in the plugin jar
Agg(
ivy"org.scoverage:scalac-scoverage-domain_${scalaBinVersion}:${sv}",
diff --git a/contrib/sonatypecentral/src/mill/contrib/sonatypecentral/package.scala b/contrib/sonatypecentral/src/mill/contrib/sonatypecentral/package.scala
new file mode 100644
index 00000000000..d1c78b3ff50
--- /dev/null
+++ b/contrib/sonatypecentral/src/mill/contrib/sonatypecentral/package.scala
@@ -0,0 +1,5 @@
+package mill.contrib.sonatypecentral
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(SonatypeCentralPublishModule)
diff --git a/contrib/twirllib/src/mill/twirllib/TwirlModule.scala b/contrib/twirllib/src/mill/twirllib/TwirlModule.scala
index 2ce296efa4d..ae5e2c8db6d 100644
--- a/contrib/twirllib/src/mill/twirllib/TwirlModule.scala
+++ b/contrib/twirllib/src/mill/twirllib/TwirlModule.scala
@@ -25,9 +25,7 @@ trait TwirlModule extends mill.Module { twirlModule =>
}
}
- def twirlSources: T[Seq[PathRef]] = Task.Sources {
- millSourcePath / "views"
- }
+ def twirlSources: T[Seq[PathRef]] = Task.Sources("views")
/**
* Replicate the logic from twirl build,
diff --git a/contrib/versionfile/src/mill/contrib/versionfile/VersionFileModule.scala b/contrib/versionfile/src/mill/contrib/versionfile/VersionFileModule.scala
index 724631cd3a7..1bcc975ddee 100644
--- a/contrib/versionfile/src/mill/contrib/versionfile/VersionFileModule.scala
+++ b/contrib/versionfile/src/mill/contrib/versionfile/VersionFileModule.scala
@@ -80,6 +80,7 @@ trait VersionFileModule extends Module {
)
}
+object `package` extends define.ExternalModule.Alias(VersionFileModule)
object VersionFileModule extends define.ExternalModule {
/** Executes the given processes. */
diff --git a/docs/modules/ROOT/images/why-mill/ExtendingIDESupport.png b/docs/modules/ROOT/images/why-mill/ExtendingIDESupport.png
new file mode 100644
index 00000000000..891468bbadf
Binary files /dev/null and b/docs/modules/ROOT/images/why-mill/ExtendingIDESupport.png differ
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 2bb8fa71e72..e6de986788e 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -52,7 +52,6 @@
** xref:comparisons/maven.adoc[]
** xref:comparisons/gradle.adoc[]
** xref:comparisons/sbt.adoc[]
-** xref:comparisons/unique.adoc[]
* The Mill CLI
** xref:cli/installation-ide.adoc[]
** xref:cli/flags.adoc[]
diff --git a/docs/modules/ROOT/pages/cli/installation-ide.adoc b/docs/modules/ROOT/pages/cli/installation-ide.adoc
index 900084cb92d..e0effd108e9 100644
--- a/docs/modules/ROOT/pages/cli/installation-ide.adoc
+++ b/docs/modules/ROOT/pages/cli/installation-ide.adoc
@@ -17,26 +17,37 @@ globally.
The Mill example projects in this documentation come with `./mill` and `./mill.bat`
bootstrap script, included. You can also download the boostrap script manually:
-[source,bash,subs="verbatim,attributes"]
+.Mac/Linux
+[source,console,subs="verbatim,attributes"]
+----
+> curl -L {mill-download-url}/mill -o mill
+> chmod +x mill
+> echo {mill-version} > .mill-version
----
-# Mac/Linux
-curl -L {mill-download-url}/mill -o mill
-chmod +x mill
-echo {mill-last-tag} > .mill-version
-# Windows
-curl -L {mill-download-url}/mill.bat -o mill.bat
-echo {mill-last-tag} > .mill-version
+.Windows
+[source,console,subs="verbatim,attributes"]
+----
+> curl -L {mill-download-url}/mill.bat -o mill.bat
+> echo {mill-version}> .mill-version
----
-Downloading a `mill` bootstrap script to the root of your project repository helps make it easier for
+[CAUTION]
+--
+*Don't use PowerShell 5.1 to install Mill!*
+ +
+PowerShell 5.1 is known to produce `UTF-16LE` encoded `.mill-version` files, which can't be read by Mill.
+You should update to Powershell 7 or newer.
+--
+
+Downloading a `mill` bootstrap script to the root of your project repository makes it easier for
new contributors to build your project, as they don't have to install Mill before they can start.
Anyone who wants to work with the project can simply use the `./mill` script directly.
-[source,bash]
+[source,console]
----
-./mill --version
-./mill __.compile # double underscore
+> ./mill --version
+> ./mill __.compile # double underscore
----
@@ -153,7 +164,7 @@ run this command to generate the BSP configuration files:
[source,bash]
----
-./mill mill.bsp.BSP/install
+./mill mill.bsp/install
----
==== IntelliJ IDEA XML Support
@@ -167,7 +178,7 @@ To generate IntelliJ IDEA project files into `.idea/`, run:
[source,bash]
----
-./mill mill.idea.GenIdea/
+./mill mill.idea/
----
This will generate the XML files IntelliJ uses to configure your project
@@ -190,7 +201,7 @@ This will generate the XML files IntelliJ uses to configure your project
After the files are generated, you can open the folder in IntelliJ to load the project
into your IDE. If you make changes to your Mill `build.mill`, you can update the project config
-those updates by running `./mill mill.idea.GenIdea/` again.
+those updates by running `./mill mill.idea/` again.
=== VSCode
diff --git a/docs/modules/ROOT/pages/comparisons/unique.adoc b/docs/modules/ROOT/pages/comparisons/unique.adoc
deleted file mode 100644
index 7d2a6953e9f..00000000000
--- a/docs/modules/ROOT/pages/comparisons/unique.adoc
+++ /dev/null
@@ -1,533 +0,0 @@
-= What Makes Mill Unique
-
-
-
-https://mill-build.org/[Mill] is a JVM build tool that targets Java/Scala/Kotlin and has
-potential to serve the large-monorepo codebases that Bazel currently serves. Mill has good
-traction among its users, benchmarks that demonstrate 3-6x faster builds than its competitors,
-and a unique "direct-style" design that make it easy to use and extend. This page discusses
-some of the most interesting design decisions in Mill, and how it sets Mill apart from
-other build tools on the market.
-
-== What is a Build Tool?
-
-A build tool is a program that coordinates the various tasks necessary to compile,
-package, test, and run a codebase: maybe you need to run a compiler, download some dependencies,
-package an executable or container. While a small codebase can get by with a shell script that
-runs every task every time one at a time, such a naive approach gets slower
-and slower as a codebase grows and the build tasks necessarily get more numerous and complex.
-
-In order to prevent development from grinding to a halt, you need to begin skipping the
-build tasks you do not need at any point in time, and caching
-and parallelizing those that you do. This often starts
-off as some ad-hoc if-else statements in a shell script, but manually maintaining
-skipping/caching/parallelization logic is tedious and error-prone. At some point it becomes
-worthwhile to use an purpose built tool to do it for you, and that is when you turn
-to build tools like https://maven.apache.org/[Maven], https://www.gnu.org/software/make/[Make],
-https://mill-build.org/[Mill], or https://bazel.build/[Bazel]. For this article,
-we will mostly discuss Mill.
-
-== What is Mill?
-
-The Mill build tool was started in 2017, an exploration of the ideas I
-found when learning to use Google's https://bazel.build/[Bazel] build tool.
-At a glance, Mill looks similar to other build tools you may be familiar with, with a
-`build.mill` file in the root of a project defining the dependencies and testing
-setup for a module:
-
-[source,scala]
-----
-package build
-import mill._, javalib._
-
-object foo extends JavaModule {
- def ivyDeps = Seq(
- ivy"net.sourceforge.argparse4j:argparse4j:0.9.0",
- ivy"org.thymeleaf:thymeleaf:3.1.1.RELEASE"
- )
-
- object test extends JavaTests with TestModule.Junit4
-}
-----
-
-The syntax may be a bit unfamiliar, but anyone familiar with programming can probably guess
-what this build means: a `JavaModule` with two ivy dependencies `argparse4j` and `thymeleaf`,
-and a `test` submodule supporting `Junit4`.
-This build can then be compiled, tested, run, or packaged into an assembly from the command line:
-
-[source,bash]
-----
-> /mill foo.compile
-compiling 1 Java source...
-
-> /mill foo.run --text hello
-
hello
-
-> ./mill foo.test
-Test foo.FooTest.testEscaping finished, ...
-Test foo.FooTest.testSimple finished, ...
-0 failed, 0 ignored, 2 total, ...
-
-> ./mill show foo.assembly
-".../out/foo/assembly.dest/out.jar"
-
-> ./out/foo/assembly.dest/out.jar --text hello
-hello
-----
-
-Mill was originally a Scala build tool competing with https://scala-sbt.org/[`sbt`], and by 2023 it
-had reached around 5-10% market share in the Scala community
-(https://www.jetbrains.com/lp/devecosystem-2023/scala/[Jetbrains Survey],
-https://scalasurvey2023.virtuslab.com/[VirtusLabs Survey]).
-It recently grew first-class Java support, demonstrating
-xref:comparisons/why-mill.adoc[3-6x speedups] over existing Java build tools
-like Maven or Gradle. Mill also has gained experimental support for Java-adjacent platforms
-like xref:kotlinlib/intro.adoc[Kotlin] and
-xref:android/java.adoc[Android], and has demonstrated the ability to branch out into supporting
-more distant toolchains like xref:extending/example-typescript-support.adoc[Typescript]
-and xref:extending/example-python-support.adoc[Python].
-
-Mill also works well with xref:large/large.adoc[large builds]: its build logic can be
-xref:large/multi-file-builds.adoc[split into multiple folders], is incrementally compiled,
-lazily initialized, and automatically cached and parallelized. That means that even large
-codebases can remain fast and responsive: Mill's own build easily manages over 400 modules,
-and the tool can likely handle thousands of modules without issue.
-
-
-== The React.js of Build Tools
-
-We've briefly covered what Mill is above, but one question remains: why Mill?
-Why not one of the other 100 build tools out there?
-
-Mill is unique in that it shares many of its core design decisions with https://react.dev/[React.js],
-the popular Javascript UI framework. I was among the first external users of React when I
-introduced it to Dropbox in 2014, and while people gripe about it today, React was
-really a revolution in how Javascript UIs were implemented. UI flows that used to take
-weeks suddenly took days, requiring a fraction of the code and complexity that they
-previously took to implement
-
-React's two most important innovations are:
-
-1. Letting users write "direct style" code to define their UI - Javascript functions that
- directly returned the HTML structure you wanted - rather than a "code behind"
- approach of registering callbacks to mutate the UI in response to events
-
-2. Using a single "general purpose" programming language for your UI, rather than splitting
- your logic into multiple special-purpose domain-specific languages
-
-While React does a huge number of clever things -
-https://legacy.reactjs.org/docs/faq-internals.html[virtual dom diffing],
-https://react.dev/learn/writing-markup-with-jsx[JSX],
-https://react.dev/reference/react-dom/client/hydrateRoot[de/re-hydration],
-etc. - all of those are only in service of the two fundamental ideas. e.g. At Dropbox we
-used React for years without JSX, and many of the later frameworks inspired by React
-provide a similar experience but use other techniques to replace virtual dom diffing.
-Furthermore, React isn't limited to the HTML UIs, with the same techniques being
-used to manage https://reactnative.dev/[mobile app UIs],
-https://github.com/vadimdemedes/ink[terminal UIs], and many other scenarios
-
-Build tools and interactive UIs are on one hand different, but on the other hand
-very similar: you are trying to update a large stateful system (whether a HTML page
-or filesystem build artifacts) to your desired state in response to change in inputs
-(whether user-clicks or source-file-edits). Like with React in 2014, these two ideas are
-not widespread among build tools today in 2024. But many of the same downstream benefits apply,
-and these ideas give Mill some unique properties as a build tool.
-
-=== Direct-Style Builds
-
-One key aspect of React.js is that you wrote your code to generate your web UI "directly":
-
-* Before React, you would write Javascript code whose purpose was to mutate some HTML properties
- to set up a forest of callbacks and event handlers. These would then be executed when a user
- interacted with your website, causing further mutations to the HTML UI. This would often
- recursively trigger other callbacks with further mutations, and you as the developer would
- somehow need to ensure this all converges to the UI state that you desire.
-
-* In React, you had normal functions containing normal code that executed top-to-bottom,
- each returning a JSX HTML snippet - really just a Javascript object - with the top-level
- component eventually returning a snippet representing the entire UI. React would handle
- all the update logic for you in an efficient manner, incrementally caching and optimizing
- things automatically. The developer just naively returns the UI structure they want from
- their React code and React.js does all the rest
-
-Before React you always had a tradeoff: do you re-render the whole UI every update (which
-is easy to implement naively, but wasteful and disruptive to users) or do you do fine-grained UI
-updates (which was difficult to implement, but efficient and user-friendly). React eliminated that
-tradeoff, letting the developer write "naive" code as if they were re-rendering the entire
-UI, while automatically optimizing it to be performant and provide a first-class user experience.
-
-Mill's approach as a build tool is similar:
-
-* Most existing build tools involve registering "task" callbacks to tell the build tool what
- to do when certain actions happen or certain files change. These callbacks mutate the filesystem
- in an ad-hoc manner, often recursively triggering further callbacks. It is up to the developer
- to make sure that these callbacks and filesystem updates end up converging such that
- your build outputs ends up containing the files you want.
-
-* With Mill, you instead write "direct-style" code: normal functions that call other
- functions and end up returning the final metadata or files that were generated.
- Mill handles the work of computing these functions efficiently: automatically caching,
- parallelizing, and optimizing your build. The developer writes naive code computing and
- returning the files they want, and Mill does all the rest to make it efficient and performant
-
-Earlier we saw a hello-world Mill build using the built in module types like `JavaModule`,
-but if we remove these built in classes we can see how Mill works under the hood. Consider
-the following Mill tasks that define some source files, use the `javac` executable to compile
-them into classfiles, and then the `jar` executable to package them together into an assembly:
-
-[source,scala]
-----
-def mainClass: T[Option[String]] = Some("foo.Foo")
-
-def sources = Task.Source("src")
-def resources = Task.Source("resources")
-
-def compile = Task {
- val allSources = os.walk(sources().path)
- os.proc("javac", allSources, "-d", Task.dest).call()
- PathRef(Task.dest)
-}
-
-def assembly = Task {
- for(p <- Seq(compile(), resources())) os.copy(p.path, Task.dest, mergeFolders = true)
-
- val mainFlags = mainClass().toSeq.flatMap(Seq("-e", _))
- os.proc("jar", "-c", mainFlags, "-f", Task.dest / "assembly.jar", ".")
- .call(cwd = Task.dest)
-
- PathRef(Task.dest / "assembly.jar")
-}
-----
-
-This code defines the following task graph, with the boxes being the tasks
-and the arrows representing the _data-flow_ between them:
-
-[graphviz]
-....
-digraph G {
- rankdir=LR
- node [shape=box width=0 height=0 style=filled fillcolor=white]
- sources -> compile -> assembly
- resources -> assembly
- mainClass -> assembly
-}
-....
-
-This example does not use any of Mill's builtin support for building Java or
-Scala projects, and instead builds a pipeline "from scratch" using Mill
-tasks and `javac`/`jar` subprocesses. We define `Task.Source` folders and
-plain ``Task``s that depend on them, implementing entirely in our own code.
-
-Two things are worth noting about this code:
-
-1. It looks almost identical to the equivalent "naive" code you would write without using
- a build tool! If you remove the `Task{...}` wrappers, you could run the code and it would
- behave as a naive script running top-to-bottom every time and generating your
- `assembly.jar` from scratch. But Mill allows you to take such naive code and turn it
- into a build pipeline with parallelism, caching, invalidation, and so on.
-
-2. You do not see any logic at all related to parallelism, caching, invalidation in the code
- at all! No `mtime` checks, no computing cache keys, no locks, no serializing and
- de-serializing of data on disk. Mill handles all this for you automatically, so you just
- need to write your "naive" code and Mill will provide all the "build tool stuff" for free.
-
-
-This direct-style code has some surprising benefits: IDEs often not understand how registered
-callbacks recursively trigger one another, but they _do_ understand function calls, and so
-they should be able to seamlessly navigate up and down your build graph just by following
-those functions. Below, we can see IntelliJ resolve `compile` to the exact `def compile`
-definition in `build.foo`, allowing us to jump to it if we want to see what it does:
-
-image::unique/IntellijDefinition.png[]
-
-In the `JavaModule` example earlier, IntelliJ is able to see the `def ivyDeps` configuration
-override, and find the exact override definitions in the parent class hierarchy:
-
-image::unique/IntellijOverride.png[]
-
-This "direct style" doesn't just make navigating your build easy for IDEs: human programmers
-are _also_ used to navigating in and out of function calls, up and down class hierarchies,
-and so on. Thus for a developer configuring or maintaining their build system, Mill's direct
-style means they easier time understanding what is going on, especially compared to the
-classic "callbacks forests" you may have come to expect from build tools. However,
-both of these benefits require that the IDE and the human understands the code in the
-first place, which leads to the second major design decision:
-
-=== Using a Single General Purpose Language
-
-React.js makes users use Javascript to implement their HTML UIs. While a common approach
-now in 2024, it is hard to overstate how controversial and unusual this design decision
-was at the time.
-
-In 2014, web UIs were implemented in some HTML _templating language_ with separate CSS
-source files, and "code behind" Javascript logic hooked in. This allowed separation of
-concerns: a graphic designer could edit the HTML and CSS without needing to know
-Javascript, and a programmer could edit the Javascript without needing to be an expert
-in HTML/CSS. And so writing frontend code in three languages in three separate files
-was the best practice, and so it was since the inception of the web two decades prior.
-
-React.js flipped all that on its head: everything was Javascript! UI components were Javascript
-objects first, containing Javascript functions that returned HTML snippets (which
-were really _also_ Javascript objects). CSS was often in-lined at the use site, perhaps
-with constants fetched from a https://cssinjs.org/[CSS-in-JS] library. This was a total
-departure from the previous two decades of web development best practices.
-
-While controversial,
-this approach had two huge advantages:
-
-1. It broke the hard language barriers between HTML/CSS/JS, allowing more flexible
- ways of organizing and grouping code in order to meet the
- needs of the particular UI. While seemingly trivial, it makes a huge difference
- to have one file in one language containing everything you need to know about a
- UI component, rather than needing to tab between three files in three different languages.
-
-2. It removed the separate second-class "templating language". While the "platonic ideal"
- was people writing HTML/CSS/JS, the HTML often ended up being https://jinja.palletsprojects.com/[Jinja2],
- https://haml.info/[HAML], or https://mustache.github.io/[Mustache] templates instead,
- and the CSS usually ended up being replaced by https://sass-lang.com/[SASS] or
- https://lesscss.org/[LESS]. While Javascript was by no means perfect, having
- everything in a single "real" programming language was a breath of fresh air
- over tabbing between three different languages each with their own half-baked version
- of language features like if-else, loops, functions, etc.
-
-The story for build tools is similar: the traditional wisdom has been
-to implement your build logic in some limited "build language", in the past often
-XML (e.g. for https://maven.apache.org/[Maven], https://github.com/dotnet/msbuild[MSBuild]),
-nowadays often JSON/TOML/YAML (e.g. https://github.com/rust-lang/cargo[Cargo]), with
-logic split out into separate shell scripts or plugins. While this worked, it always
-had issues:
-
-1. Like web development, build tools _also_ had the logic split between multiple
- languages. Templated-Bash-in-Yaml is a common outcome, Bazel makes you write
- https://bazel.build/reference/be/make-variables[make-interpolated Bash in pseudo-Python],
- Maven makes you choose between XML+Java to write plugins or
- Bash-in-XML https://maven.apache.org/plugins/maven-antrun-plugin/[Ant scripts].
- Most build tools using "simple" config languages would inevitably find logic pushed
- into shell scripts within the build, or the entire build tool itself wrapped in a shell
- script to provide the flexibility a project needs
-
-2. These "simple build languages" would always start off simple, but eventually grow
- real programming language features: not just if-else, loops, functions, inheritance, but
- also package managers, package repositories, profilers, debuggers, and
- more. These were always ad-hoc, designed and implemented in their own weird and
- idiosyncratic ways, and generally inferior to the same feature or tool provided by
- a real programming language.
-
-_"Config metadata turns into templating language turns into general-purpose language"_
-is a tale as old as time. Whether it's HTML templating using https://jinja.palletsprojects.com/en/stable/templates/[Jinja2],
-CI configuration using https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions[Github Actions Config Expressions],
-or infrastructure-as-code systems like https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html[Cloudformation Functions]
-or https://helm.sh/docs/chart_best_practices/templates/[Helm Charts]. While the allure
-of using a "simple" config language is strong, many systems inevitably end up growing
-so many programming-language features that you would have been better off using a
-general-purpose language to start off with.
-
-
-
-Mill follows React.js with its "One General-Purpose Language" approach:
-
-1. Mill tasks are just method definitions
-2. Mill task dependencies are just method calls
-3. Mill modules are just objects
-
-While this is not strictly true - Mill tasks and Mill modules have a small amount of extra
-logic necessary to handling caching parallelization and other build tool necessities - it is
-true enough that these details are often completely transparent to the user.
-
-This has the same benefits that React.js had from using a general-purpose language throughout:
-
-1. You can directly write code to wire up and perform your build logic all in one language,
- without the nested Bash-nested-in-Mustache-templates-nested-in-YAML monstrosities common when
- insufficiently flexible config languages are chosen.
-
-2. You _already know_ how programming languages works: not just conditionals loops and functions,
- but also classes, inheritance, overrides, typechecking, IDE navigation, package repositories
- and library ecosystem (in Mill's case, you can use everything on Java's Maven Central repository).
- Rather than dealing with half-baked versions of these features that specialized languages
- inevitable grow, Mill lets you use the real thing right off the bat.
-
-For example, in Mill you may not be familiar with the bundled libraries and APIs, but your
-IDE can help you understand them:
-
-image::unique/IntellijDocs.png[]
-
-And if you make an error, e.g. you typo-ed `resources` as `reources`, your IDE will
-immediately flag it for you even before you run the build:
-
-image::unique/IntellijError.png[]
-
-While all IDEs have good support for understanding JSON/TOML/YAML/XML, the support for
-understanding _a particular tool's dialect of templated-bash-in-yaml_ is much more spotty.
-Even IntelliJ, the gold standard, usually cannot provide more than basic assistance
-editing templated-bash-in-yaml configs file. In contrast, IDE support
-for a widely-used general purpose programming language is much more solid.
-
-As another example, if you need a production-quality templating engine to use in your build
-system, you have a buffet of options. The common Java
-xref:extending/import-ivy-plugins.adoc#_importing_java_libraries[Thymeleaf] templating engine is
-available with a single import, as is the popular
-xref:extending/import-ivy-plugins.adoc#_importing_scala_libraries[Scalatags] templating engine.
-Rather than being limited to what the build tool has built-in or what third-party plugins
-someone on the internet has published, you have at your fingertips any library in the huge JVM
-ecosystem, and can use them in exactly the same way you would in any Java/Scala/Kotlin application.
-
-=== What About Other Build Tools?
-
-There are existing build tools that use some of the ideas above, but perhaps none of them
-have both, which is necessary to take full advantage:
-
-* Tools like https://gradle.org/[Gradle], https://ruby.github.io/rake/[Rake], or https://gulpjs.com/[Gulp] may be written
- in a single language, but are not direct-style: they still rely on you registering a forest
- of callbacks performing filesystem mutations, and manually ensuring that they are wired up to
- converge to the state you want. This means that although that a human programmer or an IDE
- like IntelliJ may be able to navigate around the Groovy/Kotlin/Ruby code used to configure the
- build, both human and machine often have trouble tracing through the forest of mutating callbacks
- to figure out what is actually happening
-
-* Tools like https://github.com/rust-lang/cargo[Cargo], https://maven.apache.org/[Maven], or `go build`
- are very inflexible. This leads either to embedded shell scripts (or embedded-shell-scripts-as-XML
- such as the https://maven.apache.org/plugins/maven-antrun-plugin/[Maven AntRun Plugin]!), or
- having the build tool `mvn`/`cargo`/`go` being itself wrapped in shell scripts (or even another
- build tool like Bazel!)
-
-Mill's direct style code and use of a general-purpose language makes it unique among
-build tools, just like how React.js was unique among UI frameworks when it was first released
-in 2014. With these two key design features, Mill makes understanding and maintaining your build
-an order of magnitude easier than traditional tools, democratizing project builds so anyone
-can contribute without needing to be experts.
-
-== Where can Mill Go?
-
-Above, we discussed some of the unique design decisions of Mill, and the value they
-provide to users. In this section we will discuss where Mill can fit into the larger
-build-tool ecosystem.
-I think Mill has legs to potentially grow 10x to 100x bigger than it is today. There are
-three main areas where I think Mill can grow into:
-
-=== A Modern Java/JVM Build Tool
-
-Mill is a JVM build tool, and the JVM platform hosts many rich communities and ecosystems:
-the Java folks, offshoots like Android, other languages like Kotlin and Scala. All these
-ecosystems rely on tools like Maven or Gradle to build their code, and I believe Mill
-can provide a better alternative. Even today, there are already many advantages of
-using Mill over the incumbent build tools:
-
-1. Mill today runs the equivalent local workflows xref:comparisons/maven.adoc[3-6x faster than Maven]
- and xref:comparisons/gradle.adoc[2-4x faster than Gradle], with automatic parallelization and caching for
- every part of your build
-
-2. Mill today provides better ease of use than Maven or Gradle, with IDE support for
- navigating your build graph and visualizing what your build is doing
-
-3. Mill today makes extending your build 10x easier than Maven or Gradle, directly
- using the same JVM libraries you already know without being beholden to third-party plugins
-
-The JVM is a flexible platform, and although Java/Kotlin/Scala/Android
-are superficially different, underneath there is a ton of similarity. Concepts like
-classfiles, jars, assemblies, classpaths, dependency management and publishing
-artifacts, IDEs, debuggers, profilers, many third-party libraries, are all shared and identical
-between the various JVM languages. Mill provides a first class Java and Scala experience,
-with growing support for Kotlin and Android. Mill's easy extensibility
-means integrating new tools into Mill takes hours rather than days or weeks.
-
-In the last 15-20 years, we have learned a lot about build tooling, and the field
-has developed significantly:
-
-* https://bazel.build/[Bazel], https://buck.build/[Buck], https://www.pantsbuild.org/[Pants]
- have emerged to manage large codebases
-* https://webpack.js.org/[Webpack], https://www.snowpack.dev/[Snowpack], https://esbuild.github.io/[ESBuild],
- https://nx.dev/[Nx], https://turbo.build/[TurboRepo], https://vite.dev/[Vite] have emerged for Javascript
-* https://astral.sh/[Astral], https://python-poetry.org/[Poetry], and others have emerged for Python
-* We have seen papers published like https://www.microsoft.com/en-us/research/uploads/prod/2018/03/build-systems.pdf[Build Systems A La Carte],
- that thoroughly explore the design space for how a build tool might work.
-
-But there are no build tools in the Java/JVM ecosystem that really take advantage of these
-newer designs and techniques: ideas like having a build graph, automatic caching, automatic
-parallelization, side-effect-free build tasks, and so on. While Maven (from 2004) and Gradle
-(2008) have been slowly trying to move in these directions, they are also constrained by
-their two decades of legacy that limits how fast they can evolve.
-
-Mill could be the modern Java/JVM build tool: providing 10x speedups over Maven or Gradle,
-10x better ease of use, 10x better extensibility. Today Mill already provides a compelling
-Java build experience. With some focused effort, I think Mill can be not just a _good_
-option, but the _better_ option for Java projects going forward!
-
-=== An Easier Monorepo Build Tool
-
-Many companies are using Bazel today. Of the companies I interviewed from my Silicon Valley
-network, 25 out of 30 are using or trying to use Bazel.
-Bazel is an incredibly powerful tool: it provides https://bazel.build/docs/sandboxing[sandboxing],
-parallelization, https://bazel.build/remote/caching[remote caching],
-https://bazel.build/remote/rbe[remote execution]. These are all things that are
-useful or even necessary as your organization and codebase grows. I even wrote about the
-benefits on my company blog at the time:
-
-* https://www.databricks.com/blog/2019/02/27/speedy-scala-builds-with-bazel-at-databricks.html[Speedy Scala Builds with Bazel at Databricks]
-* https://www.databricks.com/blog/2019/07/23/fast-parallel-testing-at-databricks-with-bazel.html[Fast Parallel Testing with Bazel at Databricks]
-
-There is no doubt that if set up correctly, Bazel is a great experience that "just
-works", and with a single command you can do anything that you could want to do in a codebase.
-
-But of those 25 companies I interviewed, basically everyone was having a hard time adopting Bazel.
-From my own experience, both of my prior employers (Dropbox and Databricks) both took
-`O(1 person decade)` of work to adopt Bazel. I have met _multiple_ Silicon Valley dev-tools teams that
-spent months doing a Bazel proof-of-concept only to give up due to the difficulty. Bazel is
-a ferociously complex tool, and although some of that complexity is inherent, much of it is
-incidental, and some of it is to support projects at a scale beyond what most teams would encounter.
-
-I think there is room for a lightweight monorepo build tool that provides maybe 50% of Bazel's
-functionality, but at 10% the complexity:
-
-* Most companies are not Google, don't operate at Google-scale, do not have Google-level
- problems, and may not need all the most advanced features that Bazel provides
-
-* Bazel itself is not getting any simpler over time - instead is getting more complex with
- additional features and functionality, as tends to happen to projects over time
-
-Mill provides many of the same things Bazel does: automatic xref:depth/evaluation-model.adoc[caching],
-parallelization, xref:depth/sandboxing.adoc[sandboxing],
-xref:extending/import-ivy-plugins.adoc[extensibility]. Mill
-can already work with a wide variety of programming languages,
-from JVM languages like xref:javalib/intro.adoc[Java]/xref:scalalib/intro.adoc[Scala]/xref:kotlinlib/intro.adoc[Kotlin]
-to xref:extending/example-typescript-support.adoc[Typescript] and
-xref:extending/example-python-support.adoc[Python]. Mill's features are not as
-highly-scalable as their Bazel equivalents, but they are provided in a lighter-weight,
-easier-to-use fashion suitable for organizations with less than 1,000 engineers
-who cannot afford the `O(1 person decade)` it takes to adopt Bazel in their organization.
-
-For most companies, their problems with Bazel aren't its scalability or feature set,
-but its complexity. While Mill can never compete with Bazel for the largest-scale deployments
-by its most sophisticated users, the bulk of users operate at a somewhat smaller scale and
-need something easier than Bazel. Mill could be that easy monorepo build tool for them to use.
-
-
-== Next Steps For Mill Going Forward
-
-10 years ago React.js democratized front-end Web UIs: what previously took intricate
-surgery to properly wire up event handlers and UI mutations in three separate languages
-became a straightforward task of naively returning the UI you want to render. Previously
-challenging tasks (e.g. "make a loading bar that is kept in sync with the text on screen as
-a file is uploaded") became trivial, and now anyone can probably fumble through a basic
-interactive website without getting lost in callback hell.
-
-I think Mill has a chance to do the same thing for build systems. Like Web UIs 10 years ago,
-configuring and maintaining a build-system often requires juggling multiple different
-templating/config/scripting languages in an intricate dance of callbacks and filesystem
-mutations. Like React.js, Mill collapses all this complexity, letting you write naive
-"direct-style" code in a single language while getting all the benefits of caching and
-parallelism, making previously challenging build pipelines implementations trivial.
-
-Fundamentally, there are holes in the build-tool market that are not well served:
-the Java folks deserve something more modern than Maven or Gradle, and the Monorepo folks need
-something easier to use than Bazel. I think Mill has a decent shot at occupying each
-of these two niches, and even if it is only able to succeed in one that would
-still be significant. Perhaps even significant enough to build a business around!
-
-
-Going forward, I expect to pursue both paths: Mill as a better Java build tool, Mill as
-an easier Monorepo build tool. Much of the past
-quarter Q3 2024 has been spent polishing the experience of using Mill from Java, but
-similar efforts will need to be made on the Monorepo front. I will be working on this full time
-and also investing a significant amount of cash in order to support
-the effort. If anyone out there is interested in being paid to work on the next-generation
-of Java build tools or Monorepo build tools, let me know and we can try to make an arrangement!
\ No newline at end of file
diff --git a/docs/modules/ROOT/pages/comparisons/why-mill.adoc b/docs/modules/ROOT/pages/comparisons/why-mill.adoc
index 402e53ac0a0..27785a2ddf6 100644
--- a/docs/modules/ROOT/pages/comparisons/why-mill.adoc
+++ b/docs/modules/ROOT/pages/comparisons/why-mill.adoc
@@ -1,19 +1,31 @@
= Why Use Mill?
-Mill provides 3 major advantages over other build tools. The comparison pages
-for the respective build tool go into more detail (for xref:comparisons/maven.adoc[Maven],
-xref:comparisons/gradle.adoc[Gradle], and xref:comparisons/sbt.adoc[`sbt`]), but at a high level
-these advantages are:
+Although the Java compiler is very fast and the Java language is easy to learn
+with great IDEs, JVM build tools have a reputation for being sluggish and confusing.
+For example, common build tools like Maven or Gradle compile Java code
+xref:blog::1-java-compile.adoc[12-16x slower than the underlying Java compiler]. While
+these tools are the important foundation of the Java ecosystem, it is hard to deny
+that there exists room for improvement.
-1. *Performance*: Mill offers a 3-6x speedup means less time waiting for your build tool,
+Mill tries to provide a viable alternative build tool with 3 major improvements over
+other JVM build tools available today:
+
+1. xref:#_performance[Performance]: Mill offers a 3-6x speedup means less time waiting for your build tool,
meaning less time waiting for your build and more time doing useful work
-2. *Extensibility*: Mill lets you write code or use any published JVM library in your build,
+2. xref:#_extensibility[Extensibility]: Mill lets you write code or use any published JVM library in your build,
customizing it to your needs without being limited by third-party plugins
-3. *IDE Support*: Mill has better IDE support in IntelliJ and VSCode and richer
+3. xref:#_ide_support[IDE Support]: Mill has better IDE support in IntelliJ and VSCode and richer
visualization tools than other tools, to help understand your build and what it is doing
+Uniquely among build tools, Mill builds upon the idea of
+xref:#_object_oriented_builds[Object-Oriented Builds]. Where most builds invent custom
+concepts and abstractions for setting up your build pipelines, Mill instead re-uses familiar
+concepts from object-oriented programming: __methods__, __classes__, and __overrides__.
+This means any Java developer can feel immediately at home extending and maintaining their
+Mill builds without needing to first become an expert in the build-tool lifecycle or plugin API.
+
At a first glance, Mill looks like any other build tool. You have build files, you configure
dependencies, you can compile, run, or test your project:
@@ -42,8 +54,8 @@ compiling 1 Java source...
hello
> ./mill foo.test
-Test foo.FooTest.testEscaping finished, ...
-Test foo.FooTest.testSimple finished, ...
+Test foo.barTest.testEscaping finished, ...
+Test foo.barTest.testSimple finished, ...
0 failed, 0 ignored, 2 total, ...
----
@@ -69,14 +81,14 @@ both parallel and sequential, and for many modules or for a single module:
First, let's look at *Parallel Clean Compile All*.
This benchmark involves running `clean` to delete all generated files and re-compiling
-everything in parallel. Mill sees a significant ~6x speedup over Maven for this benchmark.
+everything in parallel. Mill sees a significant ~5x speedup over Maven for this benchmark.
You can click on the link above to see a more detailed discussion of how this benchmark was
run.
The second benchmark worth noting is *Incremental Compile Single Module*.
This benchmark involves making a single edit to a single already-compiled file in `common` -
adding a single newline to the end of the file - and re-compiling `common` and `common.test`.
-Mill sees a huge ~12x speedup for this benchmark, because Mill's incremental compiler
+Mill sees a huge speedup for this benchmark, because Mill's incremental compiler
(https://github.com/sbt/zinc[Zinc]) is able to detect that only one file in one module
has changed, and that the change is small enough
to not require other files to re-compile. In contrast, Maven re-compiles all files in both
@@ -134,13 +146,20 @@ also provides features that help speed up other parts of your development cycle:
image::ROOT:comparisons/NettyCompileProfile.png[]
+None of these features are rocket science, and they are all things that you can
+in theory set up with other build tools. However, Mill provides these features
+built-in without needing to first hunt down plugins or third-party integrations,
+and makes all of them easy to set up and use.
+
== Extensibility
+=== Builtins
+
Mill comes with a lot more functionality built in than other tools such as Maven,
Gradle, or SBT. In general, most common development workflows are built in to Mill:
autoformatting, autofixing, publishing, packaging, etc. This means you can go very far
with just Mill's builtin functionality, without first needing to trawl the internet to
-find plugins and evaluate their relevance, quality, and maintenance level:
+find plugins and evaluate their relevance, quality, and level of maintenance:
include::partial$Feature_Support.adoc[]
@@ -149,6 +168,9 @@ for less common use cases:
- xref::extending/thirdparty-plugins.adoc[]
+
+=== Extending Mill
+
When you do need to extend it, Mill allows you to directly write code to configure your build, and even download libraries
from Maven Central.
@@ -163,12 +185,12 @@ write plugins yourself, doing so is usually non-trivial.
Mill is different. Although it does have plugins for more advanced integrations, for most
simple things you can directly write code to achieve what you want, using the bundled
filesystem, subprocess, and dependency-management libraries. And even if you need third-party
-libraries from Maven Central to do Foo, you can directly import the "Foo" library and use it
-directly, without having to find a "Foo build plugin" wrapper.
+libraries from Maven Central to do `xyz`, you can directly import the "`xyz`" library and use it
+directly, without having to find a "xyz build plugin" wrapper.
=== Simple Custom Tasks
-The following Mill build is a minimal Java module `foo`. It contains no custom configuration, and
+The following Mill build is a minimal Java module `foo.`. It contains no custom configuration, and
so inherits all the defaults from `mill.javalib.JavaModule`: default source folder layout, default
assembly configuration, default compiler flags, and so on.
@@ -204,7 +226,7 @@ object foo extends JavaModule {
Once we define a new task, we can immediately begin using it in our build.
`lineCount` is not used by any existing `JavaModule` tasks, but we can still
-show its value via the Mill command line to force it to evaluate:
+show its value via the Mill command line to evaluate it:
[source,bash]
----
@@ -265,10 +287,10 @@ which includes usage of third-party libraries in the build.
Earlier on we discussed possibly pre-rendering HTML pages in the build so they can be
served at runtime. The use case for this are obvious: if a page never changes, rendering
-it on every request is wasteful, and even rendering it once and then caching it can impact
-your application startup time. Thus, you may want to move some HTML rendering to build-time,
-but with traditional build tools such a move is sufficiently inconvenient and complicated
-that people do not do it.
+it on every request is wasteful, and even rendering it once on startup and then caching it
+can impact your application startup time. Thus, you may want to move some HTML rendering to
+build-time, but with traditional build tools such a move is sufficiently inconvenient and
+complicated that people do not do it.
With Mill, pre-rendering HTML at build time is really easy, even if you need a third-party
library. Mill does not ship with a bundled HTML templating engine, but you can use the
@@ -296,19 +318,22 @@ object foo extends JavaModule {
context
)
}
- def resources = Task.Sources{
+
+ def resources = Task {
os.write(Task.dest / "snippet.txt", htmlSnippet())
super.resources() ++ Seq(PathRef(Task.dest))
}
}
----
-Once we have run `import $ivy`, we can import `TemplateEngine`, `Context`, and replace our
-`def lineCount` with a `def htmlSnippet` task that uses Thymeleaf to render HTML. Again,
-we get full IDE support for working with the Thymeleaf Java API, the new `htmlSnippet` task
-is inspectable from the Mill command line via `show`, and we wire it up into
-`def resources` so it can be inspected and used at runtime by the application
-(in this case just printed out):
+Once we have specified our `import $ivy` in the build file YAML header, we can import
+`TemplateEngine`, `Context`, and replace our `def lineCount` with a `def htmlSnippet` task
+that uses Thymeleaf to render HTML. We get full IDE support for working with the
+Thymeleaf Java API, the new `htmlSnippet` task is inspectable from the Mill command line
+via `show`, and we wire it up into `def resources` so it can be inspected and used at
+runtime by the application (in this case just printed out):
+
+image::why-mill/ExtendingIDESupport.png[]
[source,bash]
----
@@ -338,7 +363,8 @@ interesting here is what we did _not_ need to do:
Instead, we could simply import Thymeleaf as a Java library directly from Maven Central
and use it just like we would use it in any Java application, with IDE support,
-typechecking, and automatic parallelism and caching.
+typechecking, and all the necessary build tool features like automatic parallelism, caching,
+and invalidation.
'''
@@ -361,11 +387,15 @@ build correctly and achieve maximum performance even without being a build tool
== IDE Support
-One area that Mill does better than Gradle is providing a seamless IDE experience. For example,
-consider the snippet below where we are using Gradle to configure the javac compiler options.
-Due to `.gradle` files being untyped Groovy, the autocomplete and code-assist experience working
-with these files is hit-or-miss. In the example below, we can see that IntelliJ is able to identify
-that `compileArgs` exists and has the type `List`:
+One area that Mill does better than Maven, Gradle or SBT is providing a seamless IDE
+experience.
+
+=== Limitations of Existing Build Tool IDE Integrations
+
+For example, consider the snippet below where we are using Gradle to
+configure the javac compiler options. The autocomplete and code-assist experience working
+with these files is hit-or-miss. In the example below, we can see that IntelliJ is able to
+identify that `compileArgs` exists and has the type `List`:
image::comparisons/IntellijMockitoGradleCompileOptions.png[]
@@ -374,35 +404,51 @@ But if you try to jump to definition or find out anything else about it you hit
image::comparisons/IntellijMockitoGradleCompileOptions2.png[]
Often working with build configurations feels like hitting dead ends: if you don't have
-`options.compilerArgs` memorized in your head, there is literally nothing you can do in your editor to
-make progress to figure out what it is or what it is used for. That leaves you googling
-for answers, which can be a frustrating experience that distracts you from the task at hand.
-
-The fundamental problem with tools like Gradle is that the code you write does not
-actually perform the build: rather, you are just setting up some data structure that
-is used to configure the _real_ build engine that runs later. Thus when you explore
-the Gradle build in an IDE, the IDE can only explore the configuration logic (the
-`getCompilerArgs` method above) and is unable to explore the actual build logic (how
-`getCompilerArgs` _actually gets used in Gradle_)
-
-In comparison, not only are Mill's `.mill` files statically typed, allowing IDEs like IntelliJ
-to pull up the documentation for `def javacOptions`:
-
-image::comparisons/IntellijMockitoMillJavacOptionsDocs.png[]
-
-The way Mill builds are structured also helps the IDE: Mill
-code _actually performs your build_, rather than configuring some opaque build engine.
-While that sounds academic, one concrete consequence is that IntelliJ is able to take
-your `def javacOptions` override and
+`options.compilerArgs` memorized in your head, there is literally nothing you can do in
+your editor to make progress to figure out what it is or what it is used for. That leaves
+you googling for answers, which can be a frustrating experience that distracts you from
+the task at hand.
+
+Although this example is using Gradle's un-typed Groovy syntax, the experience
+using Gradle's typed Kotlin syntax is largely the same. The problem isn't unique
+to Gradle, and isn't unique to IntelliJ: any IDE would have similar problems working
+with this code, and many build tools ask that you write code in a similar style.
+
+=== Global Mutable Variables
+
+The fundamental problem with build tools like Gradle is that the entire configuration system
+is based around _global mutable variables_. The Groovy or Kotlin code you write does not
+actually _perform the build_, but instead is just setting up some
+global mutable data structure that is used to configure the _real_ build engine that
+runs _later_. Thus when you explore the Gradle build in an IDE, the IDE can only explore the
+configuration logic (the`getCompilerArgs` method above) and is unable to explore
+the actual build logic (how `getCompilerArgs` _actually gets used in Gradle_). And
+just because the global mutable variable is wrapped in a getter and setter
+does not make it any less global or any less mutable!
+
+The problem with IDEs not being able to understand code written with global mutable
+variables is not new. If you wrote your application code primarily using global mutable
+variables, your IDE would not be able to help you much there either! So nobody does that,
+and instead applications are primarily built using classes and methods, but some how
+no build tool follows the same style so they all have problems with IDEs not being able
+to work with their global mutable variables effectively.
+
+=== The Mill IDE Experience
+
+
+In comparison, not only are Mill's `.mill` files statically typed, they are
+built on top of classes and methods like any other JVM codebase.
+Mill code _actually performs your build_, and one concrete consequence is that
+IntelliJ is able to take your `def javacOptions` override and
find the original definitions that were overridden, and show you where they are defined:
image::comparisons/IntellijMockitoMillJavacOptionsParents.png[]
-You can jump to any of the overridden `def`s quickly and precisely:
+You can jump to any of the overridden ``def``s quickly and precisely:
image::comparisons/IntellijMockitoMillJavacOptionsDef.png[]
-Furthermore, because task dependencies in Mill are just normal method calls, IntelliJ is
+And because tasks in Mill are just normal methods, IntelliJ is
able to _find usages_, showing you where the task is used. Below, we can see the method
call in the `def compile` task, which uses `javacOptions()` along with a number of other tasks:
@@ -416,25 +462,231 @@ interactively exploring your build logic from there:
image::comparisons/IntellijMockitoMillCompileClasspath.png[]
-Unlike most other build tools, Mill builds can be explored interactively in your
+Unlike most other build tools, Mill build pipelines can be explored interactively in your
IDE. If you do not know what something does, it's documentation, definition, or usages is always
one click away in IntelliJ or VSCode. This isn't a new experience for Java developers, as it
-is what you would be used to day-to-day in your application code! But Mill brings that same
+is what you experience every day working in your application code! But Mill brings that same
polished experience to your build system - traditionally something that has been opaque
-and hard to understand - and does so in a way that no other build tool does.
+and hard to understand - and does so in a way that no other build tool does. And this is
+possible because Mill builds avoid the global mutable variables common in other build
+tools, in favor of configuring your build via classes and methods that are familiar to
+both users and to IDEs.
+
+== Object-Oriented Builds
+
+All build tools are complex, because the requirements of building any size-able real-world
+project are complex. One big source of build tool complexity is that users need some way to
+define templated, customizable graph-computations when setting up their build pipelines.
+
+- *Computations* as you need to specify what each build step does
+- *Graph* as the data structure necessary to order and parallelize steps in your build
+- *Templated* as different modules in a build are often very similar, with similar pipelines
+- *Customizable* as there are always some module, or group of modules, special in some way
+
+For example, even a simplified three-module Java build pipeline may look like this:
+
+[graphviz]
+....
+digraph G {
+ rankdir=LR
+ node [shape=box width=0 height=0 style=filled fillcolor=white]
+ bgcolor=transparent
+ newrank=true;
+ subgraph cluster_0 {
+ style=dashed
+ node [shape=box width=0 height=0 style=filled fillcolor=white]
+ label = "foo";
+ "foo.sources"
+ "foo.compile"
+ "foo.mainClass"
+ "foo.assembly"
+ "foo.classPath"
+ }
+ subgraph cluster_1 {
+ style=dashed
+ node [shape=box width=0 height=0 style=filled fillcolor=white]
+ label = "bar";
+ "bar.sources"
+ "bar.compile"
+ "bar.mainClass"
+ "bar.assembly"
+ "bar.classPath"
+ }
+ subgraph cluster_2 {
+ style=dashed
+ node [shape=box width=0 height=0 style=filled fillcolor=white]
+ label = "qux";
+ "qux.sources"
+ "qux.compile"
+ "qux.mainClass"
+ "qux.assembly"
+ "qux.classPath"
+ }
+ "foo.sources" -> "foo.compile" -> "foo.classPath" -> "foo.assembly"
+ "foo.mainClass" -> "foo.assembly"
+ "foo.classPath" -> "bar.compile" [constraint=false];
+
+ "foo.classPath" -> "bar.classPath"
+ "bar.mainClass" -> "bar.assembly"
+ "bar.sources" -> "bar.compile" -> "bar.classPath" -> "bar.assembly"
+
+ "bar.classPath" -> "qux.compile" [constraint=false];
+ "bar.classPath" -> "qux.classPath"
+ "qux.mainClass" -> "qux.assembly"
+ "qux.sources" -> "qux.compile" -> "qux.classPath" -> "qux.assembly"
+}
+....
+
+Apart from the various tasks doing different things, we also see multiple modules
+with similar task layouts (`foo`, `bar` and `qux`), but with subtle customizations in
+each module to wire them up
+(e.g. `foo.classPath` having one upstream task but `bar.classPath` and `qux.classPath`
+each having two). And this is one of the simplest possible builds: you can imagine that
+with compiler and runtime flags, code-generation, dependency-downloading, different language
+toolchains, these graphs can get complicated quickly. And any build tool will need some way
+for the user to defines these graphs and maintain them over time.
+
+Most build tools provide ad-hoc config formats (Maven's XML) or programmatic builder
+APIs (e.g. Gradle's Groovy/Kotlin) to satisfy this need, but these ad-hoc formats
+inevitably end up being unfamiliar and confusing to users. Many try to hide
+complexity behind plugins, but this inevitably leaks when end-users find their build
+pipeline misbehaving and need to debug their plugin interactions, and the attempt
+to hide things may make debugging even more difficult.
+
+Fundamentally, defining an API for templated, customizable graph-computations is
+non-trivial, so is not surprising the APIs and to do so can get complicated.
+
+=== Object-Oriented Build Pipelines
+
+Mill has the same requirement of defining templated, customizable graph-computations, but
+rather than inventing a bespoke programming, configuration and plugin model to do so, Mill
+builds upon what everyone already knows: _Object-Oriented Programming_. It turns out that the
+object-oriented programming every Java developer learned in school provides all the key
+building blocks necessary to define templated, customizable graph-computations:
+
+- *Methods* provide a way of defining discrete build steps that perform
+ some necessary action in your build
+
+- The *Call Graph* between methods provides the build graph, where which method call
+ which other methods defines the incoming edges of that node in the graph
+
+- *Classes* provide the templating, where you can define a set of methods calling each
+ other, and instantiate those call graphs more than once in different parts of your build
+
+- *Overrides* and *Subclasses* provide customization: when a particular instance or
+ sub-class needs a different implementation of one-or-more build steps, you can
+ override the respective methods to customize the build call graph to your liking
+
+Thus, when you see a Mill build configured as such, with an `object` extending a `class`:
+
+[source,scala]
+----
+package build
+import mill._, javalib._
+
+object foo extends JavaModule {
+}
+----
+
+This is not some special syntax, but is literally defining an object named `foo`
+inheriting from the class `JavaModule`. Like any other inheritance, this picks up
+the methods and method call graph of `JavaModule` (slightly simplified below)
+
+[graphviz]
+....
+digraph G {
+ rankdir=LR
+ node [shape=box width=0 height=0 style=filled fillcolor=white]
+ bgcolor=transparent
+ newrank=true;
+ subgraph cluster_0 {
+ style=dashed
+ node [shape=box width=0 height=0 style=filled fillcolor=white]
+ label = "foo";
+
+ "foo.sources" -> "foo.compile" -> "foo.classPath" -> "foo.assembly"
+ "foo.resources" -> "foo.assembly"
+ }
+}
+....
+
+And when you add additional tasks by defining methods using `def`, or `override` tasks
+and call `super`:
+
+[source,scala]
+----
+package build
+import mill._, javalib._
+
+object foo extends JavaModule {
+ /** Total number of lines in module source files */
+ def lineCount = Task {
+ allSourceFiles().map(f => os.read.lines(f.path).size).sum
+ }
+
+ /** Generate resources using lineCount of sources */
+ override def resources = Task {
+ os.write(Task.dest / "line-count.txt", "" + lineCount())
+ super.resources() ++ Seq(PathRef(Task.dest))
+ }
+}
+----
+
+You as a Java programmer already know how these changes affect the build graph, by splicing
+in the new method `foo.lineCount`, replacing `foo.resources` with a new method body, and
+calling `foo.super.resources`:
+
+[graphviz]
+....
+digraph G {
+ rankdir=LR
+ node [shape=box width=0 height=0 style=filled fillcolor=white]
+ bgcolor=transparent
+ newrank=true;
+ subgraph cluster_0 {
+ style=dashed
+ node [shape=box width=0 height=0 style=filled fillcolor=white]
+ label = "foo";
+
+ "foo.sources" -> "foo.compile" -> "foo.classPath" -> "foo.assembly"
+ "foo.super.resources" -> "foo.resources" -> "foo.assembly" [color = darkgreen, penwidth=3]
+ "foo.lineCount" [color = darkgreen, penwidth=3]
+ "foo.sources" -> "foo.lineCount" -> "foo.resources" [color = darkgreen, penwidth=3]
+ "foo.resources" [color = darkgreen, penwidth=3]
+ }
+}
+....
+
+Mill's usage of methods, classes, and overrides is also what powers the IDE support
+discussed earlier on this page. IDEs like IntelliJ or VSCode are uniquely adept at
+working with JVM codebases full of methods and classes, and so
+they can help you navigate and understand your Mill build pipelines just as easily
+as any application codebase.
+Mill does not make the complexity in your build pipelines go away, or hide it behind
+hard-coded helpers or plugins. Instead, Mill lets you manage that complexity using the
+same object-oriented programming techniques you are already familiar with. With Mill,
+you can navigate, maintain, and extend your build pipelines in exactly the same way you
+already navigate, maintain, and extend your JVM application code.
== Conclusion
-To wrap up, Mill does all the same things that other build tools like Maven or Gradle do,
-but aims to do them better: faster, easier to use, and easier to extend.
Build systems have traditionally been mysterious black boxes that only experts could work
with: slow for unknown reasons, with cargo-culted configuration and usage commands,
and challenging for normal application developers to contribute improvements to.
-Mill flips this on its head, democratizing your build system such that even non-experts
-are able to contribute, and can do so safely and easily such that your build workflows
-achieve their maximum possible performance.
+The things that a build tool needs to do - templated, customizable graph-computations -
+are inherently complex, and so it is not a surprise that this complexity bleeds through
+to the user experience.
+
+While Mill does all the same things that other build tools like Maven or Gradle do,
+it aims to do them better: faster, easier to use, and easier to extend. It brings to
+the table the idea of _Object-Oriented Builds_, using the familiar concepts of methods,
+classes, and overrides to help reduce the barrier to entry of someone who needs to
+learn enough to configure their build tool. Mill democratizing your build system such
+that even non-experts are able to contribute - anyone who has learned Java 101 knows
+the core concepts behind Mill - and they can do so safely and easily with the
+IDE support, library ecosystem, and other quality of life details they are used to.
The rest of this doc-site contains more Mill build tool comparisons
(with xref:comparisons/maven.adoc[Maven], xref:comparisons/gradle.adoc[Gradle],
diff --git a/docs/modules/ROOT/pages/extending/meta-build.adoc b/docs/modules/ROOT/pages/extending/meta-build.adoc
index 83abb9f0a49..ccc1769a330 100644
--- a/docs/modules/ROOT/pages/extending/meta-build.adoc
+++ b/docs/modules/ROOT/pages/extending/meta-build.adoc
@@ -26,11 +26,11 @@ You only need a `.scalafmt.conf` config file which at least needs configure the
.Run Scalafmt on the `build.mill` (and potentially included files)
----
-$ mill --meta-level 1 mill.scalalib.scalafmt.ScalafmtModule/
+$ mill --meta-level 1 mill.scalalib.scalafmt/
----
* `--meta-level 1` selects the first meta-build. Without any customization, this is the only built-in meta-build.
-* `mill.scalalib.scalafmt.ScalafmtModule/reformatAll` is a generic task to format scala source files with Scalafmt. It requires the tasks that refer to the source files as argument
+* `mill.scalalib.scalafmt/reformatAll` is a generic task to format scala source files with Scalafmt. It requires the tasks that refer to the source files as argument
* `sources` this selects the `sources` tasks of the meta-build, which at least contains the `build.mill`.
== Finding plugin updates
diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc
index db85a096a1c..23fff110e70 100644
--- a/docs/modules/ROOT/pages/index.adoc
+++ b/docs/modules/ROOT/pages/index.adoc
@@ -1,18 +1,32 @@
= Mill: A Better JVM Build Tool
-https://github.com/com-lihaoyi/mill/blob/main/changelog.adoc[image:https://index.scala-lang.org/com-lihaoyi/mill/mill-main/latest.svg[Mill]]
+https://github.com/com-lihaoyi/mill/blob/main/changelog.adoc[image:https://index.scala-lang.org/com-lihaoyi/mill/mill-libs-main/latest.svg[Mill]]
https://central.sonatype.com/artifact/com.lihaoyi/mill-dist[image:https://img.shields.io/maven-central/v/com.lihaoyi/mill-dist?label=latest-unstable&versionPrefix=0.12.[Maven Central Version]]
Mill is a JVM build tool that supports Java, Scala, and Kotlin:
-* Mill builds the same Java project xref:comparisons/maven.adoc[3-6x faster than Maven]
-xref:comparisons/gradle.adoc[or Gradle] due to aggressive caching & parallelism
+* Mill builds the same Java project xref:comparisons/why-mill.adoc#_performance[3-6x
+ faster than Maven or Gradle] due to aggressive caching & parallelism
+
+* Mill builds require xref:comparisons/why-mill.adoc#_builtins[fewer plugins for common workflows],
+and are xref:comparisons/why-mill.adoc#_extending_mill[easier to extend with custom tasks]
* Mill has better IDE support than other build tools, with
-superior autocomplete and navigation in IntelliJ
+xref:comparisons/why-mill.adoc#_ide_support[superior autocomplete and navigation in IntelliJ]
+
+Although the Java compiler is very fast and the Java language is easy to learn,
+JVM build tools have a reputation for being sluggish and confusing. Mill tries to
+offer a better alternative, letting your build system take full advantage of the
+Java platform's performance and usability.
+
+Mill's unique insight is xref:comparisons/why-mill.adoc#_object_oriented_builds[Object-Oriented
+Builds]. While most build tools invent their own concepts to manage the complexity of your
+build pipelines, Mill re-uses concepts you already know: methods, classes, and overrides. This
+makes the semantics of Mill builds feel familiar even to non-build-tool experts.
+
+For more discussion on the motivation behind the project, see xref:comparisons/why-mill.adoc[Why Use Mill?]
-* Mill builds require fewer plugins for common workflows,
-and are easier to extend with custom tasks
+=== Getting Started
To get started using Mill for the first time, or migrating and existing JVM
codebase (built using Maven, Gradle, or SBT) to Mill, see:
@@ -33,12 +47,7 @@ Or see the language-specific introductory documentation linked below:
=== Why Mill?
-Although the Java compiler is very fast and the Java language is easy to learn,
-JVM build tools have a reputation for being sluggish and confusing. Mill tries to
-offer a better alternative, letting your build system take full advantage of the
-Java platform's performance and usability:
-
-* *Performance*: Mill automatically xref:depth/evaluation-model.adoc#_caching_in_mill[caches]
+* *Performance*: Mill automatically xref:depth/execution-model.adoc#_caching_in_mill[caches]
and xref:depth/parallelism.adoc[parallelizes build tasks] and https://mill-build.org/blog/11-jvm-test-parallelism.html[tests]
to keep local development fast, https://mill-build.org/blog/9-mill-faster-assembly-jars.html[incrementally builds assembly jars]
to speed up manual testing workflows, and shortens CI times with xref:large/selective-execution.adoc[selective test execution]
@@ -75,17 +84,12 @@ xref:large/multi-language-builds.adoc[multiple languages].
Even projects with hundreds or thousands of sub-modules can be handled performantly
and with minimal overhead.
-For a quick introduction of why you may care about Mill, see:
-
-* xref:comparisons/why-mill.adoc[]
-* xref:comparisons/unique.adoc[]
-
-Or if you prefer a video introduction:
+If you prefer a video introduction to Mill, see:
* https://www.youtube.com/watch?v=Dry6wMRN6MI[Video: Better Java Builds with the Mill Build Tool],
- Japan Java User Group Fall Conference Oct 2024
+Japan Java User Group Fall Conference Oct 2024
* https://www.youtube.com/watch?v=igarEERjUuQ[Video: Better Scala Builds with the Mill Build Tool],
- Functional Scala Conference Dec 2024
+Functional Scala Conference Dec 2024
Mill is used to build many real-world projects, such as the
https://github.com/swaldman/c3p0[C3P0 JDBC Connection Pool],
@@ -97,7 +101,8 @@ Mill can be used for applications built on top of common JVM frameworks like
Spring Boot (both xref:javalib/web-examples.adoc#_spring_boot_todomvc_app[in Java]
and xref:kotlinlib/web-examples.adoc#_spring_boot_todomvc_app[in Kotlin]),
xref:javalib/web-examples.adoc#_micronaut_todomvc_app[Micronaut],
-or xref:kotlinlib/web-examples.adoc#_ktor_todomvc_app[Ktor].
+xref:kotlinlib/web-examples.adoc#_ktor_todomvc_app[Ktor], or
+xref:scalalib/web-examples.adoc#_todomvc_http4s_web_app[Http4S].
Mill borrows ideas from other tools like https://maven.apache.org/[Maven],
https://gradle.org/[Gradle], https://bazel.build/[Bazel], but tries to learn from the
@@ -128,8 +133,8 @@ also has a rich ecosystem of third party plugins for less common use cases:
=== Contributing and Community
-If you want to contribute to Mill, or are interested in the fundamental ideas behind
-Mill rather than the user-facing benefits discussed above, check out the page on
+If you want to contribute to Mill, or are interested in the fundamental ideas behind
+Mill rather than the user-facing benefits discussed above, check out the page on
xref:depth/design-principles.adoc[Mill Design Principles]. In particular, this video
is a must-watch to help you understand the fundamental concepts behind Mill and why
they are interesting:
diff --git a/docs/package.mill b/docs/package.mill
index baec2a1585e..7f5cdca38c5 100644
--- a/docs/package.mill
+++ b/docs/package.mill
@@ -40,7 +40,7 @@ object `package` extends RootModule {
"install",
"@antora/cli@3.1.9",
"@antora/site-generator-default@3.1.9",
- "gitlab:antora/xref-validator",
+// "gitlab:antora/xref-validator",
"@antora/lunr-extension@v1.0.0-alpha.6"
),
envArgs = Map(),
diff --git a/example/extending/metabuild/3-autoformatting/build.mill b/example/extending/metabuild/3-autoformatting/build.mill
index 3978f2a460b..2c46181e5f2 100644
--- a/example/extending/metabuild/3-autoformatting/build.mill
+++ b/example/extending/metabuild/3-autoformatting/build.mill
@@ -14,7 +14,7 @@ object foo extends Module { def task = Task { "2.13.4" } }
object foo extends Module {def task=Task{"2.13.4"}}
...
-> mill --meta-level 1 mill.scalalib.scalafmt.ScalafmtModule/
+> mill --meta-level 1 mill.scalalib.scalafmt/
> cat build.mill # build.mill is now well formatted
object foo extends Module { def task = Task { "2.13.4" } }
@@ -24,7 +24,7 @@ object foo extends Module { def task = Task { "2.13.4" } }
//
// * `--meta-level 1` selects the first meta-build. Without any customization, this is
// the only built-in meta-build.
-// * `mill.scalalib.scalafmt.ScalafmtModule/reformatAll` is a generic task to format scala
+// * `mill.scalalib.scalafmt/reformatAll` is a generic task to format scala
// source files with Scalafmt. It requires the tasks that refer to the source files as argument
// * `sources` this selects the `sources` tasks of the meta-build, which at least contains
// the `build.mill`.
diff --git a/example/extending/plugins/7-writing-mill-plugins/build.mill b/example/extending/plugins/7-writing-mill-plugins/build.mill
index de6a6767859..e38516f8699 100644
--- a/example/extending/plugins/7-writing-mill-plugins/build.mill
+++ b/example/extending/plugins/7-writing-mill-plugins/build.mill
@@ -200,6 +200,6 @@ Publishing Artifact(com.lihaoyi,myplugin_mill0.11_2.13,0.0.2) to ivy repo...
// Mill plugins are JVM libraries like any other library written in Java or Scala. Thus they
// are published the same way: by extending `PublishModule` and defining the module's `publishVersion`
// and `pomSettings`. Once done, you can publish the plugin locally via `publishLocal`,
-// or to Maven Central via `mill.scalalib.public.PublishModule/` for other developers to
+// or to Maven Central via `mill.scalalib.PublishModule/` for other developers to
// use. For more details on publishing Mill projects, see the documentation for
// xref:scalalib/publishing.adoc[Publishing Scala Projects]
diff --git a/example/fundamentals/modules/10-external-module-aliases/build.mill b/example/fundamentals/modules/10-external-module-aliases/build.mill
index f934f8059bd..4700174ab00 100644
--- a/example/fundamentals/modules/10-external-module-aliases/build.mill
+++ b/example/fundamentals/modules/10-external-module-aliases/build.mill
@@ -14,7 +14,7 @@ def myAutoformat = mill.javalib.palantirformat.PalantirFormatModule
> cat foo/src/foo/Foo.java # starts off unformatted
package foo;public class Foo{ public static void main(String[] args) {System.out.println("Hello World!");}}
-> mill myAutoformat # easier to type than `./mill mill.javalib.palantirformat.PalantirFormatModule/`
+> mill myAutoformat # easier to type than `./mill mill.javalib.palantirformat/`
> cat foo/src/foo/Foo.java # code is now formatted
package foo;
diff --git a/example/fundamentals/modules/8-diy-java-modules/build.mill b/example/fundamentals/modules/8-diy-java-modules/build.mill
index 92ebe8d40c4..8a85f9af308 100644
--- a/example/fundamentals/modules/8-diy-java-modules/build.mill
+++ b/example/fundamentals/modules/8-diy-java-modules/build.mill
@@ -96,30 +96,42 @@ object qux extends DiyJavaModule {
// style=dashed
// node [shape=box width=0 height=0 style=filled fillcolor=white]
// label = "foo.bar";
-//
-// "foo.bar.sources" -> "foo.bar.compile" -> "foo.bar.classPath" -> "foo.bar.assembly"
-// "foo.bar.mainClass" -> "foo.bar.assembly"
+// "foo.bar.sources"
+// "foo.bar.compile"
+// "foo.bar.mainClass"
+// "foo.bar.assembly"
+// "foo.bar.classPath"
// }
// subgraph cluster_1 {
// style=dashed
// node [shape=box width=0 height=0 style=filled fillcolor=white]
// label = "foo";
-//
-// "foo.bar.classPath" -> "foo.compile" [constraint=false];
-// "foo.sources" -> "foo.compile" -> "foo.classPath" -> "foo.assembly"
-// "foo.mainClass" -> "foo.assembly"
+// "foo.sources"
+// "foo.compile"
+// "foo.mainClass"
+// "foo.assembly"
+// "foo.classPath"
// }
// subgraph cluster_2 {
// style=dashed
// node [shape=box width=0 height=0 style=filled fillcolor=white]
// label = "qux";
-//
-// "qux.mainClass" -> "qux.assembly"
-// "foo.bar.classPath" -> "foo.classPath"
-// "foo.classPath" -> "qux.compile" [constraint=false];
-// "foo.classPath" -> "qux.classPath"
-// "qux.sources" -> "qux.compile" -> "qux.classPath" -> "qux.assembly"
+// "qux.sources"
+// "qux.compile"
+// "qux.mainClass"
+// "qux.assembly"
+// "qux.classPath"
// }
+// "foo.bar.sources" -> "foo.bar.compile" -> "foo.bar.classPath" -> "foo.bar.assembly"
+// "foo.bar.mainClass" -> "foo.bar.assembly"
+// "foo.bar.classPath" -> "foo.compile" [constraint=false];
+// "foo.sources" -> "foo.compile" -> "foo.classPath" -> "foo.assembly"
+// "foo.mainClass" -> "foo.assembly"
+// "qux.mainClass" -> "qux.assembly"
+// "foo.bar.classPath" -> "foo.classPath"
+// "foo.classPath" -> "qux.compile" [constraint=false];
+// "foo.classPath" -> "qux.classPath"
+// "qux.sources" -> "qux.compile" -> "qux.classPath" -> "qux.assembly"
// }
// ```
//
diff --git a/example/javalib/linting/3-palantirformat/build.mill b/example/javalib/linting/3-palantirformat/build.mill
index fb47fe1e82e..99585d0b834 100644
--- a/example/javalib/linting/3-palantirformat/build.mill
+++ b/example/javalib/linting/3-palantirformat/build.mill
@@ -28,14 +28,14 @@ error: ...palantirformat aborted due to format error(s) (or invalid plugin setti
...checking format in java sources ...
*/
// You can also use Palantir Java Format globally on all ``JavaModule``s in your build by running
-// `mill.javalib.palantirformat.PalantirFormatModule/`.
+// `mill.javalib.palantirformat/`.
/** Usage
-> ./mill mill.javalib.palantirformat.PalantirFormatModule/ # alternatively, use external module to check/format
+> ./mill mill.javalib.palantirformat/ # alternatively, use external module to check/format
...formatting java sources ...
*/
// If entering the long fully-qualified module name
-// `mill.javalib.palantirformat.PalantirFormatModule/` is tedious, you can add
+// `mill.javalib.palantirformat/` is tedious, you can add
// an xref:fundamentals/modules.adoc#_aliasing_external_modules[External Module Alias]
// to give it a shorter name that's easier to type
diff --git a/example/javascriptlib/basic/3-custom-build-logic/build.mill b/example/javascriptlib/basic/3-custom-build-logic/build.mill
index 5e60530428c..b11bdfc8596 100644
--- a/example/javascriptlib/basic/3-custom-build-logic/build.mill
+++ b/example/javascriptlib/basic/3-custom-build-logic/build.mill
@@ -5,8 +5,12 @@ import mill._, javascriptlib._
object foo extends TypeScriptModule {
/** Total number of lines in module source files */
- def lineCount = Task {
- allSources().map(f => os.read.lines(f.path).size).sum
+ def lineCount: T[Int] = Task {
+ sources()
+ .flatMap(pathRef => os.walk(pathRef.path))
+ .filter(_.ext == "ts")
+ .map(os.read.lines(_).size)
+ .sum
}
/** Generate resource using lineCount of sources */
diff --git a/example/javascriptlib/basic/3-custom-build-logic/foo/src/foo.ts b/example/javascriptlib/basic/3-custom-build-logic/foo/src/foo.ts
index c8dfa026363..b1687112d55 100644
--- a/example/javascriptlib/basic/3-custom-build-logic/foo/src/foo.ts
+++ b/example/javascriptlib/basic/3-custom-build-logic/foo/src/foo.ts
@@ -1,6 +1,6 @@
import * as fs from 'fs/promises';
-const Resources: string = process.env.RESOURCESDEST || "@foo/resources.dest" // `RESOURCES` is generated on bundle
+const Resources: string = process.env.RESOURCESDEST || "@foo/resources" // `RESOURCES` is generated on bundle
const LineCount = require.resolve(`${Resources}/line-count.txt`);
export default class Foo {
diff --git a/example/javascriptlib/basic/3-custom-build-logic/foo/test/src/foo/foo.test.ts b/example/javascriptlib/basic/3-custom-build-logic/foo/test/src/foo/foo.test.ts
index 9b30e44134c..9f76b893a5b 100644
--- a/example/javascriptlib/basic/3-custom-build-logic/foo/test/src/foo/foo.test.ts
+++ b/example/javascriptlib/basic/3-custom-build-logic/foo/test/src/foo/foo.test.ts
@@ -1,4 +1,4 @@
-import Foo from 'foo/foo';
+import Foo from '../../../src/foo';
describe('Foo.getLineCount', () => {
beforeEach(() => {
diff --git a/example/javascriptlib/basic/5-client-server-hello/server/src/server.ts b/example/javascriptlib/basic/5-client-server-hello/server/src/server.ts
index 92909dc8bfe..53a14b2ead7 100644
--- a/example/javascriptlib/basic/5-client-server-hello/server/src/server.ts
+++ b/example/javascriptlib/basic/5-client-server-hello/server/src/server.ts
@@ -2,7 +2,7 @@ import * as http from 'http';
import * as fs from 'fs';
import * as path from 'path';
-const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
+const Resources: string = (process.env.RESOURCESDEST || "@server/resources") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);
const server = http.createServer((req, res) => {
diff --git a/example/javascriptlib/basic/6-client-server-realistic/server/src/server.ts b/example/javascriptlib/basic/6-client-server-realistic/server/src/server.ts
index 59b73126f07..a8b7fa82af5 100644
--- a/example/javascriptlib/basic/6-client-server-realistic/server/src/server.ts
+++ b/example/javascriptlib/basic/6-client-server-realistic/server/src/server.ts
@@ -1,9 +1,8 @@
import express, {Express} from 'express';
import cors from 'cors';
-import path from 'path'
import api from "./api"
-const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
+const Resources: string = (process.env.RESOURCESDEST || "@server/resources") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);
const BuildPath = Client.replace(/index\.html$/, "");
const app: Express = express();
diff --git a/example/javascriptlib/dependencies/2-unmanaged-packages/build.mill b/example/javascriptlib/dependencies/2-unmanaged-packages/build.mill
index 670466bd342..83ed12c9793 100644
--- a/example/javascriptlib/dependencies/2-unmanaged-packages/build.mill
+++ b/example/javascriptlib/dependencies/2-unmanaged-packages/build.mill
@@ -2,7 +2,7 @@ package build
import mill._, javascriptlib._
object foo extends TypeScriptModule {
- def unmanagedDeps = Seq.from(os.list(millSourcePath / "lib").map(PathRef(_)))
+ def unmanagedDeps = Seq.from(os.list(moduleDir / "lib").map(PathRef(_)))
}
// You can override `unmanagedDeps` to point to a
diff --git a/example/javascriptlib/dependencies/4-repository-config/foo/src/foo.ts b/example/javascriptlib/dependencies/4-repository-config/foo/src/foo.ts
index aef906e005d..a95c7f34e50 100644
--- a/example/javascriptlib/dependencies/4-repository-config/foo/src/foo.ts
+++ b/example/javascriptlib/dependencies/4-repository-config/foo/src/foo.ts
@@ -1,6 +1,7 @@
import * as fs from 'fs';
import {sortBy} from 'lodash';
-const PackageLock = require.resolve(`../../package-lock.json`);
+
+const PackageLock = require.resolve(`../package-lock.json`);
const args = process.argv.slice(2);
console.log(`Sorted with lodash: [${sortBy(args).join(",")}]`);
diff --git a/example/javascriptlib/module/1-common-config/build.mill b/example/javascriptlib/module/1-common-config/build.mill
index 610e349b885..6170b69f6b1 100644
--- a/example/javascriptlib/module/1-common-config/build.mill
+++ b/example/javascriptlib/module/1-common-config/build.mill
@@ -5,13 +5,11 @@ import mill.javascriptlib._
object foo extends TypeScriptModule {
- def customSource = Task {
- Seq(PathRef(millSourcePath / "custom-src/foo2.ts"))
- }
+ def customSource = Task.Sources("custom-src/foo2.ts")
- def allSources = super.allSources() ++ customSource()
+ def sources = Task { super.sources() ++ customSource() }
- def resources = super.resources() ++ Seq(PathRef(millSourcePath / "custom-resources"))
+ def resources = super.resources() ++ Seq(PathRef(moduleDir / "custom-resources"))
def generatedSources = Task {
for (name <- Seq("A", "B", "C")) os.write(
@@ -29,16 +27,16 @@ object foo extends TypeScriptModule {
def mainFileName = s"foo2.ts"
- def mainFilePath = compile()._2.path / "custom-src" / mainFileName()
+ def mainFilePath = compile().path / "custom-src" / mainFileName()
}
// This example demonstrates usage of common configs
-// Note the use of `millSourcePath`, `Task.dest`, and `PathRef` when preforming
+// Note the use of `moduleDir`, `Task.dest`, and `PathRef` when preforming
// various filesystem operations:
//
-// 1. `millSourcePath`: Base path of the module. For the root module, it's the repo root.
+// 1. `moduleDir`: Base path of the module. For the root module, it's the repo root.
// For inner modules, it's the module path (e.g., `foo/bar/qux` for `foo.bar.qux`). Can be overridden if needed.
//
// 2. `Task.dest`: Destination folder in the `out/` folder for task output.
diff --git a/example/javascriptlib/module/2-custom-tasks/build.mill b/example/javascriptlib/module/2-custom-tasks/build.mill
index a09c38d6679..1197471a260 100644
--- a/example/javascriptlib/module/2-custom-tasks/build.mill
+++ b/example/javascriptlib/module/2-custom-tasks/build.mill
@@ -5,8 +5,12 @@ import mill._, javascriptlib._
object foo extends TypeScriptModule {
/** Total number of lines in module source files */
- def lineCount = Task {
- allSources().map(f => os.read.lines(f.path).size).sum
+ def lineCount: T[Int] = Task {
+ sources()
+ .flatMap(pathRef => os.walk(pathRef.path))
+ .filter(_.ext == "ts")
+ .map(os.read.lines(_).size)
+ .sum
}
def generatedSources = Task {
@@ -31,7 +35,8 @@ object foo extends TypeScriptModule {
// with the boxes representing tasks defined or overridden above and the un-boxed
// labels representing existing Mill tasks:
//
-// ```graphviz
+// [graphviz]
+// ....
// digraph G {
// rankdir=LR
// node [shape=box width=0 height=0]
@@ -45,7 +50,7 @@ object foo extends TypeScriptModule {
//
// "..." [color=white]
// }
-// ```
+// ....
//
// Mill lets you define new cached Tasks using the `Task {...}` syntax,
// depending on existing Tasks e.g. `foo.sources` via the `foo.sources()`
diff --git a/example/javascriptlib/module/3-override-tasks/build.mill b/example/javascriptlib/module/3-override-tasks/build.mill
index 963f80f310e..ceb886fa441 100644
--- a/example/javascriptlib/module/3-override-tasks/build.mill
+++ b/example/javascriptlib/module/3-override-tasks/build.mill
@@ -3,7 +3,9 @@ package build
import mill._
import mill.javascriptlib._
-object foo extends TypeScriptModule {
+object `package` extends RootModule with TypeScriptModule {
+ def moduleName = "foo"
+
def sources = Task {
val srcPath = Task.dest / "src"
val filePath = srcPath / "foo.ts"
@@ -18,7 +20,7 @@ object foo extends TypeScriptModule {
""".stripMargin
)
- PathRef(Task.dest)
+ Seq(PathRef(Task.dest))
}
def compile = Task {
@@ -46,7 +48,7 @@ object foo extends TypeScriptModule {
/** Usage
-> mill foo.run "added tags"
+> mill run "added tags"
Compiling...
Hello World!
Running... added tags
diff --git a/example/javascriptlib/module/5-resources/foo/src/foo.ts b/example/javascriptlib/module/5-resources/foo/src/foo.ts
index 54b1381cf3e..e3d09ecd5f9 100644
--- a/example/javascriptlib/module/5-resources/foo/src/foo.ts
+++ b/example/javascriptlib/module/5-resources/foo/src/foo.ts
@@ -17,4 +17,4 @@ if (process.env.NODE_ENV !== "test") {
console.error('Error:', err);
}
})();
-}
\ No newline at end of file
+}
diff --git a/example/javascriptlib/module/7-root-module/build.mill b/example/javascriptlib/module/7-root-module/build.mill
new file mode 100644
index 00000000000..c6f0c2b06a9
--- /dev/null
+++ b/example/javascriptlib/module/7-root-module/build.mill
@@ -0,0 +1,51 @@
+// You can use ``object `package` extends RootModule`` to use a `Module`
+// as the root module of the file:
+
+package build
+
+import mill._, javascriptlib._
+
+object `package` extends RootModule with TypeScriptModule {
+ def moduleName = "foo"
+ def npmDeps = Seq("immutable@4.3.7")
+ object test extends TypeScriptTests with TestModule.Jest
+}
+
+// Since our ``object `package` extends RootModule``, its files live in a
+// top-level `src/` folder.
+
+// Mill will ordinarily use the name of the singleton object, as
+// the default value for the `moduleName` task.
+
+// For exmaple:
+
+// The `moduleName` for the singleton `bar` defined as
+// `object bar extends TypeScriptModule` would be `bar`,
+// with the expected source directory in `bar/src`.
+
+// For this example, since we use the `RootModule` we would need to manually define our
+// `moduleName`.
+//
+// The `moduleName` is used in the generated `tsconfig` files `compilerOptions['paths']`
+// as the modules `src/` path mapping and to define the default `mainFileName`, which is
+// the modules entry file.
+// The generated mapping for this example would be `"foo/*": [...]`. and its expected main file
+// woudld be `foo.ts` located in top-level `src/`
+
+/** Usage
+> mill run James Bond prof
+Hello James Bond Professor
+
+> mill test
+PASS .../foo.test.ts
+...
+Test Suites:...1 passed, 1 total...
+Tests:...3 passed, 3 total...
+...
+
+> mill show bundle
+Build succeeded!
+
+> node out/bundle.dest/bundle.js James Bond prof
+Hello James Bond Professor
+*/
diff --git a/example/javascriptlib/module/7-root-module/src/foo.ts b/example/javascriptlib/module/7-root-module/src/foo.ts
new file mode 100644
index 00000000000..cda766f1e8a
--- /dev/null
+++ b/example/javascriptlib/module/7-root-module/src/foo.ts
@@ -0,0 +1,33 @@
+import {Map} from 'immutable';
+
+interface User {
+ firstName: string
+ lastName: string
+ role: string
+}
+
+export const defaultRoles: Map = Map({prof: "Professor"});
+
+/**
+ * Generate a user object based on command-line arguments
+ * @param args Command-line arguments
+ * @returns User object
+ */
+export function generateUser(args: string[]): User {
+ return {
+ firstName: args[0] || "unknown",
+ lastName: args[1] || "unknown",
+ role: defaultRoles.get(args[2], ""),
+ };
+}
+
+// Main CLI logic
+if (process.env.NODE_ENV !== "test") {
+ const args = process.argv.slice(2); // Skip 'node' and script name
+ const user = generateUser(args);
+
+ console.log(defaultRoles.toObject());
+ console.log(args[2]);
+ console.log(defaultRoles.get(args[2]));
+ console.log("Hello " + user.firstName + " " + user.lastName + " " + user.role);
+}
\ No newline at end of file
diff --git a/example/javascriptlib/module/7-root-module/test/src/foo.test.ts b/example/javascriptlib/module/7-root-module/test/src/foo.test.ts
new file mode 100644
index 00000000000..0a359977e7c
--- /dev/null
+++ b/example/javascriptlib/module/7-root-module/test/src/foo.test.ts
@@ -0,0 +1,64 @@
+import {generateUser, defaultRoles} from "foo/foo";
+import {Map} from 'immutable';
+
+// Define the type roles object
+type RoleKeys = "admin" | "user";
+type Roles = {
+ [key in RoleKeys]: string;
+};
+
+// Mock `defaultRoles` as a global variable for testing
+const mockDefaultRoles = Map({
+ admin: "Administrator",
+ user: "User",
+});
+
+describe("generateUser function", () => {
+ beforeAll(() => {
+ process.env.NODE_ENV = "test"; // Set NODE_ENV for all tests
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test("should generate a user with all specified fields", () => {
+ // Override the `defaultRoles` map for testing
+ (defaultRoles as any).get = mockDefaultRoles.get.bind(mockDefaultRoles);
+
+ const args = ["John", "Doe", "admin"];
+ const user = generateUser(args);
+
+ expect(user).toEqual({
+ firstName: "John",
+ lastName: "Doe",
+ role: "Administrator",
+ });
+ });
+
+ test("should default lastName and role when they are not provided", () => {
+ (defaultRoles as any).get = mockDefaultRoles.get.bind(mockDefaultRoles);
+
+ const args = ["Jane"];
+ const user = generateUser(args);
+
+ expect(user).toEqual({
+ firstName: "Jane",
+ lastName: "unknown",
+ role: "",
+ });
+ });
+
+ test("should default all fields when args is empty", () => {
+ (defaultRoles as any).get = mockDefaultRoles.get.bind(mockDefaultRoles);
+
+ const args: string[] = [];
+ const user = generateUser(args);
+
+ expect(user).toEqual({
+ firstName: "unknown",
+ lastName: "unknown",
+ role: "",
+ });
+ });
+});
\ No newline at end of file
diff --git a/example/javascriptlib/publishing/1-publish/build.mill b/example/javascriptlib/publishing/1-publish/build.mill
index 7e9107211d6..3cd38763517 100644
--- a/example/javascriptlib/publishing/1-publish/build.mill
+++ b/example/javascriptlib/publishing/1-publish/build.mill
@@ -1,19 +1,20 @@
package build
import mill._, javascriptlib._
+import ujson._
object foo extends PublishModule {
- def publishMeta = Task {
- PublishMeta(
- name = "mill-simple",
- version = "1.0.0",
- description = "A simple Node.js command-line tool",
- files = Seq("README.md"),
- bin = Map(
- "greet" -> "src/foo.js"
- )
+ def pubBundledOut = "dist"
+ def packageJson = PackageJson(
+ name = "mill-simple",
+ version = "1.0.0",
+ description = "A simple Node.js command-line tool",
+ files = Seq("README.md"),
+ bin = ujson.Obj(
+ "greet" -> s"${pubBundledOut()}/src/foo.js"
)
- }
+ )
+
}
// You'll need to define some metadata in the `publishMeta` tasks.
@@ -55,6 +56,16 @@ object foo extends PublishModule {
//// SNIPPET:END
/** Usage
+
+> mill foo.run "James Bond"
+Hello James Bond!
+
+> mill show foo.bundle
+Build succeeded!
+
+> node out/foo/bundle.dest/dist/src/foo.js "James Bond"
+Hello James Bond!
+
> npm i -g mill-simple # install the executable file globally
...
diff --git a/example/javascriptlib/publishing/2-realistic/build.mill b/example/javascriptlib/publishing/2-realistic/build.mill
index 5a6e85ac8ec..be11584199c 100644
--- a/example/javascriptlib/publishing/2-realistic/build.mill
+++ b/example/javascriptlib/publishing/2-realistic/build.mill
@@ -1,6 +1,7 @@
package build
import mill._, javascriptlib._
+import ujson._
object foo extends TypeScriptModule {
object bar extends TypeScriptModule {
@@ -24,19 +25,21 @@ object qux extends PublishModule {
Seq(PathRef(Task.dest))
}
- def exports = Map(
+ def pubBundledOut = "dist"
+
+ def pubExports = Map(
"./qux/generate_user" -> "src/generate_user.js",
"./foo" -> "foo/src/foo.js",
"./foo/bar" -> "foo/bar/src/bar.js"
)
- def publishMeta = PublishMeta(
+ def packageJson = PackageJson(
name = "mill-realistic",
version = "1.0.3",
description = "A simple Node.js command-line tool",
files = Seq("README.md"),
- bin = Map(
- "qux" -> "src/qux.js"
+ bin = ujson.Obj(
+ "qux" -> s"${pubBundledOut()}/src/qux.js"
)
)
@@ -84,6 +87,22 @@ object qux extends PublishModule {
//// SNIPPET:END
/** Usage
+
+> mill qux.run James Bond prof
+{ prof: 'Professor' }
+prof
+Professor
+Hello James Bond Professor
+
+> mill show qux.bundle
+Build succeeded!
+
+> node out/qux/bundle.dest/dist/src/qux.js James Bond prof
+{ prof: 'Professor' }
+prof
+Professor
+Hello James Bond Professor
+
> mill qux.test
PASS .../qux.test.ts
...
diff --git a/example/javascriptlib/testing/1-test-suite/bar/test/src/bar/calculator.test.ts b/example/javascriptlib/testing/1-test-suite/bar/test/src/bar/calculator.test.ts
index 3aeb3cf9324..b04d8ade03e 100644
--- a/example/javascriptlib/testing/1-test-suite/bar/test/src/bar/calculator.test.ts
+++ b/example/javascriptlib/testing/1-test-suite/bar/test/src/bar/calculator.test.ts
@@ -1,4 +1,4 @@
-import {Calculator} from 'bar/calculator';
+import {Calculator} from '../../../src/calculator';
describe('Calculator', () => {
const calculator = new Calculator();
diff --git a/example/javascriptlib/testing/1-test-suite/baz/test/src/baz/calculator.test.ts b/example/javascriptlib/testing/1-test-suite/baz/test/src/baz/calculator.test.ts
index 09bc7d16b00..b04d8ade03e 100644
--- a/example/javascriptlib/testing/1-test-suite/baz/test/src/baz/calculator.test.ts
+++ b/example/javascriptlib/testing/1-test-suite/baz/test/src/baz/calculator.test.ts
@@ -1,4 +1,4 @@
-import { Calculator } from 'baz/calculator';
+import {Calculator} from '../../../src/calculator';
describe('Calculator', () => {
const calculator = new Calculator();
diff --git a/example/javascriptlib/testing/3-integration-suite-cypress/server/src/server.ts b/example/javascriptlib/testing/3-integration-suite-cypress/server/src/server.ts
index d98689f6cb3..b0171e88913 100644
--- a/example/javascriptlib/testing/3-integration-suite-cypress/server/src/server.ts
+++ b/example/javascriptlib/testing/3-integration-suite-cypress/server/src/server.ts
@@ -1,7 +1,7 @@
import express, {Express} from 'express';
import cors from 'cors';
-const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
+const Resources: string = (process.env.RESOURCESDEST || "@server/resources") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);
const app: Express = express();
diff --git a/example/javascriptlib/testing/3-integration-suite-playwright/server/src/server.ts b/example/javascriptlib/testing/3-integration-suite-playwright/server/src/server.ts
index d98689f6cb3..b0171e88913 100644
--- a/example/javascriptlib/testing/3-integration-suite-playwright/server/src/server.ts
+++ b/example/javascriptlib/testing/3-integration-suite-playwright/server/src/server.ts
@@ -1,7 +1,7 @@
import express, {Express} from 'express';
import cors from 'cors';
-const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
+const Resources: string = (process.env.RESOURCESDEST || "@server/resources") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);
const app: Express = express();
diff --git a/example/kotlinlib/linting/2-ktlint/build.mill b/example/kotlinlib/linting/2-ktlint/build.mill
index a9674637cde..504199819b3 100644
--- a/example/kotlinlib/linting/2-ktlint/build.mill
+++ b/example/kotlinlib/linting/2-ktlint/build.mill
@@ -28,6 +28,6 @@ error: ...src/example/FooWrong.kt:6:28: Missing newline before ")" (standard:par
> ./mill ktlint # after fixing the violations, ktlint no longer errors
-> ./mill mill.kotlinlib.ktlint.KtlintModule/ # alternatively, use external module to check/format
+> ./mill mill.kotlinlib.ktlint/ # alternatively, use external module to check/format
*/
diff --git a/example/kotlinlib/linting/3-ktfmt/build.mill b/example/kotlinlib/linting/3-ktfmt/build.mill
index 864c5b8d3d8..d54f0953c43 100644
--- a/example/kotlinlib/linting/3-ktfmt/build.mill
+++ b/example/kotlinlib/linting/3-ktfmt/build.mill
@@ -24,6 +24,6 @@ Done formatting ...src/example/FooWrong.kt
> ./mill ktfmt # after fixing the violations, ktfmt no longer prints any file
-> ./mill mill.kotlinlib.ktfmt.KtfmtModule/ __.sources # alternatively, use external module to check/format
+> ./mill mill.kotlinlib.ktfmt/ __.sources # alternatively, use external module to check/format
*/
diff --git a/example/kotlinlib/linting/4-kover/build.mill b/example/kotlinlib/linting/4-kover/build.mill
index 56a87090da6..7bd5a2011d2 100644
--- a/example/kotlinlib/linting/4-kover/build.mill
+++ b/example/kotlinlib/linting/4-kover/build.mill
@@ -40,7 +40,7 @@ object `package` extends RootModule with KotlinModule with KoverModule {
// `./mill test` then `./mill show kover.htmlReport` and get your
// coverage in HTML format.
// Also reports for all modules can be collected in a single place by
-// running `./mill show mill.kotlinlib.kover.Kover/htmlReportAll`.
+// running `./mill show mill.kotlinlib.kover/htmlReportAll`.
/** Usage
@@ -63,7 +63,7 @@ kover.xmlReport
...
...Kover HTML Report: Overall Coverage Summary...
-> ./mill show mill.kotlinlib.kover.Kover/htmlReportAll # collect reports from all modules
+> ./mill show mill.kotlinlib.kover/htmlReportAll # collect reports from all modules
...
...out/mill/kotlinlib/kover/Kover/htmlReportAll.dest/kover-report...
diff --git a/example/pythonlib/basic/2-custom-build-logic/build.mill b/example/pythonlib/basic/2-custom-build-logic/build.mill
index dbbbde1f9f7..17a24810bdb 100644
--- a/example/pythonlib/basic/2-custom-build-logic/build.mill
+++ b/example/pythonlib/basic/2-custom-build-logic/build.mill
@@ -34,7 +34,8 @@ object foo extends PythonModule {
// provided by `PythonModule` (labelled `resources.super` below), replacing it with the
// `destination` folder of the new `resources` task, which is wired up to `lineCount`:
//
-// ```graphviz
+// [graphviz]
+// ....
// digraph G {
// rankdir=LR
// node [shape=box width=0 height=0 style=filled fillcolor=white]
@@ -45,7 +46,7 @@ object foo extends PythonModule {
// allSourceFiles [color=white]
// run [color=white]
// }
-// ```
+// ....
/** Usage
diff --git a/example/pythonlib/basic/3-multi-module/build.mill b/example/pythonlib/basic/3-multi-module/build.mill
index 3ac3b10d9c7..2ae623e04cf 100644
--- a/example/pythonlib/basic/3-multi-module/build.mill
+++ b/example/pythonlib/basic/3-multi-module/build.mill
@@ -2,7 +2,7 @@ package build
import mill._, pythonlib._
trait MyModule extends PythonModule {
- def resources = super.resources() ++ Seq(PathRef(millSourcePath / "res"))
+ def resources = super.resources() ++ Seq(PathRef(moduleDir / "res"))
object test extends PythonTests with TestModule.Unittest
}
diff --git a/example/pythonlib/dependencies/2-pip-requirements/build.mill b/example/pythonlib/dependencies/2-pip-requirements/build.mill
index 21c313ccaea..f0a1cb590c8 100644
--- a/example/pythonlib/dependencies/2-pip-requirements/build.mill
+++ b/example/pythonlib/dependencies/2-pip-requirements/build.mill
@@ -6,7 +6,7 @@ import mill._, pythonlib._
object `package` extends RootModule with PythonModule {
def pythonRequirementFiles = Task.Sources {
- millSourcePath / "requirements.txt"
+ moduleDir / "requirements.txt"
}
}
diff --git a/example/pythonlib/dependencies/3-unmanaged-wheels/build.mill b/example/pythonlib/dependencies/3-unmanaged-wheels/build.mill
index a2aeb998520..95e27945c0f 100644
--- a/example/pythonlib/dependencies/3-unmanaged-wheels/build.mill
+++ b/example/pythonlib/dependencies/3-unmanaged-wheels/build.mill
@@ -8,7 +8,7 @@ import mill._, pythonlib._
object `package` extends RootModule with PythonModule {
def unmanagedWheels: T[Seq[PathRef]] = Task.Input {
- Seq.from(os.list(millSourcePath / "lib").map(PathRef(_)))
+ Seq.from(os.list(moduleDir / "lib").map(PathRef(_)))
}
}
diff --git a/example/pythonlib/module/1-common-config/build.mill b/example/pythonlib/module/1-common-config/build.mill
index e457e814cb8..68185c63abc 100644
--- a/example/pythonlib/module/1-common-config/build.mill
+++ b/example/pythonlib/module/1-common-config/build.mill
@@ -15,14 +15,12 @@ object foo extends PythonModule {
def mainScript = Task.Source { "custom-src/foo2.py" }
// Add (or replace) source folders for the module to use
- def sources = Task.Sources {
- super.sources() ++ Seq(PathRef(millSourcePath / "custom-src"))
- }
+ def customSources = Task.Sources("custom-src")
+ def sources = Task { super.sources() ++ customSources() }
// Add (or replace) resource folders for the module to use
- def resources = Task.Sources {
- super.resources() ++ Seq(PathRef(millSourcePath / "custom-resources"))
- }
+ def customResources = Task.Sources("custom-resources")
+ def resources = Task { super.resources() ++ customResources() }
// Generate sources at build time
def generatedSources: T[Seq[PathRef]] = Task {
@@ -48,10 +46,10 @@ object foo extends PythonModule {
}
-// Note the use of `millSourcePath`, `Task.dest`, and `PathRef` when preforming
+// Note the use of `moduleDir`, `Task.dest`, and `PathRef` when preforming
// various filesystem operations:
//
-// 1. `millSourcePath`: Base path of the module. For the root module, it's the repo root.
+// 1. `moduleDir`: Base path of the module. For the root module, it's the repo root.
// For inner modules, it's the module path (e.g., `foo/bar/qux` for `foo.bar.qux`). Can be overridden if needed.
//
// 2. `Task.dest`: Destination folder in the `out/` folder for task output.
diff --git a/example/pythonlib/module/2-custom-tasks/build.mill b/example/pythonlib/module/2-custom-tasks/build.mill
index 4731ac56153..ff9f7e983ea 100644
--- a/example/pythonlib/module/2-custom-tasks/build.mill
+++ b/example/pythonlib/module/2-custom-tasks/build.mill
@@ -55,7 +55,8 @@ object foo extends PythonModule {
// with the boxes representing tasks defined or overridden above and the un-boxed
// labels representing existing Mill tasks:
//
-// ```graphviz
+// [graphviz]
+// ....
// digraph G {
// rankdir=LR
// node [shape=box width=0 height=0]
@@ -69,7 +70,7 @@ object foo extends PythonModule {
//
// "..." [color=white]
// }
-// ```
+// ....
//
// Mill lets you define new cached Tasks using the `Task {...}` syntax,
// depending on existing Tasks e.g. `foo.sources` via the `foo.sources()`
diff --git a/example/pythonlib/module/5-resources/build.mill b/example/pythonlib/module/5-resources/build.mill
index 2d279a35dab..966678b369c 100644
--- a/example/pythonlib/module/5-resources/build.mill
+++ b/example/pythonlib/module/5-resources/build.mill
@@ -4,7 +4,9 @@ import mill._, pythonlib._
object foo extends PythonModule {
def mainScript = Task.Source { "src/foo.py" }
- def resources = Task.Sources { super.resources() ++ Seq(PathRef(millSourcePath / "custom")) }
+ // Add (or replace) resource folders for the module to use
+ def customResources = Task.Sources("custom")
+ def resources = Task { super.resources() ++ customResources() }
object test extends PythonTests with TestModule.Unittest {
diff --git a/example/pythonlib/publishing/2-publish-module-advanced/build.mill b/example/pythonlib/publishing/2-publish-module-advanced/build.mill
index 530ef71f1f5..b2b825226ef 100644
--- a/example/pythonlib/publishing/2-publish-module-advanced/build.mill
+++ b/example/pythonlib/publishing/2-publish-module-advanced/build.mill
@@ -39,7 +39,7 @@ object `package` extends RootModule with PythonModule with PublishModule {
def publishVersion = "0.0.3"
// you could also reference an existing setup.py file directly, e.g.
- // `def setup = Task.Source { "setup.py" }`
+ // `def setup = Task.Source {"setup.py" }`
def setup = Task {
val str =
s"""#from setuptools import setup
diff --git a/example/pythonlib/testing/1-test-suite/build.mill b/example/pythonlib/testing/1-test-suite/build.mill
index 9bed86884c6..094dd3c28f1 100644
--- a/example/pythonlib/testing/1-test-suite/build.mill
+++ b/example/pythonlib/testing/1-test-suite/build.mill
@@ -23,7 +23,7 @@ object foo extends PythonModule {
> ./mill foo.run
Hello World
-> ./mill foo.test.test
+> ./mill foo.test.testForked
test_hello (test_foo.TestScript...) ... ok
test_mock (test_foo.TestScript...) ... ok
test_world (test_foo.TestScript...) ... ok
@@ -97,7 +97,7 @@ OK
*/
// * `foo.test`: runs tests in a subprocess in an empty `sandbox/` folder. This is short
-// for `foo.test.test`, as `test` is the default task for ``TestModule``s.
+// for `foo.test.testForked`, as `testForked` is the default task for ``TestModule``s.
/** Usage
diff --git a/example/pythonlib/testing/2-test-deps/build.mill b/example/pythonlib/testing/2-test-deps/build.mill
index 4e0b46bbccf..803a0c960a5 100644
--- a/example/pythonlib/testing/2-test-deps/build.mill
+++ b/example/pythonlib/testing/2-test-deps/build.mill
@@ -32,14 +32,15 @@ object bar extends PythonModule {
// In this example, not only does `foo` depend on `bar`, but we also make
// `foo.test` depend on `bar.test`.
//
-// ```graphviz
+// [graphviz]
+// ....
// digraph G {
// rankdir=LR
// node [shape=box width=0 height=0]
// "bar" -> "bar.test" -> "foo.test"
// "bar" -> "foo" -> "foo.test"
// }
-// ```
+// ....
//
// That lets `foo.test` make use of the
// `BarTestUtils` class that `bar.test` defines, allowing us to re-use this
diff --git a/example/scalalib/linting/1-scalafmt/build.mill b/example/scalalib/linting/1-scalafmt/build.mill
index 910243dc0b7..5d8d955fc7e 100644
--- a/example/scalalib/linting/1-scalafmt/build.mill
+++ b/example/scalalib/linting/1-scalafmt/build.mill
@@ -8,9 +8,9 @@ object `package` extends RootModule with ScalaModule {
/** See Also: .scalafmt.conf */
// Mill supports code formatting via https://scalameta.org/scalafmt/[scalafmt] out of the box.
-// You can reformat your project's code globally with `mill mill.scalalib.scalafmt.ScalafmtModule/` command,
-// specific modules via `mill mill.scalalib.scalafmt.ScalafmtModule/ '{foo,bar}.sources`
-// or only check the code's format with `+mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll`.
+// You can reformat your project's code globally with `mill mill.scalalib.scalafmt/` command,
+// specific modules via `mill mill.scalalib.scalafmt/ '{foo,bar}.sources`
+// or only check the code's format with `+mill mill.scalalib.scalafmt/checkFormatAll`.
// By default, ScalaFmt checks for a `.scalafmt.conf` file at the root of repository.
/** Usage
@@ -27,10 +27,10 @@ Array[String
}
-> mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll
+> mill mill.scalalib.scalafmt/checkFormatAll
error: ...Found 1 misformatted files
-> mill mill.scalalib.scalafmt.ScalafmtModule/
+> mill mill.scalalib.scalafmt/
> cat src/Foo.scala
package foo
@@ -38,7 +38,7 @@ object Foo {
def main(args: Array[String]): Unit = { println("hello world") }
}
-> mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll
+> mill mill.scalalib.scalafmt/checkFormatAll
Everything is formatted already
*/
@@ -48,7 +48,7 @@ Everything is formatted already
/** Usage
> echo "maxColumn: 50" >> .scalafmt.conf
-> mill mill.scalalib.scalafmt.ScalafmtModule/
+> mill mill.scalalib.scalafmt/
> cat src/Foo.scala
package foo
@@ -59,7 +59,7 @@ object Foo {
}
*/
-// If entering the long fully-qualified module name `mill.scalalib.scalafmt.ScalafmtModule/`
+// If entering the long fully-qualified module name `mill.scalalib.scalafmt/`
// is tedious, you can add
// an xref:fundamentals/modules.adoc#_aliasing_external_modules[External Module Alias]
// to give it a shorter name that's easier to type
diff --git a/example/thirdparty/arrow/build.mill b/example/thirdparty/arrow/build.mill
index b79194cb0f3..014dcba19b2 100644
--- a/example/thirdparty/arrow/build.mill
+++ b/example/thirdparty/arrow/build.mill
@@ -641,7 +641,7 @@ Test arrow.resilience...
Test arrow.collectors...
Test arrow.core...
-> ./mill mill.kotlinlib.kover.Kover/htmlReportAll
+> ./mill mill.kotlinlib.kover/htmlReportAll
> ./mill __:^TestModule.docJar
diff --git a/idea/src/mill/idea/GenIdeaImpl.scala b/idea/src/mill/idea/GenIdeaImpl.scala
index 53b43b9c88c..792771f397f 100755
--- a/idea/src/mill/idea/GenIdeaImpl.scala
+++ b/idea/src/mill/idea/GenIdeaImpl.scala
@@ -24,7 +24,7 @@ case class GenIdeaImpl(
)(implicit ctx: Ctx) {
import GenIdeaImpl._
- val workDir: os.Path = evaluators.head.rootModule.millSourcePath
+ val workDir: os.Path = evaluators.head.rootModule.moduleDir
val ideaDir: os.Path = workDir / ".idea"
val ideaConfigVersion = 4
@@ -81,8 +81,8 @@ case class GenIdeaImpl(
.flatMap { case (rootMod, transModules, ev, idx) =>
transModules.collect {
case m: Module =>
- val rootSegs = rootMod.millSourcePath.relativeTo(workDir).segments
- val modSegs = m.millModuleSegments.parts
+ val rootSegs = rootMod.moduleDir.relativeTo(workDir).segments
+ val modSegs = m.moduleSegments.parts
val segments: Seq[String] = rootSegs ++ modSegs
(Segments(segments.map(Segment.Label)), m, ev)
}
@@ -981,7 +981,7 @@ case class GenIdeaImpl(
{
for ((((plugins, params), mods), i) <- settings.toSeq.zip(1 to settings.size))
yield moduleName(m.millModuleSegments)).mkString(",")
+ mods.map(m => moduleName(m.moduleSegments)).mkString(",")
}>
{
@@ -1006,7 +1006,7 @@ object GenIdeaImpl {
/**
* Create the module name (to be used by Idea) for the module based on it segments.
- * @see [[Module.millModuleSegments]]
+ * @see [[Module.moduleSegments]]
*/
def moduleName(p: Segments): String =
p.value
diff --git a/idea/src/mill/idea/package.scala b/idea/src/mill/idea/package.scala
new file mode 100644
index 00000000000..6a20bfdf14e
--- /dev/null
+++ b/idea/src/mill/idea/package.scala
@@ -0,0 +1,5 @@
+package mill.idea
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(GenIdea)
diff --git a/integration/ide/bsp-modules/src/BspModulesTests.scala b/integration/ide/bsp-modules/src/BspModulesTests.scala
index 1ca196f3dec..8db1d7a5016 100644
--- a/integration/ide/bsp-modules/src/BspModulesTests.scala
+++ b/integration/ide/bsp-modules/src/BspModulesTests.scala
@@ -11,7 +11,7 @@ object BspModulesTests extends UtestIntegrationTestSuite {
test("BSP module with foreign modules") {
test("can be installed") - integrationTest { tester =>
import tester._
- assert(eval("mill.bsp.BSP/install").isSuccess)
+ assert(eval("mill.bsp/install").isSuccess)
os.exists(workspacePath / Constants.bspDir / s"${Constants.serverName}.json") ==> true
}
test("ModuleUtils resolves all referenced transitive modules") - integrationTest { tester =>
diff --git a/integration/ide/bsp-server/src/BspInstallDebugTests.scala b/integration/ide/bsp-server/src/BspInstallDebugTests.scala
index f3a96c8384a..95b8efc86d4 100644
--- a/integration/ide/bsp-server/src/BspInstallDebugTests.scala
+++ b/integration/ide/bsp-server/src/BspInstallDebugTests.scala
@@ -15,7 +15,7 @@ object BspInstallDebugTests extends UtestIntegrationTestSuite {
def tests: Tests = Tests {
test("BSP install forwards --debug option to server") - integrationTest { tester =>
import tester._
- eval("mill.bsp.BSP/install").isSuccess ==> true
+ eval("mill.bsp/install").isSuccess ==> true
val jsonFile = workspacePath / Constants.bspDir / s"${Constants.serverName}.json"
assert(os.exists(jsonFile))
val contents = os.read(jsonFile)
diff --git a/integration/ide/bsp-server/src/BspServerTests.scala b/integration/ide/bsp-server/src/BspServerTests.scala
index 78827733401..68843c341dd 100644
--- a/integration/ide/bsp-server/src/BspServerTests.scala
+++ b/integration/ide/bsp-server/src/BspServerTests.scala
@@ -39,7 +39,7 @@ object BspServerTests extends UtestIntegrationTestSuite {
test("requestSnapshots") - integrationTest { tester =>
import tester._
eval(
- "mill.bsp.BSP/install",
+ "mill.bsp/install",
stdout = os.Inherit,
stderr = os.Inherit,
check = true,
diff --git a/integration/ide/gen-idea/src/GenIdeaExtendedTests.scala b/integration/ide/gen-idea/src/GenIdeaExtendedTests.scala
index 94b03529fb6..d4afa8f3c16 100644
--- a/integration/ide/gen-idea/src/GenIdeaExtendedTests.scala
+++ b/integration/ide/gen-idea/src/GenIdeaExtendedTests.scala
@@ -15,7 +15,7 @@ object GenIdeaExtendedTests extends UtestIntegrationTestSuite {
val expectedBase = workspacePath / "idea"
val resources = os.walk(expectedBase).filter(os.isFile).map(_.subRelativeTo(expectedBase))
- eval("mill.idea.GenIdea/")
+ eval("mill.idea/")
for (resource <- resources) {
GenIdeaUtils.assertIdeaXmlResourceMatchesFile(
diff --git a/integration/ide/gen-idea/src/GenIdeaTests.scala b/integration/ide/gen-idea/src/GenIdeaTests.scala
index 404e9345fd4..1cd85ceceaf 100644
--- a/integration/ide/gen-idea/src/GenIdeaTests.scala
+++ b/integration/ide/gen-idea/src/GenIdeaTests.scala
@@ -56,7 +56,7 @@ object GenIdeaTests extends UtestIntegrationTestSuite {
val expectedBase = workspacePath / "idea"
val resources = os.walk(expectedBase).filter(os.isFile).map(_.subRelativeTo(expectedBase))
- eval("mill.idea.GenIdea/")
+ eval("mill.idea/")
val checks = resources.map { resource =>
Try {
diff --git a/integration/invalidation/selective-execution/src/SelectiveExecutionTests.scala b/integration/invalidation/selective-execution/src/SelectiveExecutionTests.scala
index 5777f2ebf34..a3e699a92de 100644
--- a/integration/invalidation/selective-execution/src/SelectiveExecutionTests.scala
+++ b/integration/invalidation/selective-execution/src/SelectiveExecutionTests.scala
@@ -215,12 +215,20 @@ object SelectiveExecutionWatchTests extends UtestIntegrationTestSuite {
!output.contains("Computing fooCommand") && output.contains("Computing barCommand")
}
+ // Test for a bug where modifying the sources 2nd time would run tasks from both modules.
+ output0 = Nil
+ modifyFile(workspacePath / "bar/bar.txt", _ + "!")
+ eventually {
+ !output.contains("Computing fooCommand") && output.contains("Computing barCommand")
+ }
+
output0 = Nil
modifyFile(workspacePath / "foo/foo.txt", _ + "!")
eventually {
output.contains("Computing fooCommand") && !output.contains("Computing barCommand")
}
}
+
test("show-changed-inputs") - integrationTest { tester =>
import tester._
@volatile var output0 = List.empty[String]
diff --git a/javascriptlib/package.mill b/javascriptlib/package.mill
index 139bb47b42d..563142f542d 100644
--- a/javascriptlib/package.mill
+++ b/javascriptlib/package.mill
@@ -6,5 +6,5 @@ import mill._
// TODO change MillPublishScalaModule to MillStableScalaModule after mill version with pythonlib is released,
// because currently there is no previous artifact version
object `package` extends RootModule with build.MillPublishScalaModule {
- def moduleDeps = Seq(build.main, build.scalalib)
+ def moduleDeps = Seq(build.scalalib)
}
diff --git a/javascriptlib/src/mill/javascriptlib/PublishModule.scala b/javascriptlib/src/mill/javascriptlib/PublishModule.scala
index b8b8756295e..cc2203c5ea9 100644
--- a/javascriptlib/src/mill/javascriptlib/PublishModule.scala
+++ b/javascriptlib/src/mill/javascriptlib/PublishModule.scala
@@ -1,8 +1,6 @@
package mill.javascriptlib
import mill.*
-import os.*
-import mill.scalalib.publish.licenseFormat
trait PublishModule extends TypeScriptModule {
@@ -11,229 +9,61 @@ trait PublishModule extends TypeScriptModule {
*
* This is an equivalent of a `package.json`
*/
- def publishMeta: T[PublishModule.PublishMeta]
-
- override def npmDevDeps: T[Seq[String]] =
- Task { Seq("glob@^10.4.5", "ts-patch@3.3.0", "typescript-transform-paths@3.5.3") }
+ override def npmDevDeps: T[Seq[String]] = Task {
+ Seq(
+ "glob@^10.4.5",
+ "ts-patch@3.3.0",
+ "typescript-transform-paths@3.5.3"
+ )
+ }
def pubBundledOut: T[String] = Task { "dist" }
- private def pubDeclarationOut: T[String] = Task { "declarations" }
-
- override def mainFileName: T[String] = Task { s"${millSourcePath.last}.js" }
-
// main file; defined with mainFileName
def pubMain: T[String] =
- Task { pubBundledOut() + "/src/" + mainFileName() }
+ Task { pubBundledOut() + "/src/" + mainFileName().replaceAll("\\.ts", ".js") }
private def pubMainType: T[String] = Task {
- pubMain().replaceFirst(pubBundledOut(), pubDeclarationOut()).replaceAll("\\.js", ".d.ts")
- }
-
- // Define exports for the package
- // by default: mainFile is exported
- // use this to define other exports
- def pubExports: T[Map[String, String]] = Task { Map.empty[String, String] }
-
- private def pubBuildExports: T[Map[String, PublishModule.ExportEntry]] = Task {
- pubExports().map { case (key, value) =>
- key -> PublishModule.Export("./" + pubBundledOut() + "/" + value)
- }
+ pubMain().replaceFirst(pubBundledOut(), declarationDir()).replaceAll("\\.js", ".d.ts")
}
private def pubTypesVersion: T[Map[String, Seq[String]]] = Task {
- pubAllSources().map { source =>
- val dist = source.replaceFirst("typescript", pubBundledOut())
- val declarations = source.replaceFirst("typescript", pubDeclarationOut())
+ tscAllSources().map { source =>
+ val dist = pubBundledOut() + "/" + source // source.replaceFirst("t-", )
+ val declarations = declarationDir() + "/" + source // source.replaceFirst("t-", )
("./" + dist).replaceAll("\\.ts", "") -> Seq(declarations.replaceAll("\\.ts", ".d.ts"))
}.toMap
}
- // build package.json from publishMeta
- // mv to publishDir.dest
- def pubbPackageJson: T[PathRef] = Task { // PathRef
- def splitDeps(input: String): (String, String) = {
- input.split("@", 3).toList match {
- case first :: second :: tail if input.startsWith("@") =>
- ("@" + first + "@" + second, tail.mkString)
- case first :: tail =>
- (first, tail.mkString)
- }
- }
-
- val json = publishMeta()
- val updatedJson = json.copy(
- files = json.files ++ Seq(pubBundledOut(), pubDeclarationOut()),
- main = pubMain(),
- types = pubMainType(),
- exports = Map("." -> PublishModule.Export("./" + pubMain())) ++ pubBuildExports(),
- bin = json.bin.map { case (k, v) => (k, "./" + pubBundledOut() + "/" + v) },
- typesVersions = pubTypesVersion(),
- dependencies = transitiveNpmDeps().map { deps => splitDeps(deps) }.toMap,
- devDependencies = transitiveNpmDeps().map { deps => splitDeps(deps) }.toMap
- ).toJsonClean
-
- os.write.over(Task.dest / "package.json", updatedJson)
-
- PathRef(Task.dest)
- }
-
- // Package.Json construction
-
- // Compilation Options
- override def modulePaths: Task[Seq[(String, String)]] = Task.Anon {
- val module = millSourcePath.last
-
- Seq((s"$module/*", "typescript/src" + ":" + s"${pubDeclarationOut()}")) ++
- resources().map { rp =>
- val resourceRoot = rp.path.last
- val result = (
- s"@$module/$resourceRoot/*",
- resourceRoot match {
- case s if s.contains(".dest") => rp.path.toString
- case _ => s"typescript/$resourceRoot"
- }
+ def pubPackageJson: T[PathRef] = Task {
+ val json = packageJson()
+ val updatedJson =
+ json
+ .copy(
+ files =
+ if (json.files.obj.isEmpty) Seq(pubBundledOut(), declarationDir())
+ else json.files,
+ main = if (json.main.isEmpty) pubMain() else json.main,
+ types = if (json.types.isEmpty) pubMainType() else json.types,
+ exports =
+ if (json.exports.obj.isEmpty) ujson.Obj("." -> ("./" + pubMain())) else json.exports,
+ typesVersions =
+ if (json.typesVersions.value.isEmpty) pubTypesVersion() else json.typesVersions
)
- result
- }
- }
-
- private def pubModDeps: T[Seq[String]] = Task {
- moduleDeps.map { _.millSourcePath.subRelativeTo(Task.workspace).segments.head }.distinct
- }
-
- private def pubModDepsSources: T[Seq[PathRef]] = Task {
- for {
- modSource <- Task.traverse(moduleDeps)(_.sources)
- } yield modSource
- }
-
- private def pubBaseModeGenSources: T[Seq[PathRef]] = Task {
- for {
- pr <- generatedSources()
- file <- os.walk(pr.path)
- if file.ext == "ts"
- } yield PathRef(file)
- }
-
- private def pubModDepsGenSources: T[Seq[PathRef]] = Task {
- Task.traverse(moduleDeps)(_.generatedSources)().flatMap { modSource =>
- val fileExt: Path => Boolean = _.ext == "ts"
- for {
- pr <- modSource
- file <- os.walk(pr.path)
- if fileExt(file)
- } yield PathRef(file)
- }
- }
-
- // mv generated sources for base mod and its deps
- private def pubGenSources: T[Unit] = Task {
- val allGeneratedSources = pubBaseModeGenSources() ++ pubModDepsGenSources()
- allGeneratedSources.foreach { target =>
- val destination = publishDir().path / "typescript/generatedSources" / target.path.last
- os.makeDir.all(destination / os.up)
- os.copy.over(
- target.path,
- destination
- )
- }
- }
-
- private def pubCopyModDeps: T[Unit] = Task {
- val targets = pubModDeps()
-
- targets.foreach { target =>
- val destination = publishDir().path / "typescript" / target
- os.makeDir.all(destination / os.up)
- os.copy(
- Task.workspace / target,
- destination,
- mergeFolders = true
- )
- }
- }
-
- override def resources: T[Seq[PathRef]] = Task {
- val modDepsResources = moduleDeps.map { x => PathRef(x.millSourcePath / "resources") }
- Seq(PathRef(millSourcePath / "resources")) ++ modDepsResources
- }
-
- /**
- * Generate sources relative to publishDir / "typescript"
- */
- private def pubAllSources: T[IndexedSeq[String]] = Task {
- val project = Task.workspace.toString
- val fileExt: Path => Boolean = _.ext == "ts"
- (for {
- source <-
- os.walk(sources().path) ++ pubModDepsSources().toIndexedSeq.flatMap(pr =>
- os.walk(pr.path)
- ).filter(fileExt)
- } yield source.toString
- .replaceFirst(millSourcePath.toString, "typescript")
- .replaceFirst(
- project,
- "typescript"
- )) ++ (pubBaseModeGenSources() ++ pubModDepsGenSources()).map(pr =>
- "typescript/generatedSources/" + pr.path.last
- )
-
- }
-
- override def generatedSourcesPathsBuilder: T[Seq[(String, String)]] = Task {
- Seq("@generated/*" -> "typescript/generatedSources")
- }
+ .cleanJson
- override def upstreamPathsBuilder: Task[Seq[(String, String)]] = Task.Anon {
+ os.write.over(T.dest / "package.json", updatedJson.render(2))
- val upstreams = (for {
- (res, mod) <- Task.traverse(moduleDeps)(_.resources)().zip(moduleDeps)
- } yield {
- val relative = mod.millSourcePath.subRelativeTo(Task.workspace)
- Seq((
- mod.millSourcePath.subRelativeTo(Task.workspace).toString + "/*",
- s"typescript/$relative/src:${pubDeclarationOut()}"
- )) ++
- res.map { rp =>
- val resourceRoot = rp.path.last
- val modName = mod.millSourcePath.subRelativeTo(Task.workspace).toString
- // nb: resources are be moved in bundled stage
- (
- s"@$modName/$resourceRoot/*",
- resourceRoot match {
- case s if s.contains(".dest") =>
- rp.path.toString
- case _ => s"typescript/$modName/$resourceRoot"
- }
- )
- }
-
- }).flatten
-
- upstreams
- }
-
- override def typeRoots: T[ujson.Value] = Task {
- ujson.Arr(
- "node_modules/@types",
- "declarations"
- )
- }
-
- override def declarationDir: T[ujson.Value] = Task {
- ujson.Str("declarations")
+ PathRef(T.dest)
}
- override def compilerOptionsPaths: Task[Map[String, String]] =
- Task.Anon { Map.empty[String, String] }
-
+ // Compilation Options
override def compilerOptions: T[Map[String, ujson.Value]] = Task {
Map(
"declarationMap" -> ujson.Bool(true),
"esModuleInterop" -> ujson.Bool(true),
"baseUrl" -> ujson.Str("."),
- "rootDir" -> ujson.Str("typescript"),
+ "rootDir" -> ujson.Str("."),
"declaration" -> ujson.Bool(true),
"outDir" -> ujson.Str(pubBundledOut()),
"plugins" -> ujson.Arr(
@@ -249,7 +79,11 @@ trait PublishModule extends TypeScriptModule {
)
}
- // patch typescript
+ /**
+ * Patch tsc with custom transformer: `typescript-transform-paths`.
+ * `ts-paths` can now be transformed to their relative path in the
+ * bundled code
+ */
private def pubTsPatchInstall: T[Unit] = Task {
os.call(
("node", npmInstall().path / "node_modules/ts-patch/bin/ts-patch", "install"),
@@ -258,36 +92,20 @@ trait PublishModule extends TypeScriptModule {
()
}
- private def pubSymLink: Task[Unit] = Task {
- pubTsPatchInstall() // patch typescript compiler => use custom transformers
- os.symlink(publishDir().path / "node_modules", npmInstall().path / "node_modules")
+ override def compile: T[PathRef] = Task {
+ tscCopySources()
+ tscCopyModDeps()
+ tscCopyGenSources()
+ tscLinkResources()
+ pubTsPatchInstall()
+ createNodeModulesSymlink()
+ mkTsconfig()
- if (os.exists(npmInstall().path / ".npmrc"))
- os.symlink(publishDir().path / ".npmrc", npmInstall().path / ".npmrc")
- }
+ // build declaration and out dir
+ os.call("node_modules/typescript/bin/tsc", cwd = T.dest)
- override def compile: T[(PathRef, PathRef)] = Task {
- pubSymLink()
- os.write(
- publishDir().path / "tsconfig.json",
- ujson.Obj(
- "compilerOptions" -> ujson.Obj.from(
- compilerOptionsBuilder().toSeq ++ Seq("typeRoots" -> typeRoots())
- ),
- "files" -> pubAllSources()
- )
- )
- os.copy(millSourcePath, publishDir().path / "typescript", mergeFolders = true)
- pubCopyModDeps()
- pubGenSources()
- // Run type check, build declarations
- os.call(
- ("node", npmInstall().path / "node_modules/typescript/bin/tsc"),
- cwd = publishDir().path
- )
- (publishDir(), PathRef(publishDir().path / "typescript"))
+ PathRef(T.dest)
}
-
// Compilation Options
// EsBuild - Copying Resources
@@ -306,7 +124,7 @@ trait PublishModule extends TypeScriptModule {
s""" copyStaticFiles({
| src: ${ujson.Str(rp.toString)},
| dest: ${ujson.Str(
- publishDir().path.toString + "/" + pubBundledOut() + "/" + rp.last
+ compile().path.toString + "/" + pubBundledOut() + "/" + rp.last
)},
| dereference: true,
| preserveTimestamps: true,
@@ -348,192 +166,21 @@ trait PublishModule extends TypeScriptModule {
}
- override def bundle: T[PathRef] = Task {
- val tsnode = npmInstall().path / "node_modules/.bin/ts-node"
- val bundleScript = compile()._1.path / "build.ts"
- val bundle = Task.dest / "bundle.js"
-
- os.write.over(
- bundleScript,
- bundleScriptBuilder()
- )
-
- os.call(
- (tsnode, bundleScript),
- stdout = os.Inherit,
- cwd = compile()._1.path
- )
- PathRef(bundle)
- }
-
// EsBuild - END
- // publishDir; is used to process and compile files for publishing
- def publishDir: T[PathRef] = Task { PathRef(Task.dest) }
-
def publish(): Command[Unit] = Task.Command {
- // build package.json
- os.move(pubbPackageJson().path / "package.json", publishDir().path / "package.json")
+ // bundled code for publishing
+ val bundled = bundle().path / os.up
- // bundle code for publishing
- bundle()
+ os.walk(bundled)
+ .foreach(p => os.copy.over(p, T.dest / p.relativeTo(bundled), createFolders = true))
+
+ // build package.json
+ os.copy.over(pubPackageJson().path / "package.json", T.dest / "package.json")
// run npm publish
- os.call(("npm", "publish"), stdout = os.Inherit, cwd = publishDir().path)
+ os.call(("npm", "publish"), stdout = os.Inherit, cwd = T.dest)
()
}
}
-
-object PublishModule {
- case class PublishMeta(
- name: String,
- version: String,
- description: String,
- main: String = "",
- types: String = "",
- author: String = "",
- license: mill.scalalib.publish.License = mill.scalalib.publish.License.MIT,
- homepage: String = "",
- bin: Map[String, String] = Map.empty[String, String],
- files: Seq[String] = Seq.empty[String],
- scripts: Map[String, String] = Map.empty[String, String],
- engines: Map[String, String] = Map.empty[String, String],
- keywords: Seq[String] = Seq.empty[String],
- repository: Repository = EmptyRepository,
- bugs: Bugs = EmptyBugs,
- dependencies: Map[String, String] = Map.empty[String, String],
- devDependencies: Map[String, String] = Map.empty[String, String],
- publishConfig: PublishConfig = EmptyPubConfig,
- exports: Map[String, ExportEntry] = Map.empty[String, ExportEntry],
- typesVersions: Map[String, Seq[String]] = Map.empty[String, Seq[String]]
- ) {
- def toJson: ujson.Value = ujson.Obj(
- "name" -> name,
- "version" -> version,
- "description" -> description,
- "main" -> main,
- "types" -> types,
- "files" -> ujson.Arr.from(files),
- "scripts" -> ujson.Obj.from(scripts.map { case (k, v) => k -> ujson.Str(v) }),
- "bin" -> ujson.Obj.from(bin.map { case (k, v) => k -> ujson.Str(v) }),
- "engines" -> ujson.Obj.from(engines.map { case (k, v) => k -> ujson.Str(v) }),
- "keywords" -> ujson.Arr.from(keywords),
- "author" -> author,
- "license" -> license.id,
- "repository" -> repository.toJson,
- "bugs" -> bugs.toJson,
- "homepage" -> homepage,
- "dependencies" -> ujson.Obj.from(dependencies.map { case (k, v) => k -> ujson.Str(v) }),
- "devDependencies" -> ujson.Obj.from(devDependencies.map { case (k, v) => k -> ujson.Str(v) }),
- "publishConfig" -> publishConfig.toJson,
- "exports" -> ujson.Obj.from(exports.map { case (key, value) => key -> value.toJson }),
- "typesVersions" -> ujson.Obj((
- "*",
- ujson.Obj.from(typesVersions.map { case (k, v) => k -> ujson.Arr.from(v) })
- ))
- )
-
- def toJsonClean: ujson.Value = removeEmptyValues(toJson)
- }
-
- object PublishMeta {
- implicit val rw: upickle.default.ReadWriter[PublishMeta] = upickle.default.macroRW
- }
-
- case class Repository(`type`: String, url: String) {
- def toJson: ujson.Value = ujson.Obj(
- "type" -> `type`,
- "url" -> url
- )
- }
-
- object Repository {
- implicit val rw: upickle.default.ReadWriter[Repository] = upickle.default.macroRW
- }
-
- object EmptyRepository extends Repository("", "") {
- override def toJson: ujson.Value = ujson.Obj()
- }
-
- case class Bugs(url: String, email: Option[String]) {
- def toJson: ujson.Value = {
- val base = ujson.Obj("url" -> url)
- email.foreach(e => base("email") = e)
- base
- }
- }
-
- object EmptyBugs extends Bugs("", None) {
- override def toJson: ujson.Value = ujson.Obj()
- }
-
- object Bugs {
- implicit val rw: upickle.default.ReadWriter[Bugs] = upickle.default.macroRW
- }
-
- case class PublishConfig(registry: String, access: String) {
- def toJson: ujson.Value = ujson.Obj(
- "registry" -> registry,
- "access" -> access
- )
- }
-
- object EmptyPubConfig extends PublishConfig("", "") {
- override def toJson: ujson.Value = ujson.Obj()
- }
-
- object PublishConfig {
- implicit val rw: upickle.default.ReadWriter[PublishConfig] = upickle.default.macroRW
- }
-
- sealed trait ExportEntry {
- def toJson: ujson.Value
- }
-
- object ExportEntry {
- implicit val rw: upickle.default.ReadWriter[ExportEntry] = upickle.default.macroRW
- }
-
- case class Export(path: String) extends ExportEntry {
- def toJson: ujson.Value = ujson.Str(path)
- }
-
- object Export {
- implicit val rw: upickle.default.ReadWriter[Export] = upickle.default.macroRW
- }
-
- case class ExportConditions(conditions: Map[String, ExportEntry]) extends ExportEntry {
- def toJson: ujson.Value = ujson.Obj.from(conditions.map { case (key, value) =>
- key -> value.toJson
- })
- }
-
- object ExportConditions {
- implicit val rw: upickle.default.ReadWriter[ExportConditions] = upickle.default.macroRW
- }
-
- private def removeEmptyValues(json: ujson.Value): ujson.Value = {
- json match {
- case obj: ujson.Obj =>
- val filtered = obj.value.filterNot { case (_, v) => isEmptyValue(v) }
- val transformed = filtered.map { case (k, v) => k -> removeEmptyValues(v) }
- if (transformed.isEmpty) ujson.Null else ujson.Obj.from(transformed)
- case arr: ujson.Arr =>
- val filtered = arr.value.filterNot(isEmptyValue)
- val transformed = filtered.map(removeEmptyValues)
- if (transformed.isEmpty) ujson.Null else ujson.Arr(transformed)
- case str: ujson.Str if str.value.isEmpty => ujson.Null // Added to remove empty strings
- case other => other
- }
- }
-
- private def isEmptyValue(json: ujson.Value): Boolean = {
- json match {
- case ujson.Str("") | ujson.Null => true
- case _: ujson.Obj | _: ujson.Arr => removeEmptyValues(json) == ujson.Null // crucial check
- case _ => false
- }
- }
-
-}
diff --git a/javascriptlib/src/mill/javascriptlib/ReactScriptsModule.scala b/javascriptlib/src/mill/javascriptlib/ReactScriptsModule.scala
index 14268b7a6fe..0c3904009b0 100644
--- a/javascriptlib/src/mill/javascriptlib/ReactScriptsModule.scala
+++ b/javascriptlib/src/mill/javascriptlib/ReactScriptsModule.scala
@@ -4,14 +4,19 @@ import os.*
// create-react-app: https://create-react-app.dev/docs/documentation-intro
trait ReactScriptsModule extends TypeScriptModule {
+ override def tsDeps: T[Seq[String]] = Task {
+ Seq(
+ "@types/node@16.18.121",
+ "typescript@4.9.5"
+ )
+ }
+
override def npmDeps: T[Seq[String]] = Task {
Seq(
"react@18.3.1",
"react-dom@18.3.1",
"react-scripts@5.0.1",
- "typescript@4.9.5",
"web-vitals@2.1.4",
- "@types/node@16.18.121",
"@types/react@18.3.12",
"@types/react-dom@18.3.1"
)
@@ -27,7 +32,7 @@ trait ReactScriptsModule extends TypeScriptModule {
)
}
- override def sources: Target[PathRef] = Task.Source(millSourcePath)
+ override def sources: Target[Seq[PathRef]] = Task.Sources(moduleDir)
def packageJestOptions: Target[ujson.Obj] = Task {
ujson.Obj(
@@ -64,14 +69,14 @@ trait ReactScriptsModule extends TypeScriptModule {
)
}
- override def compilerOptionsPaths: Task[Map[String, String]] =
- Task.Anon { Map("app/*" -> "src/app/*") }
+ override def compilerOptionsPaths: T[Map[String, String]] =
+ Task { Map("app/*" -> "src/app/*") }
override def compilerOptionsBuilder: Task[Map[String, ujson.Value]] = Task.Anon {
val npm = npmInstall().path
val combinedPaths = compilerOptionsPaths() ++ Seq(
"*" -> npm / "node_modules",
- "typescript" -> npm / "node_modules/typescript"
+ "typescript" -> npm / "node_modules" / "typescript"
)
val combinedCompilerOptions: Map[String, ujson.Value] = compilerOptions() ++ Map(
@@ -82,9 +87,9 @@ trait ReactScriptsModule extends TypeScriptModule {
}
// build react project via react scripts
- override def compile: T[(PathRef, PathRef)] = Task {
+ override def compile: T[PathRef] = Task {
// copy src files
- os.copy(sources().path, Task.dest, mergeFolders = true)
+ sources().foreach(source => os.copy(source.path, Task.dest, mergeFolders = true))
copyNodeModules()
// mk tsconfig.json
@@ -92,7 +97,7 @@ trait ReactScriptsModule extends TypeScriptModule {
Task.dest / "tsconfig.json",
ujson.Obj(
"compilerOptions" -> ujson.Obj.from(compilerOptionsBuilder().toSeq),
- "include" -> ujson.Arr((sources().path / "src").toString)
+ "include" -> ujson.Arr(sources().map(source => (source.path / "src").toString))
)
)
@@ -102,11 +107,11 @@ trait ReactScriptsModule extends TypeScriptModule {
ujson.Obj.from(packageOptions().toSeq)
)
- (PathRef(Task.dest), PathRef(Task.dest / "build"))
+ PathRef(Task.dest)
}
override def bundle: Target[PathRef] = Task {
- val compiled = compile()._1.path
+ val compiled = compile().path
os.call(
("node", compiled / "node_modules/react-scripts/bin/react-scripts.js", "build"),
cwd = compiled,
@@ -114,15 +119,15 @@ trait ReactScriptsModule extends TypeScriptModule {
env = forkEnv()
)
- compile()._2
+ PathRef(compiled / "build")
}
override def forkEnv =
Task {
Map("NODE_PATH" -> Seq(
".",
- compile()._1.path,
- compile()._1.path / "node_modules"
+ compile().path,
+ compile().path / "node_modules"
).mkString(":"))
}
@@ -137,7 +142,7 @@ trait ReactScriptsModule extends TypeScriptModule {
// react-script tests
def test: Target[CommandResult] = Task {
- val compiled = compile()._1.path
+ val compiled = compile().path
os.call(
(
diff --git a/javascriptlib/src/mill/javascriptlib/RsWithServeModule.scala b/javascriptlib/src/mill/javascriptlib/RsWithServeModule.scala
index 61fc110ce2c..66fbebfae6f 100644
--- a/javascriptlib/src/mill/javascriptlib/RsWithServeModule.scala
+++ b/javascriptlib/src/mill/javascriptlib/RsWithServeModule.scala
@@ -10,7 +10,7 @@ trait RsWithServeModule extends ReactScriptsModule {
// serve static Html page
def run: Target[CommandResult] = Task {
- val compiled = compile()._1.path
+ val compiled = compile().path
val build = bundle().path
val env = forkEnv()
os.call(
diff --git a/javascriptlib/src/mill/javascriptlib/TestModule.scala b/javascriptlib/src/mill/javascriptlib/TestModule.scala
index 5e2d0d4008b..553f4c66d92 100644
--- a/javascriptlib/src/mill/javascriptlib/TestModule.scala
+++ b/javascriptlib/src/mill/javascriptlib/TestModule.scala
@@ -5,7 +5,7 @@ import mill.*
trait TestModule extends TaskModule {
import TestModule.TestResult
- def test(args: String*): Command[TestResult] =
+ def testForked(args: String*): Command[TestResult] =
Task.Command {
testTask(Task.Anon { args })()
}
@@ -18,7 +18,7 @@ trait TestModule extends TaskModule {
protected def testTask(args: Task[Seq[String]]): Task[TestResult]
- override def defaultCommandName() = "test"
+ override def defaultCommandName() = "testForked"
}
object TestModule {
@@ -38,19 +38,9 @@ object TestModule {
coverageTask(Task.Anon { args })()
}
- // = '/out'; allow coverage resolve distributed source files.
- // & define coverage files relative to .
- private[TestModule] def coverageSetupSymlinks: Task[Unit] = Task.Anon {
- os.symlink(Task.workspace / "out/node_modules", npmInstall().path / "node_modules")
- os.symlink(Task.workspace / "out/tsconfig.json", compile()._1.path / "tsconfig.json")
- if (os.exists(compile()._1.path / ".nycrc"))
- os.symlink(Task.workspace / "out/.nycrc", compile()._1.path / ".nycrc")
- }
-
def istanbulNycrcConfigBuilder: Task[PathRef] = Task.Anon {
- val compiled = compile()._1.path
val fileName = ".nycrc"
- val config = compiled / fileName
+ val config = T.dest / fileName
val customConfig = Task.workspace / fileName
val content =
@@ -83,7 +73,7 @@ object TestModule {
// coverage files - returnn coverage files directory
def coverageFiles: T[PathRef] = Task {
- val dir = Task.workspace / "out" / s"${moduleDeps.head}_coverage"
+ val dir = compile().path / s"${moduleDeps.head}_coverage"
println(s"coverage files: $dir")
PathRef(dir)
}
@@ -93,16 +83,28 @@ object TestModule {
override def upstreamPathsBuilder: T[Seq[(String, String)]] =
Task {
val stuUpstreams = for {
- ((_, ts), mod) <- Task.traverse(moduleDeps)(_.compile)().zip(moduleDeps)
- } yield (
- mod.millSourcePath.subRelativeTo(Task.workspace).toString + "/test/utils/*",
- (ts.path / "test/src/utils").toString
- )
+ mod <- recModuleDeps
+ } yield {
+ val prefix = mod.moduleName.replaceAll("\\.", "/")
+ (
+ prefix + "/test/utils/*",
+ if (prefix == primeMod)
+ s"test/src/utils" + ":" + s"test/utils"
+ else s"$mod/test/src/utils" + ":" + s"$mod/test/utils"
+ )
+ }
- stuUpstreams ++ super.upstreamPathsBuilder()
+ stuUpstreams
}
- def getPathToTest: T[String] = Task { compile()._2.path.toString }
+ private def primeMod: String = outerModuleName.getOrElse("")
+
+ def testDir: String = {
+ val dir = this.toString.split("\\.").tail.mkString
+ if (dir.isEmpty) dir else dir + "/"
+ }
+
+ def getPathToTest: T[String] = Task { compile().path.toString + "/" + testDir }
}
trait IntegrationSuite extends TypeScriptModule {
@@ -125,13 +127,12 @@ object TestModule {
override def compilerOptions: T[Map[String, ujson.Value]] =
Task {
- super.compilerOptions() + ("resolveJsonModule" -> ujson.Bool(true))
+ Map("resolveJsonModule" -> ujson.Bool(true))
}
def conf: Task[PathRef] = Task.Anon {
- val compiled = compile()._1.path
val fileName = "jest.config.ts"
- val config = compiled / fileName
+ val config = T.dest / fileName
val customConfig = Task.workspace / fileName
val content =
@@ -149,15 +150,16 @@ object TestModule {
| }, {});
|
|export default {
+ |roots: [""],
|preset: 'ts-jest',
|testEnvironment: 'node',
- | testMatch: ['/**/**/**/*.test.ts', '/**/**/**/*.test.js'],
+ |testMatch: ["/$testDir**/?(*.)+(spec|test).[jt]s?(x)"],
|transform: ${ujson.Obj("^.+\\.(ts|tsx)$" -> ujson.Arr.from(Seq(
ujson.Str("ts-jest"),
ujson.Obj("tsconfig" -> "tsconfig.json")
)))},
|moduleFileExtensions: ${ujson.Arr.from(Seq("ts", "tsx", "js", "jsx", "json", "node"))},
- |moduleNameMapper: pathsToModuleNameMapper(sortedModuleDeps)
+ |moduleNameMapper: pathsToModuleNameMapper(sortedModuleDeps, {prefix: ""})
|}
|""".stripMargin
@@ -167,18 +169,27 @@ object TestModule {
PathRef(config)
}
+ override def compile: T[PathRef] = Task {
+ conf()
+ coverageConf()
+ createNodeModulesSymlink()
+ os.copy(super.compile().path, T.dest, mergeFolders = true, replaceExisting = true)
+
+ PathRef(T.dest)
+ }
+
private def runTest: T[TestResult] = Task {
+ val compileDir = compile().path
os.call(
(
- "node",
- npmInstall().path / "node_modules/jest/bin/jest.js",
+ "node_modules/.bin/jest",
"--config",
- conf().path,
+ compileDir / "jest.config.ts",
getPathToTest()
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = compile()._1.path
+ cwd = compileDir
)
()
}
@@ -189,9 +200,8 @@ object TestModule {
// with coverage
def coverageConf: Task[PathRef] = Task.Anon {
- val compiled = compile()._1.path
- val fileName = "jest.config.ts"
- val config = compiled / fileName
+ val fileName = "jest.config.coverage.ts"
+ val config = T.dest / fileName
val customConfig = Task.workspace / fileName
val content =
@@ -209,18 +219,16 @@ object TestModule {
| }, {});
|
|export default {
- |rootDir: ${ujson.Str((Task.workspace / "out").toString)},
+ |roots: [""],
|preset: 'ts-jest',
|testEnvironment: 'node',
- |testMatch: [${ujson.Str(
- s"/${compile()._2.path.subRelativeTo(Task.workspace / "out") / "src"}/**/*.test.ts"
- )}],
+ |testMatch: ["/$testDir**/?(*.)+(spec|test).[jt]s?(x)"],
|transform: ${ujson.Obj("^.+\\.(ts|tsx)$" -> ujson.Arr.from(Seq(
ujson.Str("ts-jest"),
ujson.Obj("tsconfig" -> "tsconfig.json")
)))},
|moduleFileExtensions: ${ujson.Arr.from(Seq("ts", "tsx", "js", "jsx", "json", "node"))},
- |moduleNameMapper: pathsToModuleNameMapper(sortedModuleDeps),
+ |moduleNameMapper: pathsToModuleNameMapper(sortedModuleDeps, {prefix: ""}),
|
|collectCoverage: true,
|collectCoverageFrom: ${ujson.Arr.from(coverageDirs())},
@@ -237,24 +245,20 @@ object TestModule {
}
def runCoverage: T[TestResult] = Task {
- coverageSetupSymlinks()
+ val compileDir = compile().path
os.call(
(
"node",
"node_modules/jest/bin/jest.js",
"--config",
- coverageConf().path,
+ compileDir / "jest.config.coverage.ts",
"--coverage",
getPathToTest()
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = Task.workspace / "out"
+ cwd = compileDir
)
-
- // remove symlink
- os.remove(Task.workspace / "out/node_modules")
- os.remove(Task.workspace / "out/tsconfig.json")
()
}
@@ -274,12 +278,20 @@ object TestModule {
}
override def getPathToTest: T[String] =
- Task { super.getPathToTest() + "/**/**/*.test.ts" }
+ Task { super.getPathToTest() + "/**/*.test.ts" }
+
+ override def compile: T[PathRef] = Task {
+ conf()
+ istanbulNycrcConfigBuilder()
+ createNodeModulesSymlink()
+ os.copy(super.compile().path, T.dest, mergeFolders = true, replaceExisting = true)
+
+ PathRef(T.dest)
+ }
// test-runner.js: run tests on ts files
def conf: Task[PathRef] = Task.Anon {
- val compiled = compile()._1.path
- val runner = compiled / "test-runner.js"
+ val runner = T.dest / "test-runner.js"
val content =
"""|require('ts-node/register');
@@ -293,15 +305,16 @@ object TestModule {
}
private def runTest: T[Unit] = Task {
+ val compileDir = compile().path
os.call(
(
"node",
- conf().path,
+ compileDir / "test-runner.js",
getPathToTest()
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = compile()._1.path
+ cwd = compileDir
)
()
}
@@ -312,24 +325,18 @@ object TestModule {
// with coverage
def runCoverage: T[TestResult] = Task {
- istanbulNycrcConfigBuilder()
- coverageSetupSymlinks()
+ val compileDir = compile().path
os.call(
(
"./node_modules/.bin/nyc",
"node",
- conf().path,
+ compileDir / "test-runner.js",
getPathToTest()
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = Task.workspace / "out"
+ cwd = compileDir
)
-
- // remove symlink
- os.remove(Task.workspace / "out/node_modules")
- os.remove(Task.workspace / "out/tsconfig.json")
- os.remove(Task.workspace / "out/.nycrc")
()
}
}
@@ -348,7 +355,7 @@ object TestModule {
override def compilerOptions: T[Map[String, ujson.Value]] =
Task {
- super.compilerOptions() + (
+ Map(
"target" -> ujson.Str("ESNext"),
"module" -> ujson.Str("ESNext"),
"moduleResolution" -> ujson.Str("Node"),
@@ -360,24 +367,23 @@ object TestModule {
}
def conf: Task[PathRef] = Task.Anon {
- val compiled = compile()._1.path
val fileName = "vitest.config.ts"
- val config = compiled / fileName
+ val config = T.dest / fileName
val customConfig = Task.workspace / fileName
val content =
- """|import { defineConfig } from 'vite';
- |import tsconfigPaths from 'vite-tsconfig-paths';
- |
- |export default defineConfig({
- | plugins: [tsconfigPaths()],
- | test: {
- | globals: true,
- | environment: 'node',
- | include: ['**/**/*.test.ts']
- | },
- |});
- |""".stripMargin
+ s"""|import { defineConfig } from 'vite';
+ |import tsconfigPaths from 'vite-tsconfig-paths';
+ |
+ |export default defineConfig({
+ | plugins: [tsconfigPaths()],
+ | test: {
+ | globals: true,
+ | environment: 'node',
+ | include: ['$testDir**/*.test.ts']
+ | },
+ |});
+ |""".stripMargin
if (!os.exists(customConfig)) os.write.over(config, content)
else os.copy.over(customConfig, config)
@@ -385,19 +391,29 @@ object TestModule {
PathRef(config)
}
+ override def compile: T[PathRef] = Task {
+ conf()
+ coverageConf()
+ createNodeModulesSymlink()
+ os.copy(super.compile().path, T.dest, mergeFolders = true, replaceExisting = true)
+
+ PathRef(T.dest)
+ }
+
private def runTest: T[TestResult] = Task {
+ val compileDir = compile().path
os.call(
(
npmInstall().path / "node_modules/.bin/ts-node",
npmInstall().path / "node_modules/.bin/vitest",
"--run",
"--config",
- conf().path,
+ compileDir / "vitest.config.ts",
getPathToTest()
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = compile()._1.path
+ cwd = compileDir
)
()
}
@@ -408,11 +424,9 @@ object TestModule {
// coverage
def coverageConf: Task[PathRef] = Task.Anon {
- val compiled = compile()._1.path
- val fileName = "vitest.config.ts"
- val config = compiled / fileName
+ val fileName = "vitest.config.coverage.ts"
+ val config = T.dest / fileName
val customConfig = Task.workspace / fileName
-
val content =
s"""|import { defineConfig } from 'vite';
|import tsconfigPaths from 'vite-tsconfig-paths';
@@ -422,9 +436,7 @@ object TestModule {
| test: {
| globals: true,
| environment: 'node',
- | include: [${ujson.Str(
- s"${compile()._2.path.subRelativeTo(Task.workspace / "out") / "src"}/**/*.test.ts"
- )}],
+ | include: ['$testDir**/*.test.ts'],
| coverage: {
| provider: 'v8',
| reporter: ['text', 'json', 'html'],
@@ -445,24 +457,21 @@ object TestModule {
}
def runCoverage: T[TestResult] = Task {
- coverageSetupSymlinks()
+ val compileDir = compile().path
os.call(
(
npmInstall().path / "node_modules/.bin/ts-node",
npmInstall().path / "node_modules/.bin/vitest",
"--run",
"--config",
- coverageConf().path,
+ compileDir / "vitest.config.coverage.ts",
"--coverage",
getPathToTest()
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = Task.workspace / "out"
+ cwd = compileDir
)
- // remove symlink
- os.remove(Task.workspace / "out/node_modules")
- os.remove(Task.workspace / "out/tsconfig.json")
()
}
@@ -482,7 +491,7 @@ object TestModule {
override def compilerOptions: T[Map[String, ujson.Value]] =
Task {
- super.compilerOptions() + (
+ Map(
"target" -> ujson.Str("ES5"),
"module" -> ujson.Str("commonjs"),
"moduleResolution" -> ujson.Str("node"),
@@ -491,13 +500,13 @@ object TestModule {
}
def conf: Task[PathRef] = Task.Anon {
- val path = compile()._1.path / "jasmine.json"
+ val path = T.dest / "jasmine.json"
os.write.over(
path,
ujson.write(
ujson.Obj(
- "spec_dir" -> ujson.Str("typescript/src"),
- "spec_files" -> ujson.Arr(ujson.Str("**/*.test.ts")),
+ "spec_dir" -> ujson.Str("."),
+ "spec_files" -> ujson.Arr(ujson.Str(s"$testDir**/*.test.ts")),
"stopSpecOnExpectationFailure" -> ujson.Bool(false),
"random" -> ujson.Bool(false)
)
@@ -507,8 +516,16 @@ object TestModule {
PathRef(path)
}
- private def runTest: T[Unit] = Task {
+ override def compile: T[PathRef] = Task {
conf()
+ istanbulNycrcConfigBuilder()
+ createNodeModulesSymlink()
+ os.copy(super.compile().path, T.dest, mergeFolders = true, replaceExisting = true)
+
+ PathRef(T.dest)
+ }
+
+ private def runTest: T[Unit] = Task {
val jasmine = "node_modules/jasmine/bin/jasmine.js"
val tsnode = "node_modules/ts-node/register/transpile-only.js"
val tsconfigPath = "node_modules/tsconfig-paths/register.js"
@@ -522,7 +539,7 @@ object TestModule {
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = compile()._1.path
+ cwd = compile().path
)
()
}
@@ -531,49 +548,24 @@ object TestModule {
runTest()
}
- // with coverage
- def coverageConf: T[PathRef] = Task {
- val path = compile()._1.path / "jasmine.json"
- val specDir = compile()._2.path.subRelativeTo(Task.workspace / "out") / "src"
- os.write.over(
- path,
- ujson.write(
- ujson.Obj(
- "spec_dir" -> ujson.Str(specDir.toString),
- "spec_files" -> ujson.Arr(ujson.Str("**/*.test.ts")),
- "stopSpecOnExpectationFailure" -> ujson.Bool(false),
- "random" -> ujson.Bool(false)
- )
- )
- )
- PathRef(path)
- }
-
def runCoverage: T[TestResult] = Task {
- istanbulNycrcConfigBuilder()
- coverageSetupSymlinks()
val jasmine = "node_modules/jasmine/bin/jasmine.js"
val tsnode = "node_modules/ts-node/register/transpile-only.js"
val tsconfigPath = "node_modules/tsconfig-paths/register.js"
- val relConfigPath = coverageConf().path.subRelativeTo(Task.workspace / "out")
+
os.call(
(
"./node_modules/.bin/nyc",
"node",
jasmine,
- s"--config=$relConfigPath",
+ s"--config=jasmine.json",
s"--require=$tsnode",
s"--require=$tsconfigPath"
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = Task.workspace / "out"
+ cwd = compile().path
)
-
- // remove symlink
- os.remove(Task.workspace / "out/node_modules")
- os.remove(Task.workspace / "out/tsconfig.json")
- os.remove(Task.workspace / "out/.nycrc")
()
}
@@ -586,12 +578,12 @@ object TestModule {
)
}
- def testConfigSource: T[PathRef] =
+ def configSource: T[PathRef] =
Task.Source(Task.workspace / "cypress.config.ts")
override def compilerOptions: T[Map[String, ujson.Value]] =
Task {
- super.compilerOptions() + (
+ Map(
"target" -> ujson.Str("ES5"),
"module" -> ujson.Str("ESNext"),
"moduleResolution" -> ujson.Str("Node"),
@@ -606,9 +598,9 @@ object TestModule {
val tsc = npmInstall().path / "node_modules/.bin/tsc"
os.call((
tsc,
- testConfigSource().path.toString,
+ configSource().path.toString,
"--outDir",
- compile()._1.path,
+ compile().path,
"--target",
"ES2020",
"--module",
@@ -636,7 +628,7 @@ object TestModule {
val serviceProcess = os.proc("node", tsnode, "-r", tsconfigpaths, mainFile).spawn(
stdout = os.Inherit,
env = env,
- cwd = service.compile()._1.path
+ cwd = service.compile().path
)
mkConfig()
@@ -648,7 +640,7 @@ object TestModule {
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = compile()._1.path
+ cwd = compile().path
)
serviceProcess.destroy()
@@ -669,16 +661,24 @@ object TestModule {
)
}
- def testConfigSource: T[PathRef] =
+ def configSource: T[PathRef] =
Task.Source(Task.workspace / "playwright.config.ts")
- private def copyConfig: Task[TestResult] = Task.Anon {
+ def conf: Task[TestResult] = Task.Anon {
os.copy.over(
- testConfigSource().path,
- compile()._1.path / "playwright.config.ts"
+ configSource().path,
+ T.dest / configSource().path.last
)
}
+ override def compile: T[PathRef] = Task {
+ conf()
+ createNodeModulesSymlink()
+ os.copy(super.compile().path, T.dest, mergeFolders = true, replaceExisting = true)
+
+ PathRef(T.dest)
+ }
+
private def runTest: T[TestResult] = Task {
val mainFile = service.mainFilePath()
val tsnode = npmInstall().path / "node_modules/.bin/ts-node"
@@ -689,10 +689,9 @@ object TestModule {
val serviceProcess = os.proc("node", tsnode, "-r", tsconfigpaths, mainFile).spawn(
stdout = os.Inherit,
env = env,
- cwd = service.compile()._1.path
+ cwd = service.compile().path
)
- copyConfig()
os.call(
(
"node",
@@ -701,7 +700,7 @@ object TestModule {
),
stdout = os.Inherit,
env = forkEnv(),
- cwd = compile()._1.path
+ cwd = compile().path
)
serviceProcess.destroy()
diff --git a/javascriptlib/src/mill/javascriptlib/TsLintModule.scala b/javascriptlib/src/mill/javascriptlib/TsLintModule.scala
index caca06365e5..948a964f7ea 100644
--- a/javascriptlib/src/mill/javascriptlib/TsLintModule.scala
+++ b/javascriptlib/src/mill/javascriptlib/TsLintModule.scala
@@ -10,20 +10,25 @@ trait TsLintModule extends Module {
private case object Eslint extends Lint
private case object Prettier extends Lint
- def npmLintDeps: T[Seq[String]] = Task { Seq.empty[String] }
+ def npmLintDeps: T[Seq[String]] = Task {
+ Seq(
+ "prettier@3.4.2",
+ "eslint@9.18.0",
+ "typescript-eslint@8.21.0",
+ "@eslint/js@9.18.0"
+ )
+ }
private def npmInstallLint: T[PathRef] = Task {
Try(os.copy.over(T.workspace / ".npmrc", Task.dest / ".npmrc")).getOrElse(())
os.call((
"npm",
"install",
+ "--prefix",
+ ".",
"--userconfig",
".npmrc",
"--save-dev",
- "prettier@3.4.2",
- "eslint@9.18.0",
- "typescript-eslint@8.21.0",
- "@eslint/js@9.18.0",
npmLintDeps()
))
PathRef(Task.dest)
@@ -34,7 +39,11 @@ trait TsLintModule extends Module {
T.workspace / "eslint.config.mjs",
T.workspace / "eslint.config.cjs",
T.workspace / "eslint.config.js",
- T.workspace / ".prettierrc"
+ T.workspace / ".eslintrc.js",
+ T.workspace / ".eslintrc.mjs",
+ T.workspace / ".eslintrc.cjs",
+ T.workspace / ".prettierrc",
+ T.workspace / ".prettierrc.json"
)
private def resolvedFmtConfig: Task[Lint] = Task.Anon {
@@ -47,7 +56,9 @@ trait TsLintModule extends Module {
locs.find(p => os.exists(p.path)) match {
case None =>
- Result.Failure(s"Lint couldn't find an eslint.config.(js|mjs|cjs) or a `.pretiierrc` file")
+ Result.Failure(
+ s"Lint couldn't find an eslint.config.(js|mjs|cjs) or a .eslintrc.(js|mjs|cjs) or a `.pretiierrc` file"
+ )
case Some(c) => Result.Success(lintT(c.path))
}
}
@@ -73,7 +84,7 @@ trait TsLintModule extends Module {
val replacements = Seq(
s"$cwd/" -> "",
"potentially fixable with the `--fix` option" ->
- s"potentially fixable with running ${millSourcePath.last}.reformatAll"
+ s"potentially fixable with running ${moduleDir.last}.reformatAll"
)
os.remove(cwd / "node_modules")
@@ -154,7 +165,7 @@ trait TsLintModule extends Module {
val lines = os.read.lines(logPath)
val logMssg = lines.map(_.replace(
"[warn] Code style issues found in the above file. Run Prettier with --write to fix.",
- s"[warn] Code style issues found. Run ${millSourcePath.last}.reformatAll to fix."
+ s"[warn] Code style issues found. Run ${moduleDir.last}.reformatAll to fix."
))
println(logMssg.mkString("\n"))
case Failure(e: os.SubprocessException) if e.result.exitCode == 2 =>
@@ -208,6 +219,7 @@ trait TsLintModule extends Module {
val content =
s"""|node_modules
|.git
+ |out
|""".stripMargin
os.write.over(config, content)
diff --git a/javascriptlib/src/mill/javascriptlib/TypeScriptModule.scala b/javascriptlib/src/mill/javascriptlib/TypeScriptModule.scala
index f75b5e65202..d37d76556a5 100644
--- a/javascriptlib/src/mill/javascriptlib/TypeScriptModule.scala
+++ b/javascriptlib/src/mill/javascriptlib/TypeScriptModule.scala
@@ -3,13 +3,40 @@ package mill.javascriptlib
import mill.*
import os.*
+import scala.annotation.tailrec
import scala.util.Try
+import mill.scalalib.publish.licenseFormat
trait TypeScriptModule extends Module { outer =>
+ // custom module names
+ def moduleName: String = super.toString
+
+ override def toString: String = moduleName
+
def moduleDeps: Seq[TypeScriptModule] = Nil
+ // recursively retrieve dependecies of all module dependencies
+ def recModuleDeps: Seq[TypeScriptModule] = {
+ @tailrec
+ def recModuleDeps_(
+ t: Seq[TypeScriptModule],
+ acc: Seq[TypeScriptModule]
+ ): Seq[TypeScriptModule] = {
+ if (t.isEmpty) acc
+ else {
+ val currentMod = t.head
+ val cmModDeps = currentMod.moduleDeps
+ recModuleDeps_(t.tail ++ cmModDeps, cmModDeps ++ acc)
+ }
+ }
+
+ recModuleDeps_(moduleDeps, moduleDeps).distinct
+ }
+
def npmDeps: T[Seq[String]] = Task { Seq.empty[String] }
+ def enableEsm: T[Boolean] = Task { false }
+
def npmDevDeps: T[Seq[String]] = Task { Seq.empty[String] }
def unmanagedDeps: T[Seq[PathRef]] = Task { Seq.empty[PathRef] }
@@ -18,179 +45,390 @@ trait TypeScriptModule extends Module { outer =>
Task.traverse(moduleDeps)(_.npmDeps)().flatten ++ npmDeps()
}
- def transitiveNpmDevDeps: T[Seq[String]] = Task {
+ def transitiveNpmDevDeps: T[Seq[String]] =
Task.traverse(moduleDeps)(_.npmDevDeps)().flatten ++ npmDevDeps()
- }
- def transitiveUnmanagedDeps: T[Seq[PathRef]] = Task {
+ def transitiveUnmanagedDeps: T[Seq[PathRef]] =
Task.traverse(moduleDeps)(_.unmanagedDeps)().flatten ++ unmanagedDeps()
+
+ /**
+ * Typescript versioning:
+ * - typescript
+ * - ts-node
+ * - tsconfig-paths
+ * - @types/node
+ */
+ def tsDeps: T[Seq[String]] = Task {
+ Seq(
+ "@types/node@22.10.9",
+ "typescript@5.7.3",
+ "ts-node@^10.9.2",
+ "tsconfig-paths@4.2.0"
+ )
+ }
+
+ /**
+ * Mill will use `esbuild` alongside `ts-node` to bundle Ts project,
+ * to use a custom build tool, you can simply over-ride the default bundle implementation.
+ *
+ * Default bundle versioning:
+ * - esbuild
+ * - esbuild-plugin-copy
+ * - esbuild-plugins/tsconfig-paths
+ * - esbuild-copy-static-files
+ * - @types/esbuild-copy-static-files
+ */
+ private def bundleDeps: T[Seq[String]] = Task {
+ Seq(
+ "@types/esbuild-copy-static-files@0.1.4",
+ "esbuild@0.24.2",
+ "esbuild-plugin-copy@2.1.1",
+ "@esbuild-plugins/tsconfig-paths@0.1.2",
+ "esbuild-copy-static-files@0.1.0"
+ )
}
def npmInstall: T[PathRef] = Task {
Try(os.copy.over(Task.workspace / ".npmrc", Task.dest / ".npmrc")).getOrElse(())
+
+ // build package.json with
+ mkPackageJson()
+
os.call((
"npm",
"install",
+ "--prefix",
+ ".",
"--userconfig",
".npmrc",
"--save-dev",
- "@types/node@22.10.9",
- "@types/esbuild-copy-static-files@0.1.4",
- "typescript@5.7.3",
- "ts-node@^10.9.2",
- "esbuild@0.24.2",
- "esbuild-plugin-copy@2.1.1",
- "@esbuild-plugins/tsconfig-paths@0.1.2",
- "esbuild-copy-static-files@0.1.0",
- "tsconfig-paths@4.2.0",
- transitiveNpmDeps(),
- transitiveNpmDevDeps(),
transitiveUnmanagedDeps().map(_.path.toString)
))
PathRef(Task.dest)
}
- def sources: T[PathRef] = Task.Source("src")
+ // sources :)
+ def sources: T[Seq[PathRef]] = Task.Sources("src")
- def resources: T[Seq[PathRef]] = Task { Seq(PathRef(millSourcePath / "resources")) }
+ def resources: T[Seq[PathRef]] = Task { Seq(PathRef(moduleDir / "resources")) }
def generatedSources: T[Seq[PathRef]] = Task { Seq[PathRef]() }
- def allSources: T[IndexedSeq[PathRef]] =
- Task {
- val fileExt: Path => Boolean = _.ext == "ts"
- os.walk(sources().path).filter(fileExt).map(PathRef(_))
- }
-
- // Generate coverage directories for TestModule
- private[javascriptlib] def coverageDirs: T[Seq[String]] = Task {
- Task.traverse(moduleDeps)(mod => {
- Task.Anon {
- val comp = mod.compile()
- val generated = mod.generatedSources()
- val combined = Seq(comp._2) ++ generated
+ private def tscModDepsResources: T[Seq[(PathRef, Seq[PathRef])]] =
+ Task
+ .traverse(recModuleDeps)(_.resources)()
+ .zip(recModuleDeps)
+ .map { case (r, m) => (PathRef(m.moduleDir), r) }
- combined.map(_.path.subRelativeTo(Task.workspace / "out").toString + "/**/**/*.ts")
- }
- })().flatten
- }
+ private def tscModDepsSources: T[Seq[(PathRef, Seq[PathRef])]] =
+ Task
+ .traverse(recModuleDeps)(_.sources)()
+ .zip(recModuleDeps)
+ .map { case (s, m) => (PathRef(m.moduleDir), s) }
- private[javascriptlib] def compiledSources: Task[IndexedSeq[PathRef]] = Task.Anon {
- val generated = for {
+ private def tscCoreGenSources: T[Seq[PathRef]] = Task {
+ for {
pr <- generatedSources()
file <- os.walk(pr.path)
if file.ext == "ts"
- } yield file
-
- val typescriptOut = Task.dest / "typescript"
- val core = for {
- file <- allSources()
- } yield file.path match {
- case coreS if coreS.startsWith(millSourcePath) =>
- // core - regular sources
- // expected to exist within boundaries of `millSourcePath`
- typescriptOut / coreS.relativeTo(millSourcePath)
- case otherS =>
- // sources defined by a modified source task
- // mv to compile source
- val destinationDir = Task.dest / "typescript/src"
- val fileName = otherS.last
- val destinationFile = destinationDir / fileName
- os.makeDir.all(destinationDir)
- os.copy.over(otherS, destinationFile)
- destinationFile
+ } yield PathRef(file)
+ }
+
+ private def tscModDepsGenSources: T[Seq[(PathRef, Seq[PathRef])]] =
+ Task
+ .traverse(recModuleDeps)(_.generatedSources)()
+ .zip(recModuleDeps)
+ .map { case (s, m) =>
+ (
+ PathRef(m.moduleDir),
+ s.flatMap { genS => os.walk(genS.path).filter(_.ext == "ts").map(PathRef(_)) }
+ )
+ }
+
+ def tscCopySources: Task[Unit] = Task.Anon {
+ val coreTarget = T.dest / "src"
+
+ if (!os.exists(T.dest)) os.makeDir.all(T.dest)
+
+ // Copy everything except "build.mill" and the "/out" directory from Task.workspace
+ os.walk(moduleDir, skip = _.last == "out")
+ .filter(_.last != "build.mill")
+ .filter(_.last != "mill")
+ .filter(_.last != "package.json")
+ .filter(_.last != "package-lock.json")
+ .filter(_.last != "tsconfig.json")
+ .foreach { path =>
+ val relativePath = path.relativeTo(moduleDir)
+ val destination = T.dest / relativePath
+
+ if (os.isDir(path)) os.makeDir.all(destination)
+ else os.copy.over(path, destination)
+ }
+
+ object IsSrcDirectory {
+ def unapply(path: Path): Option[Path] =
+ if (os.isDir(path) && path.last == "src") Some(path) else None
}
- // symlink node_modules for generated sources
- // remove `node_module/` package import format
- generatedSources().foreach(source =>
- os.call(
- ("ln", "-s", npmInstall().path.toString + "/node_modules/", "node_modules"),
- cwd = source.path
+ // handle copy `/out///src` directories
+ def copySrcDirectory(srcDir: Path, targetDir: Path): Unit = {
+ os.list(srcDir).foreach { srcFile =>
+ os.copy.over(srcFile, targetDir / srcFile.last, createFolders = true)
+ }
+ }
+
+ // handle sources generated in /out (eg: `out//sources.dest`)
+ def copyOutSources(sources: Seq[PathRef], target: Path): Unit = {
+
+ def copySource(source: PathRef): Unit = {
+ if (!source.path.startsWith(Task.workspace / "out")) () // Guard clause
+ else os.list(source.path).foreach {
+ case IsSrcDirectory(srcDir) => copySrcDirectory(srcDir, target)
+ case path => os.copy.over(path, target / path.last, createFolders = true)
+ }
+ }
+
+ sources.foreach(copySource)
+ }
+
+ // core
+ copyOutSources(sources(), coreTarget)
+
+ // mod deps
+ tscModDepsSources()
+ .foreach { case (mod, sources_) =>
+ copyOutSources(sources_, T.dest / mod.path.relativeTo(Task.workspace) / "src")
+ }
+
+ }
+
+ private[javascriptlib] def tscCopyModDeps: Task[Unit] = Task.Anon {
+ val targets =
+ recModuleDeps.map { _.moduleDir.subRelativeTo(Task.workspace).segments.head }.distinct
+
+ targets.foreach { target =>
+ val destination = T.dest / target
+ os.makeDir.all(destination / os.up)
+ os.copy(
+ Task.workspace / target,
+ destination,
+ mergeFolders = true
)
- )
+ }
+ }
+
+ // mv generated sources for base mod and its deps
+ private[javascriptlib] def tscCopyGenSources: Task[Unit] = Task.Anon {
+ def copyGeneratedSources(sourcePath: os.Path, destinationPath: os.Path): Unit = {
+ os.makeDir.all(destinationPath / os.up)
+ os.copy.over(sourcePath, destinationPath)
+ }
+
+ tscCoreGenSources().foreach { target =>
+ val destination = T.dest / "generatedSources" / target.path.last
+ copyGeneratedSources(target.path, destination)
+ }
+
+ tscModDepsGenSources().foreach { case (mod, source_) =>
+ source_.foreach { target =>
+ val modDir = mod.path.relativeTo(Task.workspace)
+ val destination = T.dest / modDir / "generatedSources" / target.path.last
+ copyGeneratedSources(target.path, destination)
+ }
+ }
+ }
+
+ /**
+ * Link all external resources eg: `out//resources.dest`
+ * to `moduleDir / src / resources`
+ */
+ private[javascriptlib] def tscLinkResources: Task[Unit] = Task.Anon {
+ val dest = T.dest / "resources"
+ if (!os.exists(dest)) os.makeDir.all(dest)
+
+ val externalResource: PathRef => Boolean = p =>
+ p.path.startsWith(Task.workspace / "out") &&
+ os.exists(p.path) &&
+ os.isDir(p.path)
+
+ def linkResource(resources_ : Seq[PathRef], dest: Path): Unit = {
+ resources_
+ .filter(externalResource)
+ .flatMap(p => os.list(p.path)) // Get all items from valid directories
+ .foreach(item => os.copy.over(item, dest / item.last, createFolders = true))
+ }
+
+ linkResource(resources(), dest)
+
+ tscModDepsResources().foreach { case (mod, r) =>
+ val modDir = mod.path.relativeTo(Task.workspace)
+ val modDest = T.dest / modDir / "resources"
+ if (!os.exists(modDest)) os.makeDir.all(modDest)
+ linkResource(r, modDest)
+ }
+ }
+
+ def tscAllSources: T[IndexedSeq[String]] = Task {
+ val fileExt: Path => Boolean = _.ext == "ts"
+
+ def relativeToTS(base: Path, path: Path, prefix: Option[String] = None): Option[String] =
+ prefix match {
+ case Some(value) => Some(s"$value/${path.relativeTo(base)}")
+ case None => Some(s"${path.relativeTo(base)}")
+ }
+
+ def handleOutTS(base: Path, path: Path, prefix: Option[String] = None): Option[String] = {
+ val segments = path.relativeTo(base).segments
+ val externalSourceDir = base / segments.head
+ prefix match {
+ case Some(_) => relativeToTS(externalSourceDir, path, prefix)
+ case None => relativeToTS(externalSourceDir, path)
+ }
+ }
+
+ def relativeToTypescript(base: Path, path: Path, prefix: String): Option[String] =
+ Some(s"$prefix/${path.relativeTo(base)}")
+
+ def handleOutPath(base: Path, path: Path, prefix: String): Option[String] = {
+ val segments = path.relativeTo(base).segments
+ val externalSourceDir = base / segments.head
+ relativeToTypescript(externalSourceDir, path, prefix)
+ }
+
+ val cores = sources()
+ .toIndexedSeq
+ .flatMap(pr => if (isDir(pr.path)) os.walk(pr.path) else Seq(pr.path))
+ .filter(fileExt)
+ .flatMap { p =>
+ p match {
+ case _ if p.startsWith(moduleDir) && !p.startsWith(moduleDir / "out") =>
+ relativeToTS(moduleDir, p)
+ case _ if p.startsWith(Task.workspace / "out" / moduleName) =>
+ handleOutTS(Task.workspace / "out" / moduleName, p)
+ case _ if p.startsWith(Task.workspace / "out") =>
+ handleOutTS(Task.workspace / "out", p)
+ case _ => None
+ }
+ }
+
+ val modDeps = tscModDepsSources()
+ .toIndexedSeq
+ .flatMap { case (mod, source_) =>
+ source_
+ .flatMap(pr => if (isDir(pr.path)) os.walk(pr.path) else Seq(pr.path))
+ .filter(fileExt)
+ .flatMap { p =>
+ val modDir = mod.path.relativeTo(Task.workspace)
+ val modmoduleDir = Task.workspace / modDir
+ val modOutPath = Task.workspace / "out" / modDir
+
+ p match {
+ case _ if p.startsWith(modmoduleDir) =>
+ relativeToTypescript(modmoduleDir, p, modDir.toString)
+ case _ if p.startsWith(modOutPath) =>
+ handleOutPath(modOutPath, p, modDir.toString)
+ case _ => None
+ }
+
+ }
+ }
+
+ val coreGenSources = tscCoreGenSources()
+ .toIndexedSeq
+ .map(pr => "generatedSources/" + pr.path.last)
+
+ val modGenSources = tscModDepsGenSources()
+ .toIndexedSeq
+ .flatMap { case (mod, source_) =>
+ val modDir = mod.path.relativeTo(Task.workspace)
+ source_.map(s"$modDir/generatedSources/" + _.path.last)
+ }
+
+ cores ++ modDeps ++ coreGenSources ++ modGenSources
- (core ++ generated).map(PathRef(_))
}
+ // sources
+
+ // compile :)
+ def declarationDir: T[String] = Task { "declarations" }
+
// specify tsconfig.compilerOptions
def compilerOptions: T[Map[String, ujson.Value]] = Task {
Map(
+ "skipLibCheck" -> ujson.Bool(true),
"esModuleInterop" -> ujson.Bool(true),
"declaration" -> ujson.Bool(true),
- "emitDeclarationOnly" -> ujson.Bool(true)
- )
+ "emitDeclarationOnly" -> ujson.Bool(true),
+ "baseUrl" -> ujson.Str("."),
+ "rootDir" -> ujson.Str(".")
+ ) ++ Seq(
+ if (enableEsm()) Some("module" -> ujson.Str("nodenext")) else None,
+ if (enableEsm()) Some("moduleResolution" -> ujson.Str("nodenext")) else None
+ ).flatten
}
// specify tsconfig.compilerOptions.Paths
- def compilerOptionsPaths: Task[Map[String, String]] = Task.Anon { Map.empty[String, String] }
-
- def upstreams: T[(PathRef, PathRef, Seq[PathRef])] = Task {
- val comp = compile()
-
- (comp._1, comp._2, resources())
- }
-
- def upstreamPathsBuilder: Task[Seq[(String, String)]] = Task.Anon {
+ def compilerOptionsPaths: T[Map[String, String]] = Task { Map.empty[String, String] }
+ def upstreamPathsBuilder: T[Seq[(String, String)]] = Task {
val upstreams = (for {
- ((comp, ts, res), mod) <- Task.traverse(moduleDeps)(_.upstreams)().zip(moduleDeps)
+ (res, mod) <- Task.traverse(recModuleDeps)(_.resources)().zip(recModuleDeps)
} yield {
- Seq((
- mod.millSourcePath.subRelativeTo(Task.workspace).toString + "/*",
- (ts.path / "src").toString + ":" + (comp.path / "declarations").toString
- )) ++
- res.map { rp =>
- val resourceRoot = rp.path.last
- (
- "@" + mod.millSourcePath.subRelativeTo(Task.workspace).toString + s"/$resourceRoot/*",
- resourceRoot match {
- case s if s.contains(".dest") =>
- rp.path.toString
- case _ =>
- (ts.path / resourceRoot).toString
- }
- )
+ val prefix = mod.moduleName.replaceAll("\\.", "/")
+ val customResource: PathRef => Boolean = pathRef =>
+ pathRef.path.startsWith(Task.workspace / "out" / mod.moduleName) || !pathRef.path.equals(
+ mod.moduleDir / "src" / "resources"
+ )
+
+ val customResources = res
+ .filter(customResource)
+ .map { pathRef =>
+ val resourceRoot = pathRef.path.last
+ s"@$prefix/$resourceRoot/*" -> s"$prefix/$resourceRoot"
}
+ Seq(
+ (
+ prefix + "/*",
+ s"$prefix/src" + ":" + s"declarations/$prefix"
+ ),
+ (s"@$prefix/resources/*", s"$prefix/resources")
+ ) ++ customResources
+
}).flatten
upstreams
}
- def modulePaths: Task[Seq[(String, String)]] = Task.Anon {
- val module = millSourcePath.last
- val typescriptOut = Task.dest / "typescript"
- val declarationsOut = Task.dest / "declarations"
-
- Seq((s"$module/*", (typescriptOut / "src").toString + ":" + declarationsOut.toString)) ++
- resources().map { rp =>
- val resourceRoot = rp.path.last
- val result = (
- s"@$module/$resourceRoot/*",
- resourceRoot match {
- case s if s.contains(".dest") => rp.path.toString
- case _ =>
- (typescriptOut / resourceRoot).toString
- }
- )
- result
+ def modulePaths: T[Seq[(String, String)]] = Task {
+ val customResource: PathRef => Boolean = pathRef =>
+ pathRef.path.startsWith(Task.workspace / "out") || !pathRef.path.equals(
+ moduleDir / "src" / "resources"
+ )
+
+ val customResources = resources()
+ .filter(customResource)
+ .map { pathRef =>
+ val resourceRoot = pathRef.path.last
+ s"@$moduleName/$resourceRoot/*" -> s"$resourceRoot"
}
+
+ Seq(
+ (s"$moduleName/*", "src" + ":" + declarationDir()),
+ (s"@$moduleName/resources/*", "resources")
+ ) ++ customResources
}
def typeRoots: Task[ujson.Value] = Task.Anon {
ujson.Arr(
"node_modules/@types",
- (Task.dest / "declarations").toString
+ declarationDir()
)
}
- def declarationDir: Task[ujson.Value] = Task.Anon {
- ujson.Str((Task.dest / "declarations").toString)
- }
-
def generatedSourcesPathsBuilder: T[Seq[(String, String)]] = Task {
- generatedSources().map(p => ("@generated/*", p.path.toString))
+ Seq(("@generated/*", "generatedSources"))
}
def compilerOptionsBuilder: Task[Map[String, ujson.Value]] = Task.Anon {
@@ -201,7 +439,7 @@ trait TypeScriptModule extends Module { outer =>
compilerOptionsPaths().toSeq
val combinedCompilerOptions: Map[String, ujson.Value] = compilerOptions() ++ Map(
- "declarationDir" -> declarationDir(),
+ "declarationDir" -> ujson.Str(declarationDir()),
"paths" -> ujson.Obj.from(combinedPaths.map { case (k, v) =>
val splitValues =
v.split(":").map(s => s"$s/*") // Split by ":" and append "/*" to each part
@@ -212,35 +450,93 @@ trait TypeScriptModule extends Module { outer =>
combinedCompilerOptions
}
- // create a symlink for node_modules in compile.dest
- // removes need for node_modules prefix in import statements `node_modules/`
- // import * as somepackage from ""
- private def symLink: Task[Unit] = Task.Anon {
- os.symlink(Task.dest / "node_modules", npmInstall().path / "node_modules")
- os.symlink(Task.dest / "package-lock.json", npmInstall().path / "package-lock.json")
+ /**
+ * create a createNodeModulesSymlink for node_modules in compile.dest
+ * removes need for node_modules prefix in import statements `node_modules/`
+ * import * as somepackage from ""
+ */
+ def createNodeModulesSymlink: Task[Unit] = Task.Anon {
+ os.copy.over(npmInstall().path / "package.json", T.dest / "package.json")
+
+ if (!os.exists(T.dest / "node_modules"))
+ os.symlink(T.dest / "node_modules", npmInstall().path / "node_modules")
+
+ if (!os.exists(T.dest / "package-lock.json"))
+ os.symlink(T.dest / "package-lock.json", npmInstall().path / "package-lock.json")
}
- def compile: T[(PathRef, PathRef)] = Task {
- symLink()
- os.write(
- Task.dest / "tsconfig.json",
- ujson.Obj(
- "compilerOptions" -> ujson.Obj.from(
- compilerOptionsBuilder().toSeq ++ Seq("typeRoots" -> typeRoots())
- ),
- "files" -> compiledSources().map(_.path.toString)
- )
+ /**
+ * Run `ts-node` command if set to `true`.
+ * `ts-node` will build declarations and or js output, depending on ts-config.
+ */
+ def runTypeCheck: T[Boolean] = Task { true }
+
+ /**
+ * Mill by default will use local tsconfig if one is provided,
+ * This can set to `false` if you would prefer mill to use the generated
+ * tsconfig, if you can not for some reason just not delete the local one.
+ *
+ * Regardless of the configuration, mill will auto gen a tsconfig
+ * if one does not exist in `T.workspace`.
+ */
+ def customTsConfig: T[Boolean] = Task { true }
+
+ private[javascriptlib] def mkTsconfig: Task[Unit] = Task.Anon {
+ val default: Map[String, ujson.Value] = Map(
+ "compilerOptions" -> ujson.Obj.from(
+ compilerOptionsBuilder().toSeq ++ Seq("typeRoots" -> typeRoots())
+ ),
+ "files" -> tscAllSources()
+ )
+
+ os.write.over(
+ T.dest / "tsconfig.json",
+ ujson.Obj.from(default.toSeq ++ options().toSeq)
)
- os.copy(millSourcePath, Task.dest / "typescript", mergeFolders = true)
- os.call(npmInstall().path / "node_modules/typescript/bin/tsc", cwd = Task.dest)
+ }
+
+ private def mkPackageJson: Task[Unit] = Task.Anon {
+ val packageJson = packageJsonWithDefaults()
+
+ val packageJsonClean = packageJson
+ .copy(`type` = if (enableEsm()) "module" else packageJson.`type`)
+ .cleanJson
+
+ os.write.over(Task.dest / "package.json", packageJsonClean.render(2))
+ }
+
+ def compile: T[PathRef] = Task {
+ tscCopySources()
+ tscCopyModDeps()
+ tscCopyGenSources()
+ tscLinkResources()
+ createNodeModulesSymlink()
+ mkTsconfig()
- (PathRef(Task.dest), PathRef(Task.dest / "typescript"))
+ // Run type check, build declarations
+ if (runTypeCheck())
+ os.call("node_modules/typescript/bin/tsc", cwd = T.dest)
+
+ PathRef(T.dest)
+ }
+
+ // compile
+
+ // additional ts-config options
+ def options: T[Map[String, ujson.Value]] = Task {
+ Seq(
+ Some("exclude" -> ujson.Arr.from(Seq("node_modules", "**/node_modules/*"))),
+ if (enableEsm()) Some("ts-node" -> ujson.Obj("esm" -> ujson.True, "swc" -> ujson.True))
+ else None
+ ).flatten.toMap: Map[String, ujson.Value]
}
- def mainFileName: T[String] = Task { s"${millSourcePath.last}.ts" }
+ // Execution :)
+
+ def mainFileName: T[String] = Task { s"$moduleName.ts" }
- def mainFilePath: T[Path] = Task { compile()._2.path / "src" / mainFileName() }
+ def mainFilePath: T[Path] = Task { compile().path / "src" / mainFileName() }
def forkEnv: T[Map[String, String]] = Task { Map.empty[String, String] }
@@ -250,33 +546,50 @@ trait TypeScriptModule extends Module { outer =>
def run(args: mill.define.Args): Command[CommandResult] = Task.Command {
val mainFile = mainFilePath()
- val tsnode = npmInstall().path / "node_modules/.bin/ts-node"
- val tsconfigpaths = npmInstall().path / "node_modules/tsconfig-paths/register"
val env = forkEnv()
- val execFlags: Seq[String] = executionFlags().map {
- case (key, "") => s"--$key"
- case (key, value) => s"--$key=$value"
- case _ => ""
- }.toSeq
+ val tsnode: String =
+ if (enableEsm()) "ts-node/esm"
+ else (npmInstall().path / "node_modules/.bin/ts-node").toString
+
+ val tsconfigPaths: Seq[String] =
+ Seq(
+ if (enableEsm()) Some("tsconfig-paths/register")
+ else Some((npmInstall().path / "node_modules/tsconfig-paths/register").toString),
+ if (enableEsm()) Some("--no-warnings=ExperimentalWarning") else None
+ ).flatten
+
+ val flags: Seq[String] =
+ (executionFlags()
+ .map {
+ case (key, "") => Some(s"--$key")
+ case (key, value) => Some(s"--$key=$value")
+ case _ => None
+ }.toSeq ++ Seq(if (enableEsm()) Some("--loader") else None)).flatten
+
+ val runnable: Shellable = (
+ "node",
+ flags,
+ tsnode,
+ "-r",
+ tsconfigPaths,
+ mainFile,
+ computedArgs(),
+ args.value
+ )
os.call(
- (
- "node",
- execFlags,
- tsnode,
- "-r",
- tsconfigpaths,
- mainFile,
- computedArgs(),
- args.value
- ),
+ runnable,
stdout = os.Inherit,
env = env,
- cwd = compile()._1.path
+ cwd = compile().path
)
}
+ // Execution
+
+ // bundle :)
+
def bundleExternal: T[Seq[ujson.Value]] = Task { Seq(ujson.Str("fs"), ujson.Str("path")) }
def bundleFlags: T[Map[String, ujson.Value]] = Task {
@@ -287,8 +600,10 @@ trait TypeScriptModule extends Module { outer =>
)
}
- // configure esbuild with @esbuild-plugins/tsconfig-paths
- // include .d.ts files
+ /**
+ * configure esbuild with `@esbuild-plugins/tsconfig-paths`
+ * include .d.ts files
+ */
def bundleScriptBuilder: Task[String] = Task.Anon {
val bundle = (Task.dest / "bundle.js").toString
val rps = resources().map { p => p.path }.filter(os.exists)
@@ -351,25 +666,260 @@ trait TypeScriptModule extends Module { outer =>
def bundle: T[PathRef] = Task {
val env = forkEnv()
val tsnode = npmInstall().path / "node_modules/.bin/ts-node"
- val bundleScript = compile()._1.path / "build.ts"
val bundle = Task.dest / "bundle.js"
+ val out = compile().path
- os.write.over(
- bundleScript,
+ os.walk(out)
+ .foreach(p => os.copy.over(p, T.dest / p.relativeTo(out), createFolders = true))
+
+ os.write(
+ T.dest / "build.ts",
bundleScriptBuilder()
)
os.call(
- (tsnode, bundleScript),
+ (tsnode, T.dest / "build.ts"),
stdout = os.Inherit,
env = env,
- cwd = compile()._1.path
+ cwd = T.dest
)
PathRef(bundle)
}
+ // bundle
+
+ // package.json; package-meta
+ private def packageJsonWithDefaults: T[TypeScriptModule.PackageJson] = Task {
+ def splitDeps(input: String): (String, ujson.Str) = input match {
+ case s if s.startsWith("@") =>
+ val withoutAt = s.drop(1) // Remove leading @
+ val parts = withoutAt.split("@", 2) // Split on the first '@' in the rest
+ ("@" + parts(0), ujson.Str(parts.lift(1).getOrElse(""))) // Re-add '@' to the first part
+
+ case _ =>
+ val parts = input.split("@", 2) // Regular case, split on the first '@'
+ (parts(0), ujson.Str(parts.lift(1).getOrElse(""))) // Handle case where '@' is missing
+ }
+
+ val json = packageJson()
+
+ json.copy(
+ name = if (json.name.nonEmpty) json.name else moduleName,
+ version = if (json.version.nonEmpty) json.version else "1.0.0",
+ dependencies = ujson.Obj.from(transitiveNpmDeps().map(splitDeps)),
+ devDependencies = ujson.Obj.from((
+ transitiveNpmDevDeps() ++
+ tsDeps() ++
+ bundleDeps() ++
+ Seq(if (enableEsm()) Some("@swc/core@1.10.12") else None).flatten
+ ).map(splitDeps))
+ )
+ }
+
+ def packageJson: T[TypeScriptModule.PackageJson] = Task { PackageJson() }
+
+ // test methods :)
+
+ private[javascriptlib] def coverageDirs: T[Seq[String]] = Task { Seq.empty[String] }
+
+ private[javascriptlib] def outerModuleName: Option[String] = None
+
trait TypeScriptTests extends TypeScriptModule {
override def moduleDeps: Seq[TypeScriptModule] = Seq(outer) ++ outer.moduleDeps
+
+ override def outerModuleName: Option[String] = Some(outer.moduleName)
+
+ override def sources: T[Seq[PathRef]] = Task.Sources(moduleDir)
+
+ def allSources: T[IndexedSeq[PathRef]] =
+ Task {
+ val fileExt: Path => Boolean = _.ext == "ts"
+ sources()
+ .toIndexedSeq
+ .flatMap(pr => os.walk(pr.path))
+ .filter(fileExt)
+ .map(PathRef(_))
+ }
+
+ def testResourcesPath: T[Seq[(String, String)]] = Task {
+ Seq((
+ "@test/resources/*",
+ s"test/resources"
+ ))
+ }
+
+ override def compilerOptionsBuilder: T[Map[String, ujson.Value]] = Task {
+ val combinedPaths =
+ outer.upstreamPathsBuilder() ++
+ upstreamPathsBuilder() ++
+ outer.generatedSourcesPathsBuilder() ++
+ outer.modulePaths() ++
+ outer.compilerOptionsPaths().toSeq ++
+ testResourcesPath()
+
+ val combinedCompilerOptions: Map[String, ujson.Value] =
+ outer.compilerOptions() ++ compilerOptions() ++ Map(
+ "declarationDir" -> ujson.Str(outer.declarationDir()),
+ "paths" -> ujson.Obj.from(combinedPaths.map { case (k, v) =>
+ val splitValues =
+ v.split(":").map(s => s"$s/*") // Split by ":" and append "/*" to each part
+ (k, ujson.Arr.from(splitValues))
+ })
+ )
+
+ combinedCompilerOptions
+ }
+
+ override def compile: T[PathRef] = Task {
+ val out = outer.compile()
+
+ val files: IndexedSeq[String] =
+ allSources()
+ .map(x => "test/" + x.path.relativeTo(moduleDir)) ++
+ outer.tscAllSources()
+
+ // mv compile to compile
+ os.list(out.path)
+ .filter(item =>
+ item.last != "tsconfig.json" &&
+ item.last != "package-lock.json" &&
+ !(item.last == "node_modules" && os.isDir(
+ item
+ ))
+ )
+ .foreach(item => os.copy.over(item, T.dest / item.last, createFolders = true))
+
+ // inject test specific tsconfig into tsconfig
+ os.write(
+ Task.dest / "tsconfig.json",
+ ujson.Obj(
+ "compilerOptions" -> ujson.Obj.from(
+ compilerOptionsBuilder().toSeq ++ Seq("typeRoots" -> outer.typeRoots())
+ ),
+ "files" -> files
+ )
+ )
+
+ PathRef(T.dest)
+ }
+
+ override def npmInstall: T[PathRef] = Task {
+ os.call(
+ (
+ "npm",
+ "install",
+ "--prefix",
+ ".",
+ "--userconfig",
+ ".npmrc",
+ "--save-dev",
+ transitiveNpmDeps(),
+ transitiveNpmDevDeps(),
+ transitiveUnmanagedDeps().map(_.path.toString)
+ ),
+ cwd = outer.npmInstall().path
+ )
+ outer.npmInstall()
+ }
+
+ override private[javascriptlib] def coverageDirs: T[Seq[String]] =
+ Task { outer.tscAllSources() }
+
}
}
+
+object TypeScriptModule {
+ case class PackageJson(
+ name: String = "",
+ version: String = "",
+ description: String = "",
+ main: String = "",
+ module: String = "",
+ `type`: String = "",
+ types: String = "",
+ author: String = "",
+ license: mill.scalalib.publish.License = mill.scalalib.publish.License.MIT,
+ homepage: String = "",
+ bin: ujson.Obj = ujson.Obj(),
+ files: ujson.Arr = Seq.empty[String],
+ scripts: ujson.Obj = ujson.Obj(),
+ engines: ujson.Obj = ujson.Obj(),
+ keywords: ujson.Arr = Seq.empty[String],
+ repository: ujson.Obj = ujson.Obj(),
+ bugs: ujson.Obj = ujson.Obj(),
+ dependencies: ujson.Obj = ujson.Obj(),
+ devDependencies: ujson.Obj = ujson.Obj(),
+ publishConfig: ujson.Obj = ujson.Obj(),
+ exports: ujson.Obj = ujson.Obj(),
+ typesVersions: ujson.Obj = ujson.Obj(),
+ `private`: Boolean = false,
+ peerDependencies: ujson.Obj = ujson.Obj(),
+ optionalDependencies: ujson.Obj = ujson.Obj(),
+ overrides: ujson.Obj = ujson.Obj(),
+ funding: ujson.Obj = ujson.Obj(),
+ contributors: ujson.Arr = Seq.empty[String],
+ sideEffects: Boolean = false,
+ resolutions: ujson.Obj = ujson.Obj()
+ ) {
+ def cleanJson: ujson.Value = removeEmptyValues(ujson.Obj(
+ "name" -> name,
+ "version" -> version,
+ "description" -> description,
+ "main" -> main,
+ "module" -> module,
+ "type" -> `type`,
+ "types" -> types,
+ "files" -> files,
+ "scripts" -> scripts,
+ "bin" -> bin,
+ "engines" -> engines,
+ "keywords" -> keywords,
+ "author" -> author,
+ "license" -> license.id,
+ "repository" -> repository,
+ "bugs" -> bugs,
+ "homepage" -> homepage,
+ "dependencies" -> dependencies,
+ "devDependencies" -> devDependencies,
+ "publishConfig" -> publishConfig,
+ "exports" -> exports,
+ "typesVersions" -> typesVersions,
+ "private" -> `private`,
+ "peerDependencies" -> peerDependencies,
+ "optionalDependencies" -> optionalDependencies,
+ "overrides" -> overrides,
+ "funding" -> funding,
+ "contributors" -> contributors,
+ "sideEffects" -> sideEffects,
+ "resolutions" -> resolutions
+ ))
+ }
+
+ object PackageJson {
+ implicit val rw: upickle.default.ReadWriter[PackageJson] = upickle.default.macroRW
+ }
+
+ private def removeEmptyValues(json: ujson.Value): ujson.Value = {
+ json match {
+ case obj: ujson.Obj =>
+ val filtered = obj.value.filterNot { case (_, v) => isEmptyValue(v) }
+ val transformed = filtered.map { case (k, v) => k -> removeEmptyValues(v) }
+ if (transformed.isEmpty) ujson.Null else ujson.Obj.from(transformed)
+ case arr: ujson.Arr =>
+ val filtered = arr.value.filterNot(isEmptyValue)
+ val transformed = filtered.map(removeEmptyValues)
+ if (transformed.isEmpty) ujson.Null else ujson.Arr(transformed)
+ case str: ujson.Str if str.value.isEmpty => ujson.Null // Added to remove empty strings
+ case other => other
+ }
+ }
+
+ private def isEmptyValue(json: ujson.Value): Boolean = {
+ json match {
+ case ujson.Str("") | ujson.Null => true
+ case _: ujson.Obj | _: ujson.Arr => removeEmptyValues(json) == ujson.Null // crucial check
+ case _ => false
+ }
+ }
+}
diff --git a/javascriptlib/src/mill/javascriptlib/package.scala b/javascriptlib/src/mill/javascriptlib/package.scala
index c70da0e6f3f..c65e0df02ab 100644
--- a/javascriptlib/src/mill/javascriptlib/package.scala
+++ b/javascriptlib/src/mill/javascriptlib/package.scala
@@ -7,12 +7,6 @@ package object javascriptlib {
type License = mill.scalalib.publish.License
val License = mill.scalalib.publish.License
- type PublishMeta = PublishModule.PublishMeta
- val PublishMeta = PublishModule.PublishMeta
-
- type Export = PublishModule.Export
- val Export = PublishModule.Export
-
- type ExportConditions = PublishModule.ExportConditions
- val ExportConditions = PublishModule.ExportConditions
+ type PackageJson = TypeScriptModule.PackageJson
+ val PackageJson = TypeScriptModule.PackageJson
}
diff --git a/javascriptlib/test/src/mill/javascriptlib/HelloWorldTests.scala b/javascriptlib/test/src/mill/javascriptlib/HelloWorldTests.scala
index 942181f1650..04a0003560f 100644
--- a/javascriptlib/test/src/mill/javascriptlib/HelloWorldTests.scala
+++ b/javascriptlib/test/src/mill/javascriptlib/HelloWorldTests.scala
@@ -1,6 +1,7 @@
package mill.javascriptlib
-import mill._
+import mill.*
+import mill.define.Discover
import mill.testkit.{TestBaseModule, UnitTester}
import utest.*
@@ -19,6 +20,8 @@ object HelloWorldTests extends TestSuite {
object qux extends TypeScriptModule {
override def moduleDeps: Seq[TypeScriptModule] = Seq(foo, foo.bar)
}
+
+ override lazy val millDiscover = Discover[this.type]
}
val resourcePath = os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "hello-world-typescript"
@@ -28,7 +31,7 @@ object HelloWorldTests extends TestSuite {
val baos = new ByteArrayOutputStream()
val eval = UnitTester(HelloWorldJavascript, resourcePath, outStream = new PrintStream(baos))
- val Right(result) = eval.apply(HelloWorldJavascript.qux.run(Args("James")))
+ val Right(result) = eval.apply(HelloWorldJavascript.qux.run(Args("James"))): @unchecked
assert(baos.toString() == "Hello James Qux\n")
}
diff --git a/kotlinlib/src/mill/kotlinlib/KotlinMavenModule.scala b/kotlinlib/src/mill/kotlinlib/KotlinMavenModule.scala
index 979e9a283d7..6642cda4238 100644
--- a/kotlinlib/src/mill/kotlinlib/KotlinMavenModule.scala
+++ b/kotlinlib/src/mill/kotlinlib/KotlinMavenModule.scala
@@ -11,7 +11,7 @@ trait KotlinMavenModule extends KotlinModule with MavenModule {
override def sources = super.sources() ++ sources0()
trait KotlinMavenTests extends KotlinTests with MavenTests {
- override def intellijModulePath: os.Path = millSourcePath / "src/test"
+ override def intellijModulePath: os.Path = moduleDir / "src/test"
private def sources0 = Task.Sources("src/test/kotlin")
override def sources = super.sources() ++ sources0()
diff --git a/kotlinlib/src/mill/kotlinlib/KotlinModule.scala b/kotlinlib/src/mill/kotlinlib/KotlinModule.scala
index ccc41ecc20b..c79a68e9bf5 100644
--- a/kotlinlib/src/mill/kotlinlib/KotlinModule.scala
+++ b/kotlinlib/src/mill/kotlinlib/KotlinModule.scala
@@ -11,7 +11,7 @@ import mill.define.{Command, ModuleRef, Task}
import mill.kotlinlib.worker.api.{KotlinWorker, KotlinWorkerTarget}
import mill.scalalib.api.{CompilationResult, ZincWorkerApi}
import mill.scalalib.bsp.{BspBuildTarget, BspModule}
-import mill.scalalib.{JavaModule, Lib, ZincWorkerModule}
+import mill.scalalib.{JavaModule, JvmWorkerModule, Lib}
import mill.util.Jvm
import mill.{Agg, T}
@@ -82,7 +82,7 @@ trait KotlinModule extends JavaModule { outer =>
type CompileProblemReporter = mill.api.CompileProblemReporter
- protected def zincWorkerRef: ModuleRef[ZincWorkerModule] = zincWorker
+ protected def zincWorkerRef: ModuleRef[JvmWorkerModule] = jvmWorker
protected def kotlinWorkerRef: ModuleRef[KotlinWorkerModule] = ModuleRef(KotlinWorkerModule)
diff --git a/kotlinlib/src/mill/kotlinlib/PlatformKotlinModule.scala b/kotlinlib/src/mill/kotlinlib/PlatformKotlinModule.scala
index 84229d5b756..29511aca365 100644
--- a/kotlinlib/src/mill/kotlinlib/PlatformKotlinModule.scala
+++ b/kotlinlib/src/mill/kotlinlib/PlatformKotlinModule.scala
@@ -7,7 +7,7 @@ import mill.scalalib.PlatformModuleBase
* It supports additional source directories per platform, e.g. `src-jvm/` or
* `src-js/`.
*
- * Adjusts the [[millSourcePath]] and [[artifactNameParts]] to ignore the last
+ * Adjusts the [[moduledir]] and [[artifactNameParts]] to ignore the last
* path segment, which is assumed to be the name of the platform the module is
* built against and not something that should affect the filesystem path or
* artifact name
diff --git a/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala b/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala
index 1095afd0343..235301f7622 100644
--- a/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala
+++ b/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala
@@ -30,7 +30,7 @@ import mill.scalalib.TestModule.Junit5
trait AndroidAppKotlinModule extends AndroidAppModule with KotlinModule { outer =>
override def sources: T[Seq[PathRef]] =
- super[AndroidAppModule].sources() :+ PathRef(millSourcePath / "src/main/kotlin")
+ super[AndroidAppModule].sources() :+ PathRef(moduleDir / "src/main/kotlin")
override def kotlincPluginIvyDeps = Task {
val kv = kotlinVersion()
@@ -57,7 +57,7 @@ trait AndroidAppKotlinModule extends AndroidAppModule with KotlinModule { outer
trait AndroidAppKotlinTests extends AndroidAppTests with KotlinTests {
override def sources: T[Seq[PathRef]] =
- super[AndroidAppTests].sources() ++ Seq(PathRef(outer.millSourcePath / "src/test/kotlin"))
+ super[AndroidAppTests].sources() ++ Seq(PathRef(outer.moduleDir / "src/test/kotlin"))
}
trait AndroidAppKotlinInstrumentedTests extends AndroidAppKotlinModule
@@ -68,7 +68,7 @@ trait AndroidAppKotlinModule extends AndroidAppModule with KotlinModule { outer
override def sources: T[Seq[PathRef]] =
super[AndroidAppInstrumentedTests].sources() :+ PathRef(
- outer.millSourcePath / "src/androidTest/kotlin"
+ outer.moduleDir / "src/androidTest/kotlin"
)
}
@@ -99,8 +99,8 @@ trait AndroidAppKotlinModule extends AndroidAppModule with KotlinModule { outer
override def sources: T[Seq[PathRef]] = Task.Sources(
Seq(
- PathRef(outer.millSourcePath / "src/screenshotTest/kotlin"),
- PathRef(outer.millSourcePath / "src/screenshotTest/java")
+ PathRef(outer.moduleDir / "src/screenshotTest/kotlin"),
+ PathRef(outer.moduleDir / "src/screenshotTest/java")
)
)
diff --git a/kotlinlib/src/mill/kotlinlib/detekt/DetektModule.scala b/kotlinlib/src/mill/kotlinlib/detekt/DetektModule.scala
index 527e8932ffe..7d8a43e80af 100644
--- a/kotlinlib/src/mill/kotlinlib/detekt/DetektModule.scala
+++ b/kotlinlib/src/mill/kotlinlib/detekt/DetektModule.scala
@@ -31,7 +31,7 @@ trait DetektModule extends KotlinModule {
mainClass = "io.gitlab.arturbosch.detekt.cli.Main",
classPath = detektClasspath().map(_.path).toVector,
mainArgs = args,
- cwd = millSourcePath, // allow passing relative paths for sources like src/a/b
+ cwd = moduleDir, // allow passing relative paths for sources like src/a/b
stdin = os.Inherit,
stdout = os.Inherit,
check = false
diff --git a/kotlinlib/src/mill/kotlinlib/kover/KoverModule.scala b/kotlinlib/src/mill/kotlinlib/kover/KoverModule.scala
index 89165463f99..4ebef2af109 100644
--- a/kotlinlib/src/mill/kotlinlib/kover/KoverModule.scala
+++ b/kotlinlib/src/mill/kotlinlib/kover/KoverModule.scala
@@ -121,8 +121,8 @@ trait KoverModule extends KotlinModule { outer =>
* all modules that extend [[KoverModule]].
*
* - ./mill __.test # run tests for all modules
- * - ./mill mill.kotlinlib.kover.Kover/htmlReportAll # generates report in html format for all modules
- * - ./mill mill.kotlinlib.kover.Kover/xmlReportAll # generates report in xml format for all modules
+ * - ./mill mill.kotlinlib.kover/htmlReportAll # generates report in html format for all modules
+ * - ./mill mill.kotlinlib.kover/xmlReportAll # generates report in xml format for all modules
*
* The aggregated report will be available at either `out/mill/kotlinlib/contrib/kover/Kover/htmlReportAll.dest/`
* for html reports or `out/mill/kotlinlib/contrib/kover/Kover/xmlReportAll.dest/` for xml reports.
diff --git a/kotlinlib/src/mill/kotlinlib/kover/package.scala b/kotlinlib/src/mill/kotlinlib/kover/package.scala
new file mode 100644
index 00000000000..a22c63d8ec9
--- /dev/null
+++ b/kotlinlib/src/mill/kotlinlib/kover/package.scala
@@ -0,0 +1,9 @@
+/*
+ * Some parts of this code are taken from lefou/mill-jacoco. Copyright 2021-Present Tobias Roeser.
+ */
+
+package mill.kotlinlib.kover
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(Kover)
diff --git a/kotlinlib/src/mill/kotlinlib/ktfmt/KtfmtModule.scala b/kotlinlib/src/mill/kotlinlib/ktfmt/KtfmtModule.scala
index 4ea4ff2dcb8..6ea95bb6740 100644
--- a/kotlinlib/src/mill/kotlinlib/ktfmt/KtfmtModule.scala
+++ b/kotlinlib/src/mill/kotlinlib/ktfmt/KtfmtModule.scala
@@ -124,7 +124,7 @@ object KtfmtModule extends ExternalModule with KtfmtBaseModule with TaskModule {
mainClass = "com.facebook.ktfmt.cli.Main",
classPath = classPath.map(_.path).toVector,
mainArgs = args.result(),
- cwd = millSourcePath, // allow passing relative paths for sources like src/a/b
+ cwd = moduleDir, // allow passing relative paths for sources like src/a/b
stdin = os.Inherit,
stdout = os.Inherit,
check = false
diff --git a/kotlinlib/src/mill/kotlinlib/ktfmt/package.scala b/kotlinlib/src/mill/kotlinlib/ktfmt/package.scala
new file mode 100644
index 00000000000..72d7062c7db
--- /dev/null
+++ b/kotlinlib/src/mill/kotlinlib/ktfmt/package.scala
@@ -0,0 +1,5 @@
+package mill.kotlinlib.ktfmt
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(KtfmtModule)
diff --git a/kotlinlib/src/mill/kotlinlib/ktlint/KtlintModule.scala b/kotlinlib/src/mill/kotlinlib/ktlint/KtlintModule.scala
index 22e361daac7..685a183ad5a 100644
--- a/kotlinlib/src/mill/kotlinlib/ktlint/KtlintModule.scala
+++ b/kotlinlib/src/mill/kotlinlib/ktlint/KtlintModule.scala
@@ -126,7 +126,7 @@ object KtlintModule extends ExternalModule with KtlintModule with TaskModule {
mainClass = "com.pinterest.ktlint.Main",
classPath = classPath.map(_.path).toVector,
mainArgs = args.result(),
- cwd = millSourcePath,
+ cwd = moduleDir,
stdin = os.Inherit,
stdout = os.Inherit,
check = false
diff --git a/kotlinlib/src/mill/kotlinlib/ktlint/package.scala b/kotlinlib/src/mill/kotlinlib/ktlint/package.scala
new file mode 100644
index 00000000000..991d1effaaa
--- /dev/null
+++ b/kotlinlib/src/mill/kotlinlib/ktlint/package.scala
@@ -0,0 +1,5 @@
+package mill.kotlinlib.ktlint
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(KtlintModule)
diff --git a/kotlinlib/src/mill/kotlinlib/package.scala b/kotlinlib/src/mill/kotlinlib/package.scala
index 51b0599b97e..c0caeebba4e 100644
--- a/kotlinlib/src/mill/kotlinlib/package.scala
+++ b/kotlinlib/src/mill/kotlinlib/package.scala
@@ -23,4 +23,8 @@ package object kotlinlib {
type ZincWorkerModule = mill.scalalib.ZincWorkerModule
val ZincWorkerModule = mill.scalalib.ZincWorkerModule
+
+ type JvmWorkerModule = mill.scalalib.ZincWorkerModule
+ val JvmWorkerModule = mill.scalalib.ZincWorkerModule
+
}
diff --git a/main/api/src/mill/api/Logger.scala b/main/api/src/mill/api/Logger.scala
index 50cdb977936..87a8589e516 100644
--- a/main/api/src/mill/api/Logger.scala
+++ b/main/api/src/mill/api/Logger.scala
@@ -1,6 +1,7 @@
package mill.api
import java.io.{InputStream, PrintStream}
+import scala.annotation.nowarn
/**
* The standard logging interface of the Mill build tool.
@@ -30,9 +31,14 @@ trait Logger extends AutoCloseable {
def colored: Boolean
private[mill] def unprefixedSystemStreams: SystemStreams = systemStreams
+ @deprecated("For read-access use `streams` instead", "Mill 0.12.11")
def systemStreams: SystemStreams
+ @nowarn("cat=deprecation")
+ def streams = systemStreams
+ @deprecated("Use streams.err instead", "Mill 0.12.11")
def errorStream: PrintStream = systemStreams.err
+ @deprecated("Use streams.out instead", "Mill 0.12.11")
def outputStream: PrintStream = systemStreams.out
/**
diff --git a/main/define/src/mill/define/BaseModule.scala b/main/define/src/mill/define/BaseModule.scala
index f44c5f7e7e5..fdd1a66e38f 100644
--- a/main/define/src/mill/define/BaseModule.scala
+++ b/main/define/src/mill/define/BaseModule.scala
@@ -95,3 +95,13 @@ abstract class ExternalModule(implicit
Segments(millModuleEnclosing0.value.split('.').map(Segment.Label).toIndexedSeq)
}
}
+
+object ExternalModule {
+
+ /**
+ * Allows you to define a new top-level [[ExternalModule]] that is simply an alias
+ * to an existing one. Useful for renaming an [[ExternalModule]] while preserving
+ * backwards compatibility to the existing implementation and name
+ */
+ class Alias(val value: ExternalModule)
+}
diff --git a/main/define/src/mill/define/Cross.scala b/main/define/src/mill/define/Cross.scala
index 70e094b4a19..0cb5b366b30 100644
--- a/main/define/src/mill/define/Cross.scala
+++ b/main/define/src/mill/define/Cross.scala
@@ -23,7 +23,7 @@ object Cross {
*/
trait CrossValue extends Module[T1] {
def crossValue: T1 = Module.this.crossValue
- override def crossWrapperSegments: List[String] = Module.this.millModuleSegments.parts
+ override def crossWrapperSegments: List[String] = Module.this.moduleSegments.parts
}
}
diff --git a/main/define/src/mill/define/Discover.scala b/main/define/src/mill/define/Discover.scala
index 11f50e27e3e..f951d22fb67 100644
--- a/main/define/src/mill/define/Discover.scala
+++ b/main/define/src/mill/define/Discover.scala
@@ -152,7 +152,7 @@ object Discover {
}
c.Expr[Discover](
- q"import mill.api.JsonFormatters._; _root_.mill.define.Discover.apply2(_root_.scala.collection.immutable.Map(..$mapping))"
+ q"import _root_.mill.api.JsonFormatters._; _root_.mill.define.Discover.apply2(_root_.scala.collection.immutable.Map(..$mapping))"
)
}
}
diff --git a/main/define/src/mill/define/Module.scala b/main/define/src/mill/define/Module.scala
index b29a574dcfd..a0c8c40a5fb 100644
--- a/main/define/src/mill/define/Module.scala
+++ b/main/define/src/mill/define/Module.scala
@@ -2,6 +2,7 @@ package mill.define
import mill.api.internal
+import scala.annotation.nowarn
import scala.reflect.ClassTag
/**
@@ -20,30 +21,44 @@ trait Module extends Module.BaseClass {
* that should not be needed by normal users of Mill
*/
@internal
+ @deprecated("Use moduleInternal instead", "Mill 0.12.11")
object millInternal extends Module.Internal(this)
+ @nowarn("cat=deprecation")
+ final def moduleInternal = millInternal
+
def millModuleDirectChildren: Seq[Module] = millModuleDirectChildrenImpl
// We keep a private `lazy val` and a public `def` so
// subclasses can call `super.millModuleDirectChildren`
private lazy val millModuleDirectChildrenImpl: Seq[Module] =
- millInternal.reflectNestedObjects[Module]().toSeq
+ moduleInternal.reflectNestedObjects[Module]().toSeq
def millOuterCtx: Ctx
+ @deprecated(
+ """For read-access use moduleDir instead. Calls like `Task.Sources(millSourcePath / "foo")` can be replace with `Task.Sources("foo")`""",
+ "Mill 0.12.11"
+ )
def millSourcePath: os.Path = millOuterCtx.millSourcePath / (millOuterCtx.segment match {
case Segment.Label(s) => Seq(s)
case Segment.Cross(_) => Seq.empty[String] // drop cross segments
})
+ @nowarn("cat=deprecation")
+ final def moduleDir = millSourcePath
implicit def millModuleExternal: Ctx.External = Ctx.External(millOuterCtx.external)
implicit def millModuleShared: Ctx.Foreign = Ctx.Foreign(millOuterCtx.foreign)
- implicit def millModuleBasePath: Ctx.BasePath = Ctx.BasePath(millSourcePath)
+ implicit def millModuleBasePath: Ctx.BasePath = Ctx.BasePath(moduleDir)
+ // @deprecated("For read-access use moduleSegments instead", "Mill 0.12.11")
+ // since this is an implicit, the deprecation warning would result in lots of spurious warnings
implicit def millModuleSegments: Segments = {
millOuterCtx.segments ++ Seq(millOuterCtx.segment)
}
+ @nowarn("cat=deprecation")
+ final def moduleSegments = millModuleSegments
- override def toString = millModuleSegments.render
+ override def toString = moduleSegments.render
}
object Module {
@@ -68,10 +83,10 @@ object Module {
lazy val modules: Seq[Module] = traverse(Seq(_))
lazy val segmentsToModules: Map[Segments, Module] =
- modules.map(m => (m.millModuleSegments, m)).toMap
+ modules.map(m => (m.moduleSegments, m)).toMap
lazy val targets: Set[Target[_]] =
- traverse { _.millInternal.reflectAll[Target[_]].toIndexedSeq }.toSet
+ traverse { _.moduleInternal.reflectAll[Target[_]].toIndexedSeq }.toSet
def reflect[T: ClassTag](filter: String => Boolean): Seq[T] = {
Reflect.reflect(
diff --git a/main/define/test/src/mill/define/DiscoverTests.scala b/main/define/test/src/mill/define/DiscoverTests.scala
index 7de77a734ca..dda6606e026 100644
--- a/main/define/test/src/mill/define/DiscoverTests.scala
+++ b/main/define/test/src/mill/define/DiscoverTests.scala
@@ -7,7 +7,7 @@ object DiscoverTests extends TestSuite {
val testGraphs = new TestGraphs
val tests = Tests {
def check[T <: Module](m: T)(targets: (T => Target[_])*) = {
- val discovered = m.millInternal.targets
+ val discovered = m.moduleInternal.targets
val expected = targets.map(_(m)).toSet
assert(discovered == expected)
}
diff --git a/main/eval/test/src/mill/eval/ModuleTests.scala b/main/eval/test/src/mill/eval/ModuleTests.scala
index f9160143c0c..77699320b18 100644
--- a/main/eval/test/src/mill/eval/ModuleTests.scala
+++ b/main/eval/test/src/mill/eval/ModuleTests.scala
@@ -5,9 +5,11 @@ import mill.testkit.UnitTester.Result
import mill.testkit.TestBaseModule
import mill.{T, Task}
import mill.define.Discover
+import mill.define.ExternalModule
import utest._
+object `package` extends ExternalModule.Alias(TestExternalModule)
object TestExternalModule extends mill.define.ExternalModule with mill.define.TaskModule {
def defaultCommandName() = "x"
def x = Task { 13 }
@@ -25,6 +27,10 @@ object ModuleTests extends TestSuite {
val check = UnitTester(Build, null)
val result = check.apply("mill.eval.TestExternalModule/x")
assert(result == Right(Result(Vector(13), 0)))
+
+ val result1 = check.apply("mill.eval/x") // short alias
+ assert(result1 == Right(Result(Vector(13), 0)))
+
val result2 = check.apply("mill.eval.TestExternalModule/")
assert(result2 == Right(Result(Vector(13), 0)))
}
diff --git a/main/init/src/mill/init/BuildGenModule.scala b/main/init/src/mill/init/BuildGenModule.scala
index 9b691a074f2..53c3a102c57 100644
--- a/main/init/src/mill/init/BuildGenModule.scala
+++ b/main/init/src/mill/init/BuildGenModule.scala
@@ -23,7 +23,7 @@ trait BuildGenModule extends CoursierModule with TaskModule {
def buildGenScalafmtConfig: T[PathRef] = PathRef(BuildGenUtil.scalafmtConfigFile)
def init(args: String*): Command[Unit] = Task.Command {
- val root = millSourcePath
+ val root = moduleDir
val mainClass = buildGenMainClass()
val classPath = buildGenClasspath().map(_.path)
diff --git a/main/init/src/mill/init/InitModule.scala b/main/init/src/mill/init/InitModule.scala
index 3c1253a2eb9..785bf31cc6e 100644
--- a/main/init/src/mill/init/InitModule.scala
+++ b/main/init/src/mill/init/InitModule.scala
@@ -87,7 +87,7 @@ trait InitModule extends Module {
}
} match {
case Success((ret, msg)) =>
- Task.log.outputStream.println(msg)
+ Task.log.streams.out.println(msg)
ret
case Failure(exception) =>
Task.log.error(exception.getMessage)
diff --git a/main/resolve/src/mill/resolve/Resolve.scala b/main/resolve/src/mill/resolve/Resolve.scala
index 3841a11abd2..53d9cc2f444 100644
--- a/main/resolve/src/mill/resolve/Resolve.scala
+++ b/main/resolve/src/mill/resolve/Resolve.scala
@@ -11,7 +11,8 @@ import mill.define.{
Reflect,
Segments,
Target,
- TaskModule
+ TaskModule,
+ ExternalModule
}
import mill.resolve.ResolveCore.{Resolved, makeResultException}
import mill.util.EitherOps
@@ -78,7 +79,7 @@ object Resolve {
rootModule,
value.getClass,
Some(value.defaultCommandName()),
- value.millModuleSegments,
+ value.moduleSegments,
cache = cache
)
@@ -383,9 +384,17 @@ trait Resolve[T] {
try Right(rootModule.getClass.getClassLoader.loadClass(scoping.render + "$"))
catch {
case e: ClassNotFoundException =>
- Left("Cannot resolve external module " + scoping.render)
+ try Right(rootModule.getClass.getClassLoader.loadClass(
+ scoping.render + ".package$"
+ ))
+ catch {
+ case e: ClassNotFoundException =>
+ Left("Cannot resolve external module " + scoping.render)
+ }
}
+
rootModule <- moduleCls.getField("MODULE$").get(moduleCls) match {
+ case alias: ExternalModule.Alias => Right(alias.value)
case rootModule: BaseModule => Right(rootModule)
case _ => Left("Class " + scoping.render + " is not an BaseModule")
}
diff --git a/main/resolve/src/mill/resolve/ResolveCore.scala b/main/resolve/src/mill/resolve/ResolveCore.scala
index 763201a78f4..46d73aa69e0 100644
--- a/main/resolve/src/mill/resolve/ResolveCore.scala
+++ b/main/resolve/src/mill/resolve/ResolveCore.scala
@@ -117,7 +117,9 @@ private object ResolveCore {
tail,
r,
querySoFar ++ Seq(head),
- seenModules ++ moduleClasses(Set(current)),
+ // `foo.__` wildcards can refer to `foo` as well, so make sure we don't
+ // mark it as seen to avoid spurious cyclic module reference errors
+ seenModules ++ moduleClasses(Option.when(r != current)(current)),
cache
)
}
@@ -149,7 +151,6 @@ private object ResolveCore {
m.cls,
None,
current.segments,
- Nil,
seenModules,
cache
)
@@ -174,12 +175,17 @@ private object ResolveCore {
m.cls,
None,
current.segments,
- typePattern,
seenModules,
cache
)
- transitiveOrErr.map(transitive => self ++ transitive)
+ transitiveOrErr.map(transitive =>
+ (self ++ transitive).collect {
+ case r @ Resolved.Module(segments, cls)
+ if classMatchesTypePred(typePattern)(cls) =>
+ r
+ }
+ )
case pattern if pattern.startsWith("_:") =>
val typePattern = pattern.split(":").drop(1)
@@ -188,9 +194,13 @@ private object ResolveCore {
m.cls,
None,
current.segments,
- typePattern,
cache
- )
+ ).map {
+ _.collect {
+ case r @ Resolved.Module(segments, cls)
+ if classMatchesTypePred(typePattern)(cls) => r
+ }
+ }
case _ =>
resolveDirectChildren(
@@ -232,7 +242,7 @@ private object ResolveCore {
case Right(searchModules) =>
recurse(
searchModules
- .map(m => Resolved.Module(m.millModuleSegments, m.getClass))
+ .map(m => Resolved.Module(m.moduleSegments, m.getClass))
)
}
@@ -254,7 +264,7 @@ private object ResolveCore {
assert(s != "_", s)
resolveDirectChildren0(
rootModule,
- current.millModuleSegments,
+ current.moduleSegments,
current.getClass,
Some(s),
cache = cache
@@ -289,15 +299,14 @@ private object ResolveCore {
cls: Class[_],
nameOpt: Option[String],
segments: Segments,
- typePattern: Seq[String],
seenModules: Set[Class[_]],
cache: Cache
): Either[String, Seq[Resolved]] = {
if (seenModules.contains(cls)) Left(cyclicModuleErrorMsg(segments))
else {
val errOrDirect =
- resolveDirectChildren(rootModule, cls, nameOpt, segments, typePattern, cache)
- val directTraverse = resolveDirectChildren(rootModule, cls, nameOpt, segments, Nil, cache)
+ resolveDirectChildren(rootModule, cls, nameOpt, segments, cache)
+ val directTraverse = resolveDirectChildren(rootModule, cls, nameOpt, segments, cache)
val errOrModules = directTraverse.map { modules =>
modules.flatMap {
@@ -314,7 +323,6 @@ private object ResolveCore {
m.cls,
nameOpt,
m.segments,
- typePattern,
seenModules + cls,
cache
))
@@ -368,7 +376,6 @@ private object ResolveCore {
cls: Class[_],
nameOpt: Option[String],
segments: Segments,
- typePattern: Seq[String] = Nil,
cache: Cache
): Either[String, Seq[Resolved]] = {
val crossesOrErr = if (classOf[Cross[_]].isAssignableFrom(cls) && nameOpt.isEmpty) {
@@ -392,12 +399,9 @@ private object ResolveCore {
for {
crosses <- crossesOrErr
- filteredCrosses = crosses.filter { c =>
- classMatchesTypePred(typePattern)(c.cls)
- }
- direct0 <- resolveDirectChildren0(rootModule, segments, cls, nameOpt, typePattern, cache)
+ direct0 <- resolveDirectChildren0(rootModule, segments, cls, nameOpt, cache)
direct <- Right(expandSegments(direct0))
- } yield direct ++ filteredCrosses
+ } yield direct ++ crosses
}
def resolveDirectChildren0(
@@ -405,7 +409,6 @@ private object ResolveCore {
segments: Segments,
cls: Class[_],
nameOpt: Option[String],
- typePattern: Seq[String] = Nil,
cache: Cache
): Either[String, Seq[(Resolved, Option[Module => Either[String, Module]])]] = {
def namePred(n: String) = nameOpt.isEmpty || nameOpt.contains(n)
@@ -415,12 +418,11 @@ private object ResolveCore {
instantiateModule(rootModule, segments, cache).map {
case m: DynamicModule =>
m.millModuleDirectChildren
- .filter(c => namePred(c.millModuleSegments.last.value))
- .filter(c => classMatchesTypePred(typePattern)(c.getClass))
+ .filter(c => namePred(c.moduleSegments.last.value))
.map(c =>
(
Resolved.Module(
- Segments.labels(c.millModuleSegments.last.value),
+ Segments.labels(c.moduleSegments.last.value),
c.getClass
),
Some((x: Module) => Right(c))
@@ -431,7 +433,7 @@ private object ResolveCore {
val reflectMemberObjects = Reflect
.reflectNestedObjects02[Module](cls, namePred, cache.getMethods)
.collect {
- case (name, memberCls, getter) if classMatchesTypePred(typePattern)(memberCls) =>
+ case (name, memberCls, getter) =>
val resolved = Resolved.Module(Segments.labels(cache.decode(name)), memberCls)
val getter2 = Some((mod: Module) => catchWrapException(getter(mod)))
(resolved, getter2)
diff --git a/main/resolve/test/src/mill/main/ResolveTests.scala b/main/resolve/test/src/mill/main/ResolveTests.scala
index 189ae9b2193..c889ff90f4b 100644
--- a/main/resolve/test/src/mill/main/ResolveTests.scala
+++ b/main/resolve/test/src/mill/main/ResolveTests.scala
@@ -229,6 +229,15 @@ object ResolveTests extends TestSuite {
)),
Set("nested.single", "classInstance.single")
)
+ test("wildcard4") - check(
+ "__.__.single",
+ Right(Set(
+ _.classInstance.single,
+ _.nested.single,
+ _.single
+ )),
+ Set("nested.single", "classInstance.single", "single")
+ )
}
test("doubleNested") {
val check = new Checker(doubleNestedModule)
@@ -942,6 +951,15 @@ object ResolveTests extends TestSuite {
"_:Module._",
Right(Set(_.typeA.foo, _.typeB.bar, _.typeAB.foo, _.typeAB.bar, _.typeC.baz))
)
+ test - {
+ val res = check.resolveMetadata(Seq("__:Module"))
+ assert(res == Right(List("", "typeA", "typeAB", "typeB", "typeC", "typeC.typeA")))
+ }
+ test - {
+ val res = check.resolveMetadata(Seq("_:Module"))
+ assert(res == Right(List("typeA", "typeAB", "typeB", "typeC")))
+ }
+
// parens should work
test - check(
"(_:Module)._",
diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala
index 0ba0b6d34e2..0472b17eed8 100644
--- a/main/src/mill/main/MainModule.scala
+++ b/main/src/mill/main/MainModule.scala
@@ -267,7 +267,7 @@ trait MainModule extends BaseModule0 {
if (seen(t)) Nil // do nothing
else t match {
case t: mill.define.Target[_]
- if evaluator.rootModule.millInternal.targets.contains(t) =>
+ if evaluator.rootModule.moduleInternal.targets.contains(t) =>
Seq(t.ctx.segments)
case _ =>
seen.add(t)
diff --git a/main/src/mill/main/RunScript.scala b/main/src/mill/main/RunScript.scala
index 71ebdf38c3a..d4dc73221e2 100644
--- a/main/src/mill/main/RunScript.scala
+++ b/main/src/mill/main/RunScript.scala
@@ -1,13 +1,13 @@
package mill.main
+import mill.api.Strict.Agg
+import mill.api.{PathRef, Result, Val}
import mill.define._
+import mill.eval.Evaluator._
import mill.eval.{Evaluator, EvaluatorPaths, Plan}
-import mill.util.Watchable
-import mill.api.{PathRef, Result, Val}
-import mill.api.Strict.Agg
-import Evaluator._
import mill.main.client.OutFiles
import mill.resolve.{Resolve, SelectMode}
+import mill.util.Watchable
import scala.annotation.nowarn
@@ -107,19 +107,33 @@ object RunScript {
val selectiveExecutionEnabled = selectiveExecution && !targets.exists(_.isExclusiveCommand)
val selectedTargetsOrErr =
- if (
- selectiveExecutionEnabled && os.exists(evaluator.outPath / OutFiles.millSelectiveExecution)
- ) {
- val changedTasks = SelectiveExecution.computeChangedTasks0(evaluator, targets.toSeq)
- val selectedSet = changedTasks.downstreamTasks.map(_.ctx.segments.render).toSet
- (
- targets.filter(t => t.isExclusiveCommand || selectedSet(terminals(t).render)),
- changedTasks.results
- )
- } else (targets -> Map.empty)
+ if (!selectiveExecutionEnabled) (targets, Map.empty, None)
+ else {
+ val newComputedMetadata = SelectiveExecution.Metadata.compute(evaluator, targets.toSeq)
+
+ val selectiveExecutionStoredData = for {
+ _ <- Option.when(os.exists(evaluator.outPath / OutFiles.millSelectiveExecution))(())
+ changedTasks <-
+ SelectiveExecution.computeChangedTasks0(evaluator, targets.toSeq, newComputedMetadata)
+ } yield changedTasks
+
+ selectiveExecutionStoredData match {
+ case None =>
+ // Ran when previous selective execution metadata is not available, which happens the first time you run
+ // selective execution.
+ (targets, Map.empty, Some(newComputedMetadata.metadata))
+ case Some(changedTasks) =>
+ val selectedSet = changedTasks.downstreamTasks.map(_.ctx.segments.render).toSet
+ (
+ targets.filter(t => t.isExclusiveCommand || selectedSet(terminals(t).render)),
+ newComputedMetadata.results,
+ Some(newComputedMetadata.metadata)
+ )
+ }
+ }
selectedTargetsOrErr match {
- case (selectedTargets, selectiveResults) =>
+ case (selectedTargets, selectiveResults, maybeNewMetadata) =>
val evaluated: Results = evaluator.evaluate(selectedTargets, serialCommandExec = true)
val watched = (evaluated.results.iterator ++ selectiveResults)
.collect {
@@ -134,15 +148,8 @@ object RunScript {
.flatten
.toSeq
- val allInputHashes = evaluated.results
- .iterator
- .collect {
- case (t: InputImpl[_], TaskResult(Result.Success(Val(value)), _)) =>
- (terminals(t).render, value.##)
- }
- .toMap
-
- if (selectiveExecutionEnabled) {
+ maybeNewMetadata.foreach { newMetadata =>
+ val allInputHashes = newMetadata.inputHashes
SelectiveExecution.saveMetadata(
evaluator,
SelectiveExecution.Metadata(allInputHashes, evaluator.methodCodeHashSignatures)
diff --git a/main/src/mill/main/SelectiveExecution.scala b/main/src/mill/main/SelectiveExecution.scala
index 216def687a3..f6aa7793e54 100644
--- a/main/src/mill/main/SelectiveExecution.scala
+++ b/main/src/mill/main/SelectiveExecution.scala
@@ -2,11 +2,11 @@ package mill.main
import mill.api.{Strict, Val}
import mill.define.{InputImpl, NamedTask, Task}
-import mill.eval.{CodeSigUtils, Evaluator, EvaluatorCore, Plan, Terminal}
+import mill.eval._
import mill.main.client.OutFiles
-import mill.util.SpanningForest.breadthFirst
import mill.resolve.{Resolve, SelectMode}
import mill.util.SpanningForest
+import mill.util.SpanningForest.breadthFirst
private[mill] object SelectiveExecution {
case class Metadata(inputHashes: Map[String, Int], methodCodeHashSignatures: Map[String, Int])
@@ -14,10 +14,16 @@ private[mill] object SelectiveExecution {
implicit val rw: upickle.default.ReadWriter[Metadata] = upickle.default.macroRW
object Metadata {
+ case class Computed(
+ metadata: Metadata,
+ // TODO: better name
+ results: Map[Task[_], Evaluator.TaskResult[Val]]
+ )
+
def compute(
evaluator: Evaluator,
tasks: Seq[NamedTask[_]]
- ): (Metadata, Map[Task[_], Evaluator.TaskResult[Val]]) = {
+ ): Computed = {
val (sortedGroups, transitive) = Plan.plan(tasks)
val inputTasksToLabels: Map[Task[_], String] = sortedGroups.keys()
.collect { case Terminal.Labelled(task: InputImpl[_], segments) =>
@@ -27,17 +33,20 @@ private[mill] object SelectiveExecution {
val results = evaluator.evaluate(Strict.Agg.from(inputTasksToLabels.keys))
- new Metadata(
- inputHashes = results
- .results
- .flatMap { case (task, taskResult) =>
- inputTasksToLabels.get(task).map { l =>
- l -> taskResult.result.getOrThrow.value.hashCode
+ Computed(
+ new Metadata(
+ inputHashes = results
+ .results
+ .flatMap { case (task, taskResult) =>
+ inputTasksToLabels.get(task).map { label =>
+ label -> taskResult.result.getOrThrow.value.hashCode
+ }
}
- }
- .toMap,
- methodCodeHashSignatures = evaluator.methodCodeHashSignatures
- ) -> results.results.toMap
+ .toMap,
+ methodCodeHashSignatures = evaluator.methodCodeHashSignatures
+ ),
+ results.results.toMap
+ )
}
}
@@ -116,9 +125,13 @@ private[mill] object SelectiveExecution {
case class ChangedTasks(
resolved: Seq[NamedTask[_]],
changedRootTasks: Set[NamedTask[_]],
- downstreamTasks: Seq[NamedTask[_]],
- results: Map[Task[_], Evaluator.TaskResult[Val]]
+ downstreamTasks: Seq[NamedTask[_]]
)
+ object ChangedTasks {
+
+ /** Indicates that all of the passed in tasks were changed. */
+ def all(tasks: Seq[NamedTask[_]]): ChangedTasks = ChangedTasks(tasks, tasks.toSet, tasks)
+ }
def computeChangedTasks(
evaluator: Evaluator,
@@ -129,24 +142,39 @@ private[mill] object SelectiveExecution {
tasks,
SelectMode.Separated,
evaluator.allowPositionalCommandArgs
- ).map(computeChangedTasks0(evaluator, _))
+ ).map { tasks =>
+ computeChangedTasks0(evaluator, tasks, SelectiveExecution.Metadata.compute(evaluator, tasks))
+ // If we did not have the metadata, presume everything was changed.
+ .getOrElse(ChangedTasks.all(tasks))
+ }
}
- def computeChangedTasks0(evaluator: Evaluator, tasks: Seq[NamedTask[_]]): ChangedTasks = {
+ /**
+ * @return [[None]] when the metadata file is empty.
+ * @note throws if the metadata file does not exist.
+ */
+ def computeChangedTasks0(
+ evaluator: Evaluator,
+ tasks: Seq[NamedTask[_]],
+ computedMetadata: SelectiveExecution.Metadata.Computed
+ ): Option[ChangedTasks] = {
val oldMetadataTxt = os.read(evaluator.outPath / OutFiles.millSelectiveExecution)
- if (oldMetadataTxt == "") ChangedTasks(tasks, tasks.toSet, tasks, Map.empty)
- else {
+
+ // We allow to clear the selective execution metadata to rerun all tasks.
+ //
+ // You would think that removing the file achieves the same result, however, blanking the file indicates that
+ // this was intentional and you did not simply forgot to run `selective.prepare` beforehand.
+ if (oldMetadataTxt == "") None
+ else Some {
val oldMetadata = upickle.default.read[SelectiveExecution.Metadata](oldMetadataTxt)
- val (newMetadata, results) = SelectiveExecution.Metadata.compute(evaluator, tasks)
val (changedRootTasks, downstreamTasks) =
- SelectiveExecution.computeDownstream(tasks, oldMetadata, newMetadata)
+ SelectiveExecution.computeDownstream(tasks, oldMetadata, computedMetadata.metadata)
ChangedTasks(
tasks,
changedRootTasks.collect { case n: NamedTask[_] => n },
- downstreamTasks.collect { case n: NamedTask[_] => n },
- results
+ downstreamTasks.collect { case n: NamedTask[_] => n }
)
}
}
diff --git a/main/src/mill/main/SelectiveExecutionModule.scala b/main/src/mill/main/SelectiveExecutionModule.scala
index a238493f0a6..6c0ccde9c3c 100644
--- a/main/src/mill/main/SelectiveExecutionModule.scala
+++ b/main/src/mill/main/SelectiveExecutionModule.scala
@@ -20,9 +20,8 @@ trait SelectiveExecutionModule extends mill.define.Module {
if (tasks.isEmpty) Seq("__") else tasks,
SelectMode.Multi
).map { resolvedTasks =>
- SelectiveExecution.Metadata
- .compute(evaluator, resolvedTasks)
- .map(x => SelectiveExecution.saveMetadata(evaluator, x._1))
+ val computed = SelectiveExecution.Metadata.compute(evaluator, resolvedTasks)
+ SelectiveExecution.saveMetadata(evaluator, computed.metadata)
}
res match {
diff --git a/main/util/src/mill/util/LinePrefixOutputStream.scala b/main/util/src/mill/util/LinePrefixOutputStream.scala
index 0d12617d12d..92510fe2a74 100644
--- a/main/util/src/mill/util/LinePrefixOutputStream.scala
+++ b/main/util/src/mill/util/LinePrefixOutputStream.scala
@@ -42,7 +42,10 @@ class LinePrefixOutputStream(
val bufferString = buffer.toString
if (bufferString.length > 0) {
val s = fansi.Str.apply(bufferString, errorMode = fansi.ErrorMode.Sanitize)
- endOfLastLineColor = s.getColor(math.max(0, s.length - 1))
+ if (s.length > 0) {
+ // only change last value if we have some proper ansi string
+ endOfLastLineColor = s.getColor(s.length - 1)
+ }
}
}
out.synchronized { buffer.writeTo(out) }
diff --git a/pythonlib/package.mill b/pythonlib/package.mill
index 435806645af..8822b596b3e 100644
--- a/pythonlib/package.mill
+++ b/pythonlib/package.mill
@@ -8,7 +8,9 @@ import mill._
object `package` extends RootModule with build.MillPublishScalaModule {
// we depend on scalalib for re-using some common infrastructure (e.g. License
// management of projects), NOT for reusing build logic
- def moduleDeps = Seq(build.main, build.scalalib)
+ def moduleDeps = Seq(build.scalalib)
def testTransitiveDeps =
super.testTransitiveDeps() ++ Seq(build.scalalib.backgroundwrapper.testDep())
+
+ def testModuleDeps = super.testModuleDeps ++ Seq(build.main.client)
}
diff --git a/pythonlib/src/mill/pythonlib/CoverageModule.scala b/pythonlib/src/mill/pythonlib/CoverageModule.scala
index b578ae70569..f8229a5ea68 100644
--- a/pythonlib/src/mill/pythonlib/CoverageModule.scala
+++ b/pythonlib/src/mill/pythonlib/CoverageModule.scala
@@ -45,7 +45,7 @@ trait CoverageModule extends PythonModule {
* [[coverageDataFile]]. It is required that this file be readable as soon
* as this task returns.
*/
- def coverageTask: Task[_]
+ def coverageTask: Task[?]
private case class CoverageReporter(
interp: os.Path,
diff --git a/pythonlib/src/mill/pythonlib/PublishModule.scala b/pythonlib/src/mill/pythonlib/PublishModule.scala
index 4c6a14b4b25..ffcc580b699 100644
--- a/pythonlib/src/mill/pythonlib/PublishModule.scala
+++ b/pythonlib/src/mill/pythonlib/PublishModule.scala
@@ -1,7 +1,8 @@
package mill.pythonlib
import mill.api.Result
-import mill.{PathRef, Task, T, Command}
+import mill.scalalib.publish.License
+import mill.{Command, PathRef, T, Task}
/**
* A python module which also defines how to build and publish source distributions and wheels.
@@ -17,7 +18,11 @@ trait PublishModule extends PythonModule {
}
override def pythonToolDeps = Task {
- super.pythonToolDeps() ++ Seq("setuptools>=75.6.0", "build>=1.2.2", "twine>=5.1.1")
+ super.pythonToolDeps() ++ Seq(
+ "setuptools>=75.6.0",
+ "build>=1.2.2",
+ "twine>=5.1.1"
+ )
}
/**
@@ -105,14 +110,14 @@ trait PublishModule extends PythonModule {
* The readme file to include in the published distribution.
*/
def publishReadme: T[PathRef] = Task.Input {
- val readme = if (os.exists(millSourcePath)) {
- os.list(millSourcePath).find(_.last.toLowerCase().startsWith("readme"))
+ val readme = if (os.exists(moduleDir)) {
+ os.list(moduleDir).find(_.last.toLowerCase().startsWith("readme"))
} else None
readme match {
case None =>
Result.Failure(
- s"No readme file found in `${millSourcePath}`. A readme file is required for publishing distributions. " +
- s"Please create a file named `${millSourcePath}/readme*` (any capitalization), or override the `publishReadme` task."
+ s"No readme file found in `${moduleDir}`. A readme file is required for publishing distributions. " +
+ s"Please create a file named `${moduleDir}/readme*` (any capitalization), or override the `publishReadme` task."
)
case Some(path) =>
Result.Success(PathRef(path))
@@ -236,6 +241,8 @@ trait PublishModule extends PythonModule {
}
object PublishModule {
+ private implicit lazy val licenseFormat: upickle.default.ReadWriter[License] =
+ upickle.default.macroRW
/**
* Static metadata about a project.
diff --git a/pythonlib/src/mill/pythonlib/PythonModule.scala b/pythonlib/src/mill/pythonlib/PythonModule.scala
index d87d7419a29..3c905750ca9 100644
--- a/pythonlib/src/mill/pythonlib/PythonModule.scala
+++ b/pythonlib/src/mill/pythonlib/PythonModule.scala
@@ -2,8 +2,7 @@ package mill.pythonlib
import mill._
import mill.api.Result
-import mill.util.Util
-import mill.util.Jvm
+import mill.util.{Jvm}
import mill.api.Ctx
import mill.main.client.ServerFiles
@@ -67,7 +66,10 @@ trait PythonModule extends PipModule with TaskModule { outer =>
def mainScript: T[PathRef] = Task.Source { "src/main.py" }
override def pythonToolDeps: T[Seq[String]] = Task {
- super.pythonToolDeps() ++ Seq("mypy==1.13.0", "pex==2.24.1")
+ super.pythonToolDeps() ++ Seq(
+ "mypy==1.13.0",
+ "pex==2.24.1"
+ )
}
/**
@@ -161,7 +163,7 @@ trait PythonModule extends PipModule with TaskModule { outer =>
*/
def run(args: mill.define.Args) = Task.Command {
runner().run(
- (
+ args = (
mainScript().path,
args.value
)
@@ -177,29 +179,31 @@ trait PythonModule extends PipModule with TaskModule { outer =>
val (procUuidPath, procLockfile, procUuid) = mill.scalalib.RunModule.backgroundSetup(Task.dest)
val pwd0 = os.Path(java.nio.file.Paths.get(".").toAbsolutePath)
- Jvm.spawnProcess(
- mainClass = "mill.scalalib.backgroundwrapper.MillBackgroundWrapper",
- classPath = mill.scalalib.ZincWorkerModule.backgroundWrapperClasspath().map(_.path).toSeq,
- jvmArgs = Nil,
- env = runnerEnvTask(),
- mainArgs = Seq(
- procUuidPath.toString,
- procLockfile.toString,
- procUuid,
- "500",
- "",
- pythonExe().path.toString,
- mainScript().path.toString
- ) ++ args.value,
- cwd = Task.workspace,
- stdin = "",
- // Hack to forward the background subprocess output to the Mill server process
- // stdout/stderr files, so the output will get properly slurped up by the Mill server
- // and shown to any connected Mill client even if the current command has completed
- stdout = os.PathAppendRedirect(pwd0 / ".." / ServerFiles.stdout),
- stderr = os.PathAppendRedirect(pwd0 / ".." / ServerFiles.stderr),
- javaHome = mill.scalalib.ZincWorkerModule.javaHome().map(_.path)
- )
+ os.checker.withValue(os.Checker.Nop) {
+ Jvm.spawnProcess(
+ mainClass = "mill.scalalib.backgroundwrapper.MillBackgroundWrapper",
+ classPath = mill.scalalib.JvmWorkerModule.backgroundWrapperClasspath().map(_.path).toSeq,
+ jvmArgs = Nil,
+ env = runnerEnvTask(),
+ mainArgs = Seq(
+ procUuidPath.toString,
+ procLockfile.toString,
+ procUuid,
+ "500",
+ "",
+ pythonExe().path.toString,
+ mainScript().path.toString
+ ) ++ args.value,
+ cwd = Task.workspace,
+ stdin = "",
+ // Hack to forward the background subprocess output to the Mill server process
+ // stdout/stderr files, so the output will get properly slurped up by the Mill server
+ // and shown to any connected Mill client even if the current command has completed
+ stdout = os.PathAppendRedirect(pwd0 / ".." / ServerFiles.stdout),
+ stderr = os.PathAppendRedirect(pwd0 / ".." / ServerFiles.stderr),
+ javaHome = mill.scalalib.JvmWorkerModule.javaHome().map(_.path)
+ )
+ }
()
}
@@ -210,7 +214,7 @@ trait PythonModule extends PipModule with TaskModule { outer =>
* for you to test and operate your code interactively.
*/
def console(): Command[Unit] = Task.Command(exclusive = true) {
- if (!Util.isInteractive()) {
+ if (!mill.main.client.Util.hasConsole()) {
Result.Failure("console needs to be run with the -i/--interactive flag")
} else {
runner().run()
diff --git a/pythonlib/src/mill/pythonlib/RuffModule.scala b/pythonlib/src/mill/pythonlib/RuffModule.scala
index bfa3d8e799d..341bc77e5c7 100644
--- a/pythonlib/src/mill/pythonlib/RuffModule.scala
+++ b/pythonlib/src/mill/pythonlib/RuffModule.scala
@@ -122,6 +122,6 @@ object RuffModule extends ExternalModule with RuffModule with TaskModule {
)
}
- lazy val millDiscover: Discover = Discover[this.type]
+ lazy val millDiscover = Discover[this.type]
}
diff --git a/pythonlib/src/mill/pythonlib/TestModule.scala b/pythonlib/src/mill/pythonlib/TestModule.scala
index f317789ca40..e051e455710 100644
--- a/pythonlib/src/mill/pythonlib/TestModule.scala
+++ b/pythonlib/src/mill/pythonlib/TestModule.scala
@@ -13,7 +13,7 @@ trait TestModule extends TaskModule {
* results to the console.
* @see [[testCached]]
*/
- def test(args: String*): Command[Seq[TestResult]] =
+ def testForked(args: String*): Command[Seq[TestResult]] =
Task.Command {
testTask(Task.Anon { args })()
}
@@ -27,7 +27,8 @@ trait TestModule extends TaskModule {
* Discovers and runs the module's tests in a subprocess, reporting the
* results to the console.
* If no input has changed since the last run, no test were executed.
- * @see [[test()]]
+ *
+ * @see [[testForked()]]
*/
def testCached: T[Seq[TestResult]] = Task {
testTask(testCachedArgs)()
@@ -38,7 +39,7 @@ trait TestModule extends TaskModule {
*/
protected def testTask(args: Task[Seq[String]]): Task[Seq[TestResult]]
- override def defaultCommandName() = "test"
+ override def defaultCommandName() = "testForked"
}
object TestModule {
diff --git a/pythonlib/test/src/mill/pythonlib/HelloWorldTests.scala b/pythonlib/test/src/mill/pythonlib/HelloWorldTests.scala
index c14d371c996..ea79c95a548 100644
--- a/pythonlib/test/src/mill/pythonlib/HelloWorldTests.scala
+++ b/pythonlib/test/src/mill/pythonlib/HelloWorldTests.scala
@@ -1,6 +1,7 @@
package mill
package pythonlib
+import mill.define.Discover
import mill.testkit.{TestBaseModule, UnitTester}
import utest.*
@@ -19,6 +20,8 @@ object HelloWorldTests extends TestSuite {
override def mainScript = Task.Source("src/qux.py")
object test extends PythonTests with TestModule.Unittest
}
+
+ override lazy val millDiscover = Discover[this.type]
}
val resourcePath = os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "hello-world-python"
@@ -27,7 +30,7 @@ object HelloWorldTests extends TestSuite {
val baos = new ByteArrayOutputStream()
val eval = UnitTester(HelloWorldPython, resourcePath, outStream = new PrintStream(baos))
- val Right(result) = eval.apply(HelloWorldPython.qux.run(Args()))
+ val Right(result) = eval.apply(HelloWorldPython.qux.run(Args())): @unchecked
assert(baos.toString().contains("Hello, Qux!\n"))
}
@@ -35,7 +38,7 @@ object HelloWorldTests extends TestSuite {
test("test") {
val eval = UnitTester(HelloWorldPython, resourcePath)
- val result = eval.apply(HelloWorldPython.qux.test.test())
+ val result = eval.apply(HelloWorldPython.qux.test.testForked())
assert(result.isRight)
}
}
diff --git a/pythonlib/test/src/mill/pythonlib/RunBackgroundTests.scala b/pythonlib/test/src/mill/pythonlib/RunBackgroundTests.scala
index dc045339fd4..c6f34d0f05d 100644
--- a/pythonlib/test/src/mill/pythonlib/RunBackgroundTests.scala
+++ b/pythonlib/test/src/mill/pythonlib/RunBackgroundTests.scala
@@ -2,7 +2,9 @@ package mill.pythonlib
import mill.testkit.{TestBaseModule, UnitTester}
import utest.*
-import mill._
+import mill.*
+import mill.main.client.lock.Lock
+import mill.define.Discover
object RunBackgroundTests extends TestSuite {
@@ -10,6 +12,7 @@ object RunBackgroundTests extends TestSuite {
object foo extends PythonModule {
override def mainScript = Task.Source("src/foo.py")
}
+ override lazy val millDiscover = Discover[this.type]
}
val resourcePath = os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "run-background"
@@ -18,10 +21,11 @@ object RunBackgroundTests extends TestSuite {
val eval = UnitTester(HelloWorldPython, resourcePath)
val lockedFile = os.temp()
- val Right(result) = eval.apply(HelloWorldPython.foo.runBackground(Args(lockedFile)))
+ val Right(result) =
+ eval.apply(HelloWorldPython.foo.runBackground(Args(lockedFile))): @unchecked
val maxSleep = 20000
val now1 = System.currentTimeMillis()
- val lock = mill.main.client.lock.Lock.file(lockedFile.toString())
+ val lock = Lock.file(lockedFile.toString())
def sleepIfTimeAvailable(error: String) = {
Thread.sleep(100)
diff --git a/runner/src/mill/runner/MillBuildRootModule.scala b/runner/src/mill/runner/MillBuildRootModule.scala
index 33730f3a9c5..ddd4ace6569 100644
--- a/runner/src/mill/runner/MillBuildRootModule.scala
+++ b/runner/src/mill/runner/MillBuildRootModule.scala
@@ -7,7 +7,7 @@ import mill.define.{Discover, Task}
import mill.scalalib.{BoundDep, Dep, DepSyntax, Lib, ScalaModule}
import mill.util.CoursierSupport
import mill.util.Util.millProjectModule
-import mill.scalalib.api.{CompilationResult, Versions, ZincWorkerUtil}
+import mill.scalalib.api.{CompilationResult, Versions, JvmWorkerUtil}
import mill.main.client.OutFiles._
import mill.main.client.CodeGenConstants.buildFileExtensions
import mill.main.{BuildInfo, RootModule}
@@ -34,7 +34,7 @@ abstract class MillBuildRootModule()(implicit
.mkString("/")
override def millSourcePath: os.Path = rootModuleInfo.projectRoot / os.up / millBuild
- override def intellijModulePath: os.Path = millSourcePath / os.up
+ override def intellijModulePath: os.Path = moduleDir / os.up
override def scalaVersion: T[String] = BuildInfo.scalaVersion
@@ -98,7 +98,10 @@ abstract class MillBuildRootModule()(implicit
override def runIvyDeps = Task {
val imports = cliImports()
- val ivyImports = imports.collect { case s"ivy:$rest" => rest }
+ val ivyImports = imports.collect {
+ case s"ivy:$rest" => rest
+ case s"mvn:$rest" => rest
+ }
Agg.from(
MillIvy.processMillIvyDepSignature(ivyImports.toSet)
.map(mill.scalalib.Dep.parse)
@@ -234,7 +237,7 @@ abstract class MillBuildRootModule()(implicit
)
}
}
- val isScala3 = ZincWorkerUtil.isScala3(scalaVersion())
+ val isScala3 = JvmWorkerUtil.isScala3(scalaVersion())
if (isScala3)
allMillDistModules.filter(_._2 != "scala-library").toSeq
else
@@ -302,7 +305,7 @@ abstract class MillBuildRootModule()(implicit
}
// copied from `ScalaModule`
- zincWorker()
+ jvmWorker()
.worker()
.compileMixed(
upstreamCompileOutput = upstreamCompileOutput(),
diff --git a/runner/src/mill/runner/MillCliConfig.scala b/runner/src/mill/runner/MillCliConfig.scala
index 3ee33c622a4..6c94406d64a 100644
--- a/runner/src/mill/runner/MillCliConfig.scala
+++ b/runner/src/mill/runner/MillCliConfig.scala
@@ -78,7 +78,7 @@ case class MillCliConfig(
threadCountRaw: Option[String] = None,
@arg(
name = "import",
- doc = """Additional ivy dependencies to load into mill, e.g. plugins."""
+ doc = """Additional ivy/mvn dependencies to load into mill, e.g. plugins."""
)
imports: Seq[String] = Nil,
@arg(
diff --git a/runner/src/mill/runner/MillMain.scala b/runner/src/mill/runner/MillMain.scala
index 81dd172d928..83219f97e78 100644
--- a/runner/src/mill/runner/MillMain.scala
+++ b/runner/src/mill/runner/MillMain.scala
@@ -209,7 +209,7 @@ object MillMain {
val bspContext =
if (bspMode) Some(new BspContext(streams, bspLog, config.home)) else None
- val bspCmd = "mill.bsp.BSP/startSession"
+ val bspCmd = "mill.bsp/startSession"
val targetsAndParams =
bspContext
.map(_ => Seq(bspCmd))
diff --git a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala
index 7eea14e94ab..b8c5f4dbbce 100644
--- a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala
+++ b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala
@@ -3,7 +3,7 @@ package scalajslib
import mainargs.{Flag, arg}
import mill.api.{Loose, PathRef, Result, internal}
-import mill.scalalib.api.ZincWorkerUtil
+import mill.scalalib.api.JvmWorkerUtil
import mill.scalalib.{CrossVersion, Dep, DepSyntax, Lib, TestModule}
import mill.testrunner.{TestResult, TestRunner, TestRunnerUtils}
import mill.define.{Command, Task}
@@ -31,13 +31,13 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
@deprecated("use ScalaJSTests", "0.11.0")
trait Tests extends ScalaJSTests
- def scalaJSBinaryVersion = Task { ZincWorkerUtil.scalaJSBinaryVersion(scalaJSVersion()) }
+ def scalaJSBinaryVersion = Task { JvmWorkerUtil.scalaJSBinaryVersion(scalaJSVersion()) }
- def scalaJSWorkerVersion = Task { ZincWorkerUtil.scalaJSWorkerVersion(scalaJSVersion()) }
+ def scalaJSWorkerVersion = Task { JvmWorkerUtil.scalaJSWorkerVersion(scalaJSVersion()) }
override def scalaLibraryIvyDeps: T[Loose.Agg[Dep]] = Task {
val deps = super.scalaLibraryIvyDeps()
- if (ZincWorkerUtil.isScala3(scalaVersion())) {
+ if (JvmWorkerUtil.isScala3(scalaVersion())) {
// Since Dotty/Scala3, Scala.JS is published with a platform suffix
deps.map(dep =>
dep.copy(cross = dep.cross match {
@@ -220,7 +220,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
// ScalaJSModule as well as from the enclosing non-test ScalaJSModule
val scalajsFlag =
if (
- ZincWorkerUtil.isScala3(scalaVersion()) &&
+ JvmWorkerUtil.isScala3(scalaVersion()) &&
!super.mandatoryScalacOptions().contains("-scalajs")
) Seq("-scalajs")
else Seq.empty
@@ -230,7 +230,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
override def scalacPluginIvyDeps = Task {
super.scalacPluginIvyDeps() ++ {
- if (ZincWorkerUtil.isScala3(scalaVersion())) {
+ if (JvmWorkerUtil.isScala3(scalaVersion())) {
Seq.empty
} else {
Seq(ivy"org.scala-js:::scalajs-compiler:${scalaJSVersion()}")
@@ -343,7 +343,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
ScalaBuildTarget(
scalaOrganization = scalaOrganization(),
scalaVersion = scalaVersion(),
- scalaBinaryVersion = ZincWorkerUtil.scalaBinaryVersion(scalaVersion()),
+ scalaBinaryVersion = JvmWorkerUtil.scalaBinaryVersion(scalaVersion()),
platform = ScalaPlatform.JS,
jars = scalaCompilerClasspath().iterator.map(_.path.toNIO.toUri.toString).toSeq,
jvmBuildTarget = None
diff --git a/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala b/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala
index db790af1ff0..1208470bca3 100644
--- a/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala
+++ b/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala
@@ -5,9 +5,11 @@ import mill.api.Loose.Agg
import scala.annotation.nowarn
+@deprecated("Use JvmWorkerApi instead", "Mill 0.12.11")
object ZincWorkerApi {
type Ctx = mill.api.Ctx.Dest with mill.api.Ctx.Log with mill.api.Ctx.Home
}
+@deprecated("Use JvmWorkerApi instead", "Mill 0.12.11")
trait ZincWorkerApi {
/** Compile a Java-only project */
diff --git a/scalalib/api/src/mill/scalalib/api/ZincWorkerUtil.scala b/scalalib/api/src/mill/scalalib/api/ZincWorkerUtil.scala
index 699506c1a39..58491a429cb 100644
--- a/scalalib/api/src/mill/scalalib/api/ZincWorkerUtil.scala
+++ b/scalalib/api/src/mill/scalalib/api/ZincWorkerUtil.scala
@@ -4,6 +4,7 @@ import mill.api.Loose.Agg
import mill.api.PathRef
import scala.util.matching.Regex
+@deprecated("Use JvmWorkerUtil instead", "Mill 0.12.11")
trait ZincWorkerUtil {
def isDotty(scalaVersion: String): Boolean = scalaVersion.startsWith("0.")
@@ -158,4 +159,5 @@ trait ZincWorkerUtil {
}
}
+@deprecated("Use JvmWorkerUtil instead", "Mill 0.12.11")
object ZincWorkerUtil extends ZincWorkerUtil
diff --git a/scalalib/api/src/mill/scalalib/api/package.scala b/scalalib/api/src/mill/scalalib/api/package.scala
new file mode 100644
index 00000000000..01e3c578c92
--- /dev/null
+++ b/scalalib/api/src/mill/scalalib/api/package.scala
@@ -0,0 +1,16 @@
+package mill.scalalib
+
+import scala.annotation.nowarn
+
+package object api {
+ @nowarn("cat=deprecation")
+ type JvmWorkerApi = ZincWorkerApi
+ @nowarn("cat=deprecation")
+ val JvmWorkerApi = ZincWorkerApi
+
+ @nowarn("cat=deprecation")
+ type JvmWorkerUtil = ZincWorkerUtil
+ @nowarn("cat=deprecation")
+ val JvmWorkerUtil = ZincWorkerUtil
+
+}
diff --git a/scalalib/package.mill b/scalalib/package.mill
index d94750e3eda..fce2cf71d5b 100644
--- a/scalalib/package.mill
+++ b/scalalib/package.mill
@@ -1,21 +1,8 @@
package build.scalalib
-import scala.util.chaining._
-import com.github.lolgab.mill.mima.Mima
-import coursier.maven.MavenRepository
-import de.tobiasroeser.mill.vcs.version.VcsVersion
-import com.goyeau.mill.scalafix.ScalafixModule
import mill._
-import mill.api.JarManifest
-import mill.define.NamedTask
-import mill.main.Tasks
-import mill.scalalib._
-import mill.scalalib.api.ZincWorkerUtil
-import mill.scalalib.publish._
-import mill.util.Jvm
-import mill.resolve.SelectMode
import mill.contrib.buildinfo.BuildInfo
-import mill.T
-import mill.define.Cross
+
+import scala.util.chaining._
object `package` extends RootModule with build.MillStableScalaModule {
def moduleDeps = Seq(build.main, build.scalalib.api, build.testrunner)
def ivyDeps = Agg(build.Deps.scalafmtDynamic, build.Deps.scalaXml)
diff --git a/scalalib/src/mill/javalib/android/AndroidAppModule.scala b/scalalib/src/mill/javalib/android/AndroidAppModule.scala
index fe1c13e8c88..65b940f6e83 100644
--- a/scalalib/src/mill/javalib/android/AndroidAppModule.scala
+++ b/scalalib/src/mill/javalib/android/AndroidAppModule.scala
@@ -130,7 +130,7 @@ trait AndroidAppModule extends JavaModule {
* Users can customize the keystore file name to change this path.
*/
def androidReleaseKeyPath: T[Option[PathRef]] = Task {
- androidReleaseKeyName().map(name => PathRef(millSourcePath / name))
+ androidReleaseKeyName().map(name => PathRef(moduleDir / name))
}
/**
@@ -269,7 +269,7 @@ trait AndroidAppModule extends JavaModule {
*/
override def resources: T[Seq[PathRef]] = Task {
val libResFolders = androidUnpackArchives().flatMap(_.resources)
- libResFolders :+ PathRef(millSourcePath / "src/main/res")
+ libResFolders :+ PathRef(moduleDir / "src/main/res")
}
@internal
@@ -279,7 +279,7 @@ trait AndroidAppModule extends JavaModule {
@internal
override def bspBuildTarget: BspBuildTarget = super.bspBuildTarget.copy(
- baseDirectory = Some(millSourcePath / "src/main"),
+ baseDirectory = Some(moduleDir / "src/main"),
tags = Seq("application")
)
@@ -762,7 +762,7 @@ trait AndroidAppModule extends JavaModule {
os.call(
Seq(
androidSdkModule().lintToolPath().path.toString,
- (millSourcePath / "src/main").toString,
+ (moduleDir / "src/main").toString,
"--classpath",
cp,
"--sources",
@@ -1047,7 +1047,7 @@ trait AndroidAppModule extends JavaModule {
}
trait AndroidAppTests extends JavaTests {
- private def testPath = parent.millSourcePath / "src/test"
+ private def testPath = parent.moduleDir / "src/test"
override def sources: T[Seq[PathRef]] = Seq(PathRef(testPath / "java"))
@@ -1061,7 +1061,7 @@ trait AndroidAppModule extends JavaModule {
}
trait AndroidAppInstrumentedTests extends AndroidAppModule with AndroidTestModule {
- private def androidMainSourcePath = parent.millSourcePath
+ private def androidMainSourcePath = parent.moduleDir
private def androidTestPath = androidMainSourcePath / "src/androidTest"
override def moduleDeps: Seq[JavaModule] = Seq(parent)
diff --git a/scalalib/src/mill/javalib/checkstyle/CheckstyleModule.scala b/scalalib/src/mill/javalib/checkstyle/CheckstyleModule.scala
index 4f20af2e5bb..ea0a619d42b 100644
--- a/scalalib/src/mill/javalib/checkstyle/CheckstyleModule.scala
+++ b/scalalib/src/mill/javalib/checkstyle/CheckstyleModule.scala
@@ -39,7 +39,7 @@ trait CheckstyleModule extends JavaModule {
mainClass = "com.puppycrawl.tools.checkstyle.Main",
classPath = checkstyleClasspath().map(_.path).toVector,
mainArgs = args,
- cwd = millSourcePath, // allow passing relative paths for sources like src/a/b
+ cwd = moduleDir, // allow passing relative paths for sources like src/a/b
stdin = os.Inherit,
stdout = os.Inherit,
check = false
diff --git a/scalalib/src/mill/javalib/package.scala b/scalalib/src/mill/javalib/package.scala
index 780dec9f0a9..d54169f96d9 100644
--- a/scalalib/src/mill/javalib/package.scala
+++ b/scalalib/src/mill/javalib/package.scala
@@ -19,6 +19,8 @@ package object javalib extends mill.scalalib.JsonFormatters {
val ZincWorkerModule = mill.scalalib.ZincWorkerModule
type ZincWorkerModule = mill.scalalib.ZincWorkerModule
+ val JvmWorkerModule = mill.scalalib.JvmWorkerModule
+ type JvmWorkerModule = mill.scalalib.JvmWorkerModule
type CoursierModule = mill.scalalib.CoursierModule
diff --git a/scalalib/src/mill/javalib/palantirformat/PalantirFormatModule.scala b/scalalib/src/mill/javalib/palantirformat/PalantirFormatModule.scala
index cc8938cb9b0..5b6cff6d804 100644
--- a/scalalib/src/mill/javalib/palantirformat/PalantirFormatModule.scala
+++ b/scalalib/src/mill/javalib/palantirformat/PalantirFormatModule.scala
@@ -40,9 +40,7 @@ trait PalantirFormatBaseModule extends CoursierModule {
/**
* Path to options file for Palantir Java Format CLI. Defaults to `millSourcePath` `/` `palantirformat.options`.
*/
- def palantirformatOptions: T[PathRef] = Task.Source(
- millSourcePath / "palantirformat.options"
- )
+ def palantirformatOptions: T[PathRef] = Task.Source("palantirformat.options")
/**
* Palantir Java Format version. Defaults to `2.50.0`.
@@ -63,7 +61,7 @@ trait PalantirFormatModule extends JavaModule with PalantirFormatBaseModule {
* @param check if an exception should be raised when formatting errors are found
* - when set, files are not formatted
* @param sources list of file or folder path(s) to be processed
- * - path must be relative to [[millSourcePath]]
+ * - path must be relative to [[moduleDir]]
* - when empty, all [[sources]] are processed
*/
def palantirformat(
@@ -73,7 +71,7 @@ trait PalantirFormatModule extends JavaModule with PalantirFormatBaseModule {
val _sources =
if (sources.value.isEmpty) this.sources()
- else sources.value.iterator.map(rel => PathRef(millSourcePath / os.RelPath(rel)))
+ else sources.value.iterator.map(rel => PathRef(moduleDir / os.RelPath(rel)))
PalantirFormatModule.palantirAction(
_sources,
@@ -84,6 +82,7 @@ trait PalantirFormatModule extends JavaModule with PalantirFormatBaseModule {
)
}
}
+
object PalantirFormatModule extends ExternalModule with PalantirFormatBaseModule with TaskModule {
override def defaultCommandName(): String = "formatAll"
diff --git a/scalalib/src/mill/javalib/palantirformat/package.scala b/scalalib/src/mill/javalib/palantirformat/package.scala
new file mode 100644
index 00000000000..be62a40528e
--- /dev/null
+++ b/scalalib/src/mill/javalib/palantirformat/package.scala
@@ -0,0 +1,5 @@
+package mill.javalib.palantirformat
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(PalantirFormatModule)
diff --git a/scalalib/src/mill/scalalib/CrossModuleBase.scala b/scalalib/src/mill/scalalib/CrossModuleBase.scala
index 4cb343e41cc..07ae849f56e 100644
--- a/scalalib/src/mill/scalalib/CrossModuleBase.scala
+++ b/scalalib/src/mill/scalalib/CrossModuleBase.scala
@@ -3,7 +3,7 @@ package mill.scalalib
import mill.{T, Task}
import mill.define.Cross
import mill.define.Cross.Resolver
-import mill.scalalib.api.ZincWorkerUtil
+import mill.scalalib.api.JvmWorkerUtil
trait CrossModuleBase extends ScalaModule with Cross.Module[String] {
def crossScalaVersion: String = crossValue
@@ -11,9 +11,9 @@ trait CrossModuleBase extends ScalaModule with Cross.Module[String] {
def scalaVersion = Task { crossScalaVersion }
protected def scalaVersionDirectoryNames: Seq[String] =
- ZincWorkerUtil.matchingVersions(crossScalaVersion)
+ JvmWorkerUtil.matchingVersions(crossScalaVersion)
- override def crossWrapperSegments: List[String] = millModuleSegments.parts
+ override def crossWrapperSegments: List[String] = moduleSegments.parts
override def artifactNameParts: T[Seq[String]] =
super.artifactNameParts().patch(crossWrapperSegments.size - 1, Nil, 1)
@@ -24,7 +24,7 @@ trait CrossModuleBase extends ScalaModule with Cross.Module[String] {
crossScalaVersion
.split('.')
.inits
- .takeWhile(_.length > (if (ZincWorkerUtil.isScala3(crossScalaVersion)) 0 else 1))
+ .takeWhile(_.length > (if (JvmWorkerUtil.isScala3(crossScalaVersion)) 0 else 1))
.flatMap(prefix =>
c.crossModules
.find(_.crossScalaVersion.split('.').startsWith(prefix))
diff --git a/scalalib/src/mill/scalalib/CrossSbtModule.scala b/scalalib/src/mill/scalalib/CrossSbtModule.scala
index e80fce57571..9c9d14d0094 100644
--- a/scalalib/src/mill/scalalib/CrossSbtModule.scala
+++ b/scalalib/src/mill/scalalib/CrossSbtModule.scala
@@ -9,7 +9,7 @@ trait CrossSbtModule extends SbtModule with CrossModuleBase { outer =>
override def sources: T[Seq[PathRef]] = Task.Sources {
super.sources() ++ scalaVersionDirectoryNames.map(s =>
- PathRef(millSourcePath / "src/main" / s"scala-$s")
+ PathRef(moduleDir / "src/main" / s"scala-$s")
)
}
@@ -20,7 +20,7 @@ trait CrossSbtModule extends SbtModule with CrossModuleBase { outer =>
override def millSourcePath = outer.millSourcePath
override def sources = Task.Sources {
super.sources() ++ scalaVersionDirectoryNames.map(s =>
- PathRef(millSourcePath / "src/test" / s"scala-$s")
+ PathRef(moduleDir / "src/test" / s"scala-$s")
)
}
}
diff --git a/scalalib/src/mill/scalalib/CrossScalaModule.scala b/scalalib/src/mill/scalalib/CrossScalaModule.scala
index cb5a226d463..06baf4c7ce1 100644
--- a/scalalib/src/mill/scalalib/CrossScalaModule.scala
+++ b/scalalib/src/mill/scalalib/CrossScalaModule.scala
@@ -15,6 +15,6 @@ import mill.{T, Task}
trait CrossScalaModule extends ScalaModule with CrossModuleBase {
override def sources: T[Seq[PathRef]] = Task.Sources {
super.sources() ++
- scalaVersionDirectoryNames.map(s => PathRef(millSourcePath / s"src-$s"))
+ scalaVersionDirectoryNames.map(s => PathRef(moduleDir / s"src-$s"))
}
}
diff --git a/scalalib/src/mill/scalalib/CrossScalaVersionRanges.scala b/scalalib/src/mill/scalalib/CrossScalaVersionRanges.scala
index 0a8d6c613ed..86c2a9387d1 100644
--- a/scalalib/src/mill/scalalib/CrossScalaVersionRanges.scala
+++ b/scalalib/src/mill/scalalib/CrossScalaVersionRanges.scala
@@ -1,6 +1,6 @@
package mill.scalalib
-import mill.scalalib.api.ZincWorkerUtil
+import mill.scalalib.api.JvmWorkerUtil
/**
* Adds version range specific sources when mixed-in to a cross module like
@@ -18,5 +18,5 @@ trait CrossScalaVersionRanges extends CrossModuleBase {
override def scalaVersionDirectoryNames: Seq[String] =
super.scalaVersionDirectoryNames ++
- ZincWorkerUtil.versionRanges(crossScalaVersion, crossScalaVersionsRangeAllVersions)
+ JvmWorkerUtil.versionRanges(crossScalaVersion, crossScalaVersionsRangeAllVersions)
}
diff --git a/scalalib/src/mill/scalalib/Dep.scala b/scalalib/src/mill/scalalib/Dep.scala
index af7b509c3a9..0d7e764926b 100644
--- a/scalalib/src/mill/scalalib/Dep.scala
+++ b/scalalib/src/mill/scalalib/Dep.scala
@@ -3,7 +3,7 @@ package mill.scalalib
import upickle.default.{macroRW, ReadWriter => RW}
import mill.scalalib.CrossVersion._
import coursier.core.{Configuration, Dependency, MinimizedExclusions}
-import mill.scalalib.api.{Versions, ZincWorkerUtil}
+import mill.scalalib.api.{Versions, JvmWorkerUtil}
import scala.annotation.unused
case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
@@ -75,12 +75,12 @@ case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
*/
def withDottyCompat(scalaVersion: String): Dep =
cross match {
- case cross: Binary if ZincWorkerUtil.isDottyOrScala3(scalaVersion) =>
+ case cross: Binary if JvmWorkerUtil.isDottyOrScala3(scalaVersion) =>
val compatSuffix =
scalaVersion match {
- case ZincWorkerUtil.Scala3Version(_, _) | ZincWorkerUtil.Scala3EarlyVersion(_) =>
+ case JvmWorkerUtil.Scala3Version(_, _) | JvmWorkerUtil.Scala3EarlyVersion(_) =>
"_2.13"
- case ZincWorkerUtil.DottyVersion(minor, patch) =>
+ case JvmWorkerUtil.DottyVersion(minor, patch) =>
if (minor.toInt > 18 || minor.toInt == 18 && patch.toInt >= 1)
"_2.13"
else
diff --git a/scalalib/src/mill/scalalib/GenIdea.scala b/scalalib/src/mill/scalalib/GenIdea.scala
index 013e28673aa..a86bf4cc7d0 100644
--- a/scalalib/src/mill/scalalib/GenIdea.scala
+++ b/scalalib/src/mill/scalalib/GenIdea.scala
@@ -16,7 +16,7 @@ object GenIdea extends ExternalModule {
mill.main.RunScript.evaluateTasksNamed(
ev,
Seq(
- "mill.idea.GenIdea/"
+ "mill.idea/"
),
selectMode = SelectMode.Separated
)
diff --git a/scalalib/src/mill/scalalib/GenIdeaImpl.scala b/scalalib/src/mill/scalalib/GenIdeaImpl.scala
index 9863c23eb6b..b3b643b030c 100755
--- a/scalalib/src/mill/scalalib/GenIdeaImpl.scala
+++ b/scalalib/src/mill/scalalib/GenIdeaImpl.scala
@@ -22,7 +22,7 @@ case class GenIdeaImpl(
) {
import GenIdeaImpl._
- val workDir: Path = rootModule.millSourcePath
+ val workDir: Path = rootModule.moduleDir
val ideaDir: Path = workDir / ".idea"
val ideaConfigVersion = 4
@@ -91,7 +91,7 @@ object GenIdeaImpl {
/**
* Create the module name (to be used by Idea) for the module based on it segments.
- * @see [[Module.millModuleSegments]]
+ * @see [[Module.moduleSegments]]
*/
def moduleName(p: Segments): String = ???
sealed trait ResolvedLibrary { def path: os.Path }
diff --git a/scalalib/src/mill/scalalib/GenIdeaModule.scala b/scalalib/src/mill/scalalib/GenIdeaModule.scala
index 07dabf4a9ef..b4396f5cc09 100644
--- a/scalalib/src/mill/scalalib/GenIdeaModule.scala
+++ b/scalalib/src/mill/scalalib/GenIdeaModule.scala
@@ -10,7 +10,7 @@ import os.SubPath
trait GenIdeaModule extends Module {
import GenIdeaModule._
- def intellijModulePath: os.Path = millSourcePath
+ def intellijModulePath: os.Path = moduleDir
/**
* Skip Idea project file generation.
diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala
index cb1e314dd84..abb01d5bc7b 100644
--- a/scalalib/src/mill/scalalib/JavaModule.scala
+++ b/scalalib/src/mill/scalalib/JavaModule.scala
@@ -10,13 +10,13 @@ import coursier.util.{EitherT, ModuleMatcher, Monad}
import coursier.{Repository, Type}
import mainargs.{Flag, arg}
import mill.Agg
-import mill.api.{Ctx, JarManifest, MillException, PathRef, Result, internal}
+import mill.api.{Ctx, MillException, PathRef, Result, internal}
import mill.define.{Command, ModuleRef, Segment, Task, TaskModule}
import mill.scalalib.internal.ModuleUtils
import mill.scalalib.api.CompilationResult
import mill.scalalib.bsp.{BspBuildTarget, BspModule, BspUri, JvmBuildTarget}
import mill.scalalib.publish.Artifact
-import mill.util.Jvm
+import mill.util.{JarManifest, Jvm}
import os.{Path, ProcessOutput}
@@ -27,7 +27,7 @@ import scala.annotation.nowarn
*/
trait JavaModule
extends mill.Module
- with WithZincWorker
+ with WithJvmWorker
with TestModule.JavaModuleBase
with TaskModule
with RunModule
@@ -38,8 +38,8 @@ trait JavaModule
with SemanticDbJavaModule
with AssemblyModule { outer =>
- override def zincWorker: ModuleRef[ZincWorkerModule] = super.zincWorker
- @nowarn
+ override def zincWorker: ModuleRef[JvmWorkerModule] = super.zincWorker
+ @nowarn("cat=deprecation")
type JavaTests = JavaModuleTests
@deprecated("Use JavaTests instead", since = "Mill 0.11.10")
trait JavaModuleTests extends JavaModule with TestModule {
@@ -54,12 +54,12 @@ trait JavaModule
override def resolutionCustomizer: Task[Option[coursier.Resolution => coursier.Resolution]] =
outer.resolutionCustomizer
override def javacOptions: T[Seq[String]] = Task { outer.javacOptions() }
- override def zincWorker: ModuleRef[ZincWorkerModule] = outer.zincWorker
+ override def zincWorker: ModuleRef[JvmWorkerModule] = outer.zincWorker
override def skipIdea: Boolean = outer.skipIdea
override def runUseArgsFile: T[Boolean] = Task { outer.runUseArgsFile() }
override def sources = Task.Sources {
for (src <- outer.sources()) yield {
- PathRef(this.millSourcePath / src.path.relativeTo(outer.millSourcePath))
+ PathRef(this.moduleDir / src.path.relativeTo(outer.moduleDir))
}
}
@@ -355,7 +355,7 @@ trait JavaModule
/** Should only be called from [[moduleDepsChecked]] */
private lazy val recModuleDeps: Seq[JavaModule] =
ModuleUtils.recursive[JavaModule](
- (millModuleSegments ++ Seq(Segment.Label("moduleDeps"))).render,
+ (moduleSegments ++ Seq(Segment.Label("moduleDeps"))).render,
this,
_.moduleDeps
)
@@ -363,7 +363,7 @@ trait JavaModule
/** Should only be called from [[compileModuleDeps]] */
private lazy val recCompileModuleDeps: Seq[JavaModule] =
ModuleUtils.recursive[JavaModule](
- (millModuleSegments ++ Seq(Segment.Label("compileModuleDeps"))).render,
+ (moduleSegments ++ Seq(Segment.Label("compileModuleDeps"))).render,
this,
_.compileModuleDeps
)
@@ -371,7 +371,7 @@ trait JavaModule
/** Should only be called from [[runModuleDepsChecked]] */
private lazy val recRunModuleDeps: Seq[JavaModule] =
ModuleUtils.recursive[JavaModule](
- (millModuleSegments ++ Seq(Segment.Label("runModuleDeps"))).render,
+ (moduleSegments ++ Seq(Segment.Label("runModuleDeps"))).render,
this,
m => m.runModuleDeps ++ m.moduleDeps
)
@@ -379,7 +379,7 @@ trait JavaModule
/** Should only be called from [[bomModuleDepsChecked]] */
private lazy val recBomModuleDeps: Seq[BomModule] =
ModuleUtils.recursive[BomModule](
- (millModuleSegments ++ Seq(Segment.Label("bomModuleDeps"))).render,
+ (moduleSegments ++ Seq(Segment.Label("bomModuleDeps"))).render,
null,
mod => if (mod == null) bomModuleDeps else mod.bomModuleDeps
)
@@ -437,7 +437,7 @@ trait JavaModule
val deps = (normalDeps ++ compileDeps ++ runModuleDeps).distinct
val header = Option.when(includeHeader)(
- s"${if (recursive) "Recursive module" else "Module"} dependencies of ${millModuleSegments.render}:"
+ s"${if (recursive) "Recursive module" else "Module"} dependencies of ${moduleSegments.render}:"
).toSeq
val lines = deps.map { dep =>
val isNormal = normalDeps.contains(dep)
@@ -446,7 +446,7 @@ trait JavaModule
Option.when(!isNormal && runtimeDeps.contains(dep))("runtime")
).flatten
val suffix = if (markers.isEmpty) "" else markers.mkString(" (", ",", ")")
- " " + dep.millModuleSegments.render + suffix
+ " " + dep.moduleSegments.render + suffix
}
(header ++ lines).mkString("\n")
}
@@ -459,7 +459,7 @@ trait JavaModule
// This is exclusive to avoid scrambled output
Task.Command(exclusive = true) {
val asString = formatModuleDeps(recursive, true)()
- Task.log.outputStream.println(asString)
+ Task.log.streams.out.println(asString)
}
}
@@ -479,7 +479,7 @@ trait JavaModule
cs.Dependency(
cs.Module(
JavaModule.internalOrg,
- coursier.core.ModuleName(millModuleSegments.parts.mkString("-")),
+ coursier.core.ModuleName(moduleSegments.parts.mkString("-")),
Map.empty
),
JavaModule.internalVersion
@@ -528,7 +528,7 @@ trait JavaModule
val dep = coursier.core.Dependency(
coursier.core.Module(
coursier.core.Organization("mill-internal"),
- coursier.core.ModuleName(modDep.millModuleSegments.parts.mkString("-")),
+ coursier.core.ModuleName(modDep.moduleSegments.parts.mkString("-")),
Map.empty
),
"0+mill-internal"
@@ -878,7 +878,7 @@ trait JavaModule
* Keep in sync with [[bspCompileClassesPath]]
*/
def compile: T[mill.scalalib.api.CompilationResult] = Task(persistent = true) {
- zincWorker()
+ jvmWorker()
.worker()
.compileJava(
upstreamCompileOutput = upstreamCompileOutput(),
@@ -1437,7 +1437,7 @@ trait JavaModule
*/
def artifactName: T[String] = artifactNameParts().mkString("-")
- def artifactNameParts: T[Seq[String]] = millModuleSegments.parts
+ def artifactNameParts: T[Seq[String]] = moduleSegments.parts
/**
* The exact id of the artifact to be published. You probably don't want to override this.
@@ -1494,7 +1494,7 @@ trait JavaModule
Task.Command {
super.prepareOffline(all)()
resolvedIvyDeps()
- zincWorker().prepareOffline(all)()
+ jvmWorker().prepareOffline(all)()
resolvedRunIvyDeps()
Task.sequence(tasks)()
()
@@ -1519,7 +1519,7 @@ trait JavaModule
@internal
def bspJvmBuildTargetTask: Task[JvmBuildTarget] = Task.Anon {
JvmBuildTarget(
- javaHome = zincWorker()
+ javaHome = jvmWorker()
.javaHome()
.map(p => BspUri(p.path))
.orElse(Option(System.getProperty("java.home")).map(p => BspUri(os.Path(p)))),
diff --git a/scalalib/src/mill/scalalib/JlinkModule.scala b/scalalib/src/mill/scalalib/JlinkModule.scala
index a60df2eee4a..3ccd84c6678 100644
--- a/scalalib/src/mill/scalalib/JlinkModule.scala
+++ b/scalalib/src/mill/scalalib/JlinkModule.scala
@@ -54,7 +54,7 @@ trait JlinkModule extends JavaModule {
val classPath = jars.map(_.toString).mkString(sys.props("path.separator"))
val args = {
val baseArgs = Seq(
- Jvm.jdkTool("jmod", this.zincWorker().javaHome().map(_.path)),
+ Jvm.jdkTool("jmod", this.jvmWorker().javaHome().map(_.path)),
"create",
"--class-path",
classPath.toString,
@@ -82,7 +82,7 @@ trait JlinkModule extends JavaModule {
val outputPath = Task.dest / "jlink-runtime"
val args = Seq(
- Jvm.jdkTool("jlink", this.zincWorker().javaHome().map(_.path)),
+ Jvm.jdkTool("jlink", this.jvmWorker().javaHome().map(_.path)),
"--launcher",
s"${jlinkImageName()}=${jlinkModuleName()}/${jlinkMainClass()}",
"--module-path",
diff --git a/scalalib/src/mill/scalalib/JpackageModule.scala b/scalalib/src/mill/scalalib/JpackageModule.scala
index c4c0fc22c29..e1c8aa1f070 100644
--- a/scalalib/src/mill/scalalib/JpackageModule.scala
+++ b/scalalib/src/mill/scalalib/JpackageModule.scala
@@ -62,7 +62,7 @@ trait JpackageModule extends JavaModule {
val mainJarName = jars.head.last
val args: Seq[String] = Seq(
- Jvm.jdkTool("jpackage", this.zincWorker().javaHome().map(_.path)),
+ Jvm.jdkTool("jpackage", this.jvmWorker().javaHome().map(_.path)),
"--type",
appType,
"--name",
diff --git a/scalalib/src/mill/scalalib/Lib.scala b/scalalib/src/mill/scalalib/Lib.scala
index 285382f7eae..d8f4808cb58 100644
--- a/scalalib/src/mill/scalalib/Lib.scala
+++ b/scalalib/src/mill/scalalib/Lib.scala
@@ -9,7 +9,7 @@ import mill.api.{Ctx, Loose, PathRef, Result}
import mill.main.BuildInfo
import mill.main.client.EnvVars
import mill.util.Util
-import mill.scalalib.api.ZincWorkerUtil
+import mill.scalalib.api.JvmWorkerUtil
object Lib {
def depToDependencyJava(dep: Dep, platformSuffix: String = ""): Dependency = {
@@ -19,7 +19,7 @@ object Lib {
def depToDependency(dep: Dep, scalaVersion: String, platformSuffix: String = ""): Dependency =
dep.toDependency(
- binaryVersion = ZincWorkerUtil.scalaBinaryVersion(scalaVersion),
+ binaryVersion = JvmWorkerUtil.scalaBinaryVersion(scalaVersion),
fullVersion = scalaVersion,
platformSuffix = platformSuffix
)
@@ -127,7 +127,7 @@ object Lib {
/**
* Resolve dependencies using Coursier.
*
- * We do not bother breaking this out into the separate ZincWorker classpath,
+ * We do not bother breaking this out into the separate JvmWorker classpath,
* because Coursier is already bundled with mill/Ammonite to support the
* `import $ivy` syntax.
*/
@@ -212,11 +212,11 @@ object Lib {
)
def scalaCompilerIvyDeps(scalaOrganization: String, scalaVersion: String): Loose.Agg[Dep] =
- if (ZincWorkerUtil.isDotty(scalaVersion))
+ if (JvmWorkerUtil.isDotty(scalaVersion))
Agg(
ivy"$scalaOrganization::dotty-compiler:$scalaVersion".forceVersion()
)
- else if (ZincWorkerUtil.isScala3(scalaVersion))
+ else if (JvmWorkerUtil.isScala3(scalaVersion))
Agg(
ivy"$scalaOrganization::scala3-compiler:$scalaVersion".forceVersion()
)
@@ -227,16 +227,16 @@ object Lib {
)
def scalaDocIvyDeps(scalaOrganization: String, scalaVersion: String): Loose.Agg[Dep] =
- if (ZincWorkerUtil.isDotty(scalaVersion))
+ if (JvmWorkerUtil.isDotty(scalaVersion))
Agg(
ivy"$scalaOrganization::dotty-doc:$scalaVersion".forceVersion()
)
- else if (ZincWorkerUtil.isScala3Milestone(scalaVersion))
+ else if (JvmWorkerUtil.isScala3Milestone(scalaVersion))
Agg(
// 3.0.0-RC1 > scalaVersion >= 3.0.0-M1 still uses dotty-doc, but under a different artifact name
ivy"$scalaOrganization::scala3-doc:$scalaVersion".forceVersion()
)
- else if (ZincWorkerUtil.isScala3(scalaVersion))
+ else if (JvmWorkerUtil.isScala3(scalaVersion))
Agg(
// scalaVersion >= 3.0.0-RC1 uses scaladoc
ivy"$scalaOrganization::scaladoc:$scalaVersion".forceVersion()
@@ -246,11 +246,11 @@ object Lib {
scalaCompilerIvyDeps(scalaOrganization, scalaVersion)
def scalaRuntimeIvyDeps(scalaOrganization: String, scalaVersion: String): Loose.Agg[Dep] =
- if (ZincWorkerUtil.isDotty(scalaVersion)) {
+ if (JvmWorkerUtil.isDotty(scalaVersion)) {
Agg(
ivy"$scalaOrganization::dotty-library:$scalaVersion".forceVersion()
)
- } else if (ZincWorkerUtil.isScala3(scalaVersion))
+ } else if (JvmWorkerUtil.isScala3(scalaVersion))
Agg(
ivy"$scalaOrganization::scala3-library:$scalaVersion".forceVersion()
)
diff --git a/scalalib/src/mill/scalalib/MavenModule.scala b/scalalib/src/mill/scalalib/MavenModule.scala
index 58b7d4febc9..ea9b9460806 100644
--- a/scalalib/src/mill/scalalib/MavenModule.scala
+++ b/scalalib/src/mill/scalalib/MavenModule.scala
@@ -11,25 +11,17 @@ import scala.annotation.nowarn
*/
trait MavenModule extends JavaModule { outer =>
- override def sources = Task.Sources(
- millSourcePath / "src/main/java"
- )
- override def resources = Task.Sources {
- millSourcePath / "src/main/resources"
- }
+ override def sources = Task.Sources("src/main/java")
+ override def resources = Task.Sources("src/main/resources")
- @nowarn
+ @nowarn("cat=deprecation")
type MavenTests = MavenModuleTests
@deprecated("Use MavenTests instead", since = "Mill 0.11.10")
trait MavenModuleTests extends JavaTests {
override def millSourcePath = outer.millSourcePath
- override def intellijModulePath: os.Path = outer.millSourcePath / "src/test"
+ override def intellijModulePath: os.Path = outer.moduleDir / "src/test"
- override def sources = Task.Sources(
- millSourcePath / "src/test/java"
- )
- override def resources = Task.Sources {
- millSourcePath / "src/test/resources"
- }
+ override def sources = Task.Sources("src/test/java")
+ override def resources = Task.Sources("src/test/resources")
}
}
diff --git a/scalalib/src/mill/scalalib/NativeImageModule.scala b/scalalib/src/mill/scalalib/NativeImageModule.scala
index 6eaf19c9078..ba1bbddbe08 100644
--- a/scalalib/src/mill/scalalib/NativeImageModule.scala
+++ b/scalalib/src/mill/scalalib/NativeImageModule.scala
@@ -19,7 +19,7 @@ import scala.util.Properties
* }}}
*/
@mill.api.experimental
-trait NativeImageModule extends WithZincWorker {
+trait NativeImageModule extends WithJvmWorker {
def runClasspath: T[Seq[PathRef]]
def finalMainClass: T[String]
@@ -75,7 +75,7 @@ trait NativeImageModule extends WithZincWorker {
* @note The task fails if the `native-image` Tool is not found.
*/
def nativeImageTool: T[PathRef] = Task {
- zincWorker().javaHome().map(_.path)
+ jvmWorker().javaHome().map(_.path)
.orElse(sys.env.get("GRAALVM_HOME").map(os.Path(_))) match {
case Some(home) =>
val tool = if (Properties.isWin) "native-image.cmd" else "native-image"
diff --git a/scalalib/src/mill/scalalib/PlatformModuleBase.scala b/scalalib/src/mill/scalalib/PlatformModuleBase.scala
index f9c6e4ca111..f84deffd002 100644
--- a/scalalib/src/mill/scalalib/PlatformModuleBase.scala
+++ b/scalalib/src/mill/scalalib/PlatformModuleBase.scala
@@ -10,7 +10,7 @@ trait PlatformModuleBase extends JavaModule {
* The platform suffix of this [[PlatformModuleBase]]. Useful if you want to
* further customize the source paths or artifact names.
*/
- def platformCrossSuffix: String = millModuleSegments
+ def platformCrossSuffix: String = moduleSegments
.value
.collect { case l: mill.define.Segment.Label => l.value }
.last
diff --git a/scalalib/src/mill/scalalib/PlatformScalaModule.scala b/scalalib/src/mill/scalalib/PlatformScalaModule.scala
index 2655d904979..3b8e94de134 100644
--- a/scalalib/src/mill/scalalib/PlatformScalaModule.scala
+++ b/scalalib/src/mill/scalalib/PlatformScalaModule.scala
@@ -8,7 +8,7 @@ import mill.{PathRef, T, Task}
* `src-js/` and can be used inside a [[CrossScalaModule.Base]], to get one
* source folder per platform per version e.g. `src-2.12-jvm/`.
*
- * Adjusts the [[millSourcePath]] and [[artifactNameParts]] to ignore the last
+ * Adjusts the [[moduleDir]] and [[artifactNameParts]] to ignore the last
* path segment, which is assumed to be the name of the platform the module is
* built against and not something that should affect the filesystem path or
* artifact name
@@ -22,7 +22,7 @@ trait PlatformScalaModule extends /* PlatformModuleBase with*/ ScalaModule {
* The platform suffix of this [[PlatformScalaModule]]. Useful if you want to
* further customize the source paths or artifact names.
*/
- def platformScalaSuffix: String = millModuleSegments
+ def platformScalaSuffix: String = moduleSegments
.value
.collect { case l: mill.define.Segment.Label => l.value }
.last
diff --git a/scalalib/src/mill/scalalib/RunModule.scala b/scalalib/src/mill/scalalib/RunModule.scala
index 0365c98c5a0..9419d07fe79 100644
--- a/scalalib/src/mill/scalalib/RunModule.scala
+++ b/scalalib/src/mill/scalalib/RunModule.scala
@@ -14,7 +14,7 @@ import scala.util.control.NonFatal
import mill.scalalib.classgraph.ClassgraphWorkerModule
-trait RunModule extends WithZincWorker {
+trait RunModule extends WithJvmWorker {
def classgraphWorkerModule: ModuleRef[ClassgraphWorkerModule] = ModuleRef(ClassgraphWorkerModule)
@@ -146,7 +146,7 @@ trait RunModule extends WithZincWorker {
forkArgs(),
forkEnv(),
runUseArgsFile(),
- zincWorker().javaHome().map(_.path)
+ jvmWorker().javaHome().map(_.path)
)
}
@@ -173,7 +173,7 @@ trait RunModule extends WithZincWorker {
) ++ args().value,
mainClass = "mill.scalalib.backgroundwrapper.MillBackgroundWrapper",
workingDir = forkWorkingDir(),
- extraRunClasspath = zincWorker().backgroundWrapperClasspath().map(_.path).toSeq,
+ extraRunClasspath = jvmWorker().backgroundWrapperClasspath().map(_.path).toSeq,
background = true,
runBackgroundLogToConsole = runBackgroundLogToConsole
)
diff --git a/scalalib/src/mill/scalalib/SbtModule.scala b/scalalib/src/mill/scalalib/SbtModule.scala
index 2c985f69d6c..0ec0e0dcfcb 100644
--- a/scalalib/src/mill/scalalib/SbtModule.scala
+++ b/scalalib/src/mill/scalalib/SbtModule.scala
@@ -10,8 +10,8 @@ import scala.annotation.nowarn
trait SbtModule extends ScalaModule with MavenModule {
override def sources = Task.Sources(
- millSourcePath / "src/main/scala",
- millSourcePath / "src/main/java"
+ "src/main/scala",
+ "src/main/java"
)
@nowarn
@@ -19,8 +19,8 @@ trait SbtModule extends ScalaModule with MavenModule {
@deprecated("Use SbtTests instead", since = "Mill 0.11.10")
trait SbtModuleTests extends ScalaTests with MavenTests {
override def sources = Task.Sources(
- millSourcePath / "src/test/scala",
- millSourcePath / "src/test/java"
+ "src/test/scala",
+ "src/test/java"
)
}
}
diff --git a/scalalib/src/mill/scalalib/ScalaModule.scala b/scalalib/src/mill/scalalib/ScalaModule.scala
index 5b3f06cd4fe..2eba272a12d 100644
--- a/scalalib/src/mill/scalalib/ScalaModule.scala
+++ b/scalalib/src/mill/scalalib/ScalaModule.scala
@@ -6,7 +6,7 @@ import mill.main.BuildInfo
import mill.util.{Jvm, Util}
import mill.util.Jvm.createJar
import mill.api.Loose.Agg
-import mill.scalalib.api.{CompilationResult, Versions, ZincWorkerUtil}
+import mill.scalalib.api.{CompilationResult, Versions, JvmWorkerUtil}
import mainargs.Flag
import mill.scalalib.bsp.{BspBuildTarget, BspModule, ScalaBuildTarget, ScalaPlatform}
import mill.scalalib.dependency.versions.{ValidVersion, Version}
@@ -40,7 +40,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
* @return
*/
def scalaOrganization: T[String] = Task {
- if (ZincWorkerUtil.isDotty(scalaVersion()))
+ if (JvmWorkerUtil.isDotty(scalaVersion()))
"ch.epfl.lamp"
else
"org.scala-lang"
@@ -61,9 +61,9 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
override def mapDependencies: Task[coursier.Dependency => coursier.Dependency] = Task.Anon {
super.mapDependencies().andThen { (d: coursier.Dependency) =>
val artifacts =
- if (ZincWorkerUtil.isDotty(scalaVersion()))
+ if (JvmWorkerUtil.isDotty(scalaVersion()))
Set("dotty-library", "dotty-compiler")
- else if (ZincWorkerUtil.isScala3(scalaVersion()))
+ else if (JvmWorkerUtil.isScala3(scalaVersion()))
Set("scala3-library", "scala3-compiler")
else
Set("scala-library", "scala-compiler", "scala-reflect")
@@ -88,7 +88,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
publish.Artifact.fromDep(
_: Dep,
scalaVersion(),
- ZincWorkerUtil.scalaBinaryVersion(scalaVersion()),
+ JvmWorkerUtil.scalaBinaryVersion(scalaVersion()),
platformSuffix()
)
}
@@ -207,7 +207,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
*/
def scalaDocOptions: T[Seq[String]] = Task {
val defaults =
- if (ZincWorkerUtil.isDottyOrScala3(scalaVersion()))
+ if (JvmWorkerUtil.isDottyOrScala3(scalaVersion()))
Seq(
"-project",
artifactName()
@@ -270,7 +270,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
|You may want to select another version. Upgrading to a more recent Scala version is recommended.
|For details, see: https://github.com/sbt/zinc/issues/1010""".stripMargin
)
- zincWorker()
+ jvmWorker()
.worker()
.compileMixed(
upstreamCompileOutput = upstreamCompileOutput(),
@@ -309,9 +309,8 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
}
override def docSources: T[Seq[PathRef]] = Task {
- if (
- ZincWorkerUtil.isScala3(scalaVersion()) && !ZincWorkerUtil.isScala3Milestone(scalaVersion())
- ) Seq(compile().classes)
+ if (JvmWorkerUtil.isScala3(scalaVersion()) && !JvmWorkerUtil.isScala3Milestone(scalaVersion()))
+ Seq(compile().classes)
else allSources()
}
@@ -328,7 +327,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
def packageWithZinc(options: Seq[String], files: Seq[os.Path], javadocDir: os.Path) = {
if (files.isEmpty) Result.Success(createJar(Agg(javadocDir))(Task.dest))
else {
- zincWorker()
+ jvmWorker()
.worker()
.docJar(
scalaVersion(),
@@ -344,9 +343,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
}
}
- if (
- ZincWorkerUtil.isDotty(scalaVersion()) || ZincWorkerUtil.isScala3Milestone(scalaVersion())
- ) { // dottydoc
+ if (JvmWorkerUtil.isDotty(scalaVersion()) || JvmWorkerUtil.isScala3Milestone(scalaVersion())) { // dottydoc
val javadocDir = Task.dest / "javadoc"
os.makeDir.all(javadocDir)
@@ -370,7 +367,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
javadocDir / "_site"
)
- } else if (ZincWorkerUtil.isScala3(scalaVersion())) { // scaladoc 3
+ } else if (JvmWorkerUtil.isScala3(scalaVersion())) { // scaladoc 3
val javadocDir = Task.dest / "javadoc"
os.makeDir.all(javadocDir)
@@ -436,7 +433,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
Jvm.callProcess(
mainClass =
- if (ZincWorkerUtil.isDottyOrScala3(scalaVersion()))
+ if (JvmWorkerUtil.isDottyOrScala3(scalaVersion()))
"dotty.tools.repl.Main"
else
"scala.tools.nsc.MainGenericRunner",
@@ -536,12 +533,12 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
*/
def artifactScalaVersion: T[String] = Task {
if (crossFullScalaVersion()) scalaVersion()
- else ZincWorkerUtil.scalaBinaryVersion(scalaVersion())
+ else JvmWorkerUtil.scalaBinaryVersion(scalaVersion())
}
override def zincAuxiliaryClassFileExtensions: T[Seq[String]] = Task {
super.zincAuxiliaryClassFileExtensions() ++ (
- if (ZincWorkerUtil.isScala3(scalaVersion())) Seq("tasty")
+ if (JvmWorkerUtil.isScala3(scalaVersion())) Seq("tasty")
else Seq.empty[String]
)
}
@@ -568,7 +565,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
defaultResolver().classpath(
scalaDocPluginIvyDeps()
)
- zincWorker().scalaCompilerBridgeJar(
+ jvmWorker().scalaCompilerBridgeJar(
scalaVersion(),
scalaOrganization(),
defaultResolver()
@@ -596,7 +593,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
ScalaBuildTarget(
scalaOrganization = scalaOrganization(),
scalaVersion = scalaVersion(),
- scalaBinaryVersion = ZincWorkerUtil.scalaBinaryVersion(scalaVersion()),
+ scalaBinaryVersion = JvmWorkerUtil.scalaBinaryVersion(scalaVersion()),
platform = ScalaPlatform.JVM,
jars = scalaCompilerClasspath().map(_.path.toNIO.toUri.toString).iterator.toSeq,
jvmBuildTarget = Some(bspJvmBuildTargetTask())
@@ -622,7 +619,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
val stopAfterSemanticDbOpts =
if (isMixedProject) Seq.empty else Seq("-Ystop-after:semanticdb-typer")
- val additionalScalacOptions = if (ZincWorkerUtil.isScala3(sv)) {
+ val additionalScalacOptions = if (JvmWorkerUtil.isScala3(sv)) {
Seq("-Xsemanticdb", s"-sourceroot:${T.workspace}")
} else {
Seq(
@@ -643,7 +640,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
Task.log.debug(s"effective scalac options: ${scalacOptions}")
Task.log.debug(s"effective javac options: ${javacOpts}")
- zincWorker().worker()
+ jvmWorker().worker()
.compileMixed(
upstreamCompileOutput = upstreamCompileOutput(),
sources = allSourceFiles().map(_.path),
diff --git a/scalalib/src/mill/scalalib/TestModule.scala b/scalalib/src/mill/scalalib/TestModule.scala
index 195b5f4263d..6d62ab009b9 100644
--- a/scalalib/src/mill/scalalib/TestModule.scala
+++ b/scalalib/src/mill/scalalib/TestModule.scala
@@ -9,7 +9,7 @@ import mill.{Agg, T}
trait TestModule
extends TestModule.JavaModuleBase
- with WithZincWorker
+ with WithJvmWorker
with RunModule
with TaskModule {
@@ -41,15 +41,15 @@ trait TestModule
def testFramework: T[String]
def discoveredTestClasses: T[Seq[String]] = Task {
- val classes = if (zincWorker().javaHome().isDefined) {
+ val classes = if (jvmWorker().javaHome().isDefined) {
Jvm.callProcess(
mainClass = "mill.testrunner.DiscoverTestsMain",
- classPath = zincWorker().scalalibClasspath().map(_.path).toVector,
+ classPath = jvmWorker().scalalibClasspath().map(_.path).toVector,
mainArgs =
runClasspath().flatMap(p => Seq("--runCp", p.path.toString())) ++
testClasspath().flatMap(p => Seq("--testCp", p.path.toString())) ++
Seq("--framework", testFramework()),
- javaHome = zincWorker().javaHome().map(_.path),
+ javaHome = jvmWorker().javaHome().map(_.path),
stdin = os.Inherit,
stdout = os.Pipe,
cwd = Task.dest
@@ -175,10 +175,10 @@ trait TestModule
os.write(argsFile, upickle.default.write(testArgs))
val testRunnerClasspathArg =
- zincWorker().scalalibClasspath()
+ jvmWorker().scalalibClasspath()
.map(_.path.toNIO.toUri.toURL).mkString(",")
- val cp = (runClasspath() ++ zincWorker().testrunnerEntrypointClasspath()).map(_.path.toString)
+ val cp = (runClasspath() ++ jvmWorker().testrunnerEntrypointClasspath()).map(_.path.toString)
Result.Success((mainClass, testRunnerClasspathArg, argsFile.toString, cp))
}
@@ -205,19 +205,19 @@ trait TestModule
testUseArgsFile(),
forkArgs(),
globSelectors(),
- zincWorker().scalalibClasspath(),
+ jvmWorker().scalalibClasspath(),
resources(),
testFramework(),
runClasspath(),
testClasspath(),
args(),
testForkGrouping(),
- zincWorker().testrunnerEntrypointClasspath(),
+ jvmWorker().testrunnerEntrypointClasspath(),
forkEnv(),
testSandboxWorkingDir(),
forkWorkingDir(),
testReportXml(),
- zincWorker().javaHome().map(_.path),
+ jvmWorker().javaHome().map(_.path),
testParallelism()
)
testModuleUtil.runTests()
diff --git a/scalalib/src/mill/scalalib/UnidocModule.scala b/scalalib/src/mill/scalalib/UnidocModule.scala
index 42d5a082f02..4fb53372c2d 100644
--- a/scalalib/src/mill/scalalib/UnidocModule.scala
+++ b/scalalib/src/mill/scalalib/UnidocModule.scala
@@ -19,6 +19,15 @@ trait UnidocModule extends ScalaModule {
allSourceFiles() ++ Task.traverse(moduleDeps)(_.allSourceFiles)().flatten
}
+ /** The title of the scaladoc site. */
+ def unidocDocumentTitle: T[String] = Task { "Mill" }
+
+ /** Extra options passed to scaladoc. */
+ def unidocOptions: T[Seq[String]] = Task { Seq.empty[String] }
+
+ /**
+ * @param local whether to use 'file://' as the `-doc-source-url`.
+ */
def unidocCommon(local: Boolean) = Task.Anon {
val unidocSourceFiles0 = unidocSourceFiles()
@@ -30,7 +39,7 @@ trait UnidocModule extends ScalaModule {
// below is for scala-2 variant
val options: Seq[String] = Seq(
"-doc-title",
- "Mill",
+ unidocDocumentTitle(),
"-d",
Task.dest.toString,
"-classpath",
@@ -48,9 +57,9 @@ trait UnidocModule extends ScalaModule {
"-sourcepath",
Task.workspace.toString
)
- }
+ } ++ unidocOptions()
- zincWorker().worker().docJar(
+ jvmWorker().worker().docJar(
scalaVersion(),
scalaOrganization(),
scalaDocClasspath(),
diff --git a/scalalib/src/mill/scalalib/WithZincWorker.scala b/scalalib/src/mill/scalalib/WithZincWorker.scala
index 0d4754cf224..2cfd63240ee 100644
--- a/scalalib/src/mill/scalalib/WithZincWorker.scala
+++ b/scalalib/src/mill/scalalib/WithZincWorker.scala
@@ -2,9 +2,20 @@ package mill.scalalib
import mill.define.{Module, ModuleRef}
+import scala.annotation.nowarn
+
/**
* Common trait for modules that use either a custom or a globally shared [[ZincWorkerModule]].
+ *
+ * Deprecation info: This will be renamed to WithJvmWorker in Mill 0.13
*/
+@deprecated("This will be renamed to WithJvmWorker in Mill 0.13", "Mill 0.12.11")
trait WithZincWorker extends Module {
+ @deprecated(
+ "Use jvmWorker for read access. This def will be renamed to `jvmWorker` in Mill 0.13",
+ "Mill 0.12.11"
+ )
def zincWorker: ModuleRef[ZincWorkerModule] = ModuleRef(ZincWorkerModule)
+ @nowarn("cat=deprecation")
+ final def jvmWorker: ModuleRef[JvmWorkerModule] = zincWorker
}
diff --git a/scalalib/src/mill/scalalib/ZincWorkerModule.scala b/scalalib/src/mill/scalalib/ZincWorkerModule.scala
index 7ebdc6408c4..7c5d44c763a 100644
--- a/scalalib/src/mill/scalalib/ZincWorkerModule.scala
+++ b/scalalib/src/mill/scalalib/ZincWorkerModule.scala
@@ -13,6 +13,7 @@ import mill.scalalib.CoursierModule.Resolver
/**
* A default implementation of [[ZincWorkerModule]]
*/
+@deprecated("Use JvmWorkerModule instead", "Mill 0.12.11")
object ZincWorkerModule extends ExternalModule with ZincWorkerModule with CoursierModule {
lazy val millDiscover = Discover[this.type]
}
@@ -20,6 +21,7 @@ object ZincWorkerModule extends ExternalModule with ZincWorkerModule with Coursi
/**
* A module managing an in-memory Zinc Scala incremental compiler
*/
+@deprecated("Use JvmWorkerModule instead", "Mill 0.12.11")
trait ZincWorkerModule extends mill.Module with OfflineSupportModule with CoursierModule {
def jvmId: mill.define.Target[String] = Task[String] { "" }
diff --git a/scalalib/src/mill/scalalib/bsp/BspModule.scala b/scalalib/src/mill/scalalib/bsp/BspModule.scala
index edd208ca579..2dad50955f0 100644
--- a/scalalib/src/mill/scalalib/bsp/BspModule.scala
+++ b/scalalib/src/mill/scalalib/bsp/BspModule.scala
@@ -19,7 +19,7 @@ trait BspModule extends Module {
@internal
def bspBuildTarget: BspBuildTarget = BspBuildTarget(
displayName = Some(bspDisplayName),
- baseDirectory = Some(millSourcePath),
+ baseDirectory = Some(moduleDir),
tags = Seq(Tag.Library, Tag.Application),
languageIds = Seq(),
canCompile = false,
diff --git a/scalalib/src/mill/scalalib/dependency/versions/VersionsFinder.scala b/scalalib/src/mill/scalalib/dependency/versions/VersionsFinder.scala
index 9e079eab184..8402aca3736 100644
--- a/scalalib/src/mill/scalalib/dependency/versions/VersionsFinder.scala
+++ b/scalalib/src/mill/scalalib/dependency/versions/VersionsFinder.scala
@@ -17,7 +17,7 @@ private[dependency] object VersionsFinder {
rootModule: BaseModule
): Seq[ModuleDependenciesVersions] = {
- val javaModules = rootModule.millInternal.modules.collect {
+ val javaModules = rootModule.moduleInternal.modules.collect {
case javaModule: JavaModule => javaModule
}
diff --git a/scalalib/src/mill/scalalib/internal/ModuleUtils.scala b/scalalib/src/mill/scalalib/internal/ModuleUtils.scala
index 52af88f5ff5..34494d37119 100644
--- a/scalalib/src/mill/scalalib/internal/ModuleUtils.scala
+++ b/scalalib/src/mill/scalalib/internal/ModuleUtils.scala
@@ -12,7 +12,7 @@ object ModuleUtils {
* Computes a display name for a module which is also disambiguates foreign modules.
*/
def moduleDisplayName(module: Module): String = {
- (module.millModuleShared.value.getOrElse(Segments()) ++ module.millModuleSegments).render
+ (module.millModuleShared.value.getOrElse(Segments()) ++ module.moduleSegments).render
}
/**
diff --git a/scalalib/src/mill/scalalib/package.scala b/scalalib/src/mill/scalalib/package.scala
index a7d232582ed..5024bd4a232 100644
--- a/scalalib/src/mill/scalalib/package.scala
+++ b/scalalib/src/mill/scalalib/package.scala
@@ -1,5 +1,7 @@
package mill
+import scala.annotation.nowarn
+
package object scalalib extends mill.scalalib.JsonFormatters {
implicit class DepSyntax(ctx: StringContext) {
def ivy(args: Any*): Dep = Dep.parse {
@@ -8,5 +10,16 @@ package object scalalib extends mill.scalalib.JsonFormatters {
ctx.parts.drop(args.length)
).mkString
}
+
+ // Forward-compatibility with Mill 1.0
+ def mvn(args: Any*): Dep = ivy(args: _*)
}
+
+ @nowarn("cat=deprecation")
+ type JvmWorkerModule = ZincWorkerModule
+ @nowarn("cat=deprecation")
+ val JvmWorkerModule = ZincWorkerModule
+
+ @nowarn("cat=deprecation")
+ type WithJvmWorker = WithZincWorker
}
diff --git a/scalalib/src/mill/scalalib/scalafmt/package.scala b/scalalib/src/mill/scalalib/scalafmt/package.scala
new file mode 100644
index 00000000000..fd43f690266
--- /dev/null
+++ b/scalalib/src/mill/scalalib/scalafmt/package.scala
@@ -0,0 +1,5 @@
+package mill.scalalib.scalafmt
+
+import mill.define.ExternalModule
+
+object `package` extends ExternalModule.Alias(ScalafmtModule)
diff --git a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala
index 652b3aeed5d..50debd6a259 100644
--- a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala
+++ b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala
@@ -2,7 +2,7 @@ package mill.scalalib.worker
import mill.api.Loose.Agg
import mill.api.{CachedFactory, CompileProblemReporter, Ctx, PathRef, Result, internal}
-import mill.scalalib.api.{CompilationResult, Versions, ZincWorkerApi, ZincWorkerUtil}
+import mill.scalalib.api.{CompilationResult, JvmWorkerApi, JvmWorkerUtil, Versions}
import os.Path
import sbt.internal.inc.{
Analysis,
@@ -45,7 +45,7 @@ import scala.util.Properties.isWin
@internal
class ZincWorkerImpl(
compilerBridge: Either[
- (ZincWorkerApi.Ctx, (String, String) => (Option[Agg[PathRef]], PathRef)),
+ (JvmWorkerApi.Ctx, (String, String) => (Option[Agg[PathRef]], PathRef)),
String => PathRef
],
jobs: Int,
@@ -53,9 +53,9 @@ class ZincWorkerImpl(
zincLogDebug: Boolean,
javaHome: Option[PathRef],
close0: () => Unit
-) extends ZincWorkerApi with AutoCloseable {
+) extends JvmWorkerApi with AutoCloseable {
val libraryJarNameGrep: (Agg[PathRef], String) => PathRef =
- ZincWorkerUtil.grepJar(_, "scala-library", _, sources = false)
+ JvmWorkerUtil.grepJar(_, "scala-library", _, sources = false)
case class CompileCacheKey(
scalaVersion: String,
@@ -93,7 +93,7 @@ class ZincWorkerImpl(
compilerClasspath,
// we don't support too outdated dotty versions
// and because there will be no scala 2.14, so hardcode "2.13." here is acceptable
- if (ZincWorkerUtil.isDottyOrScala3(key.scalaVersion)) "2.13." else key.scalaVersion
+ if (JvmWorkerUtil.isDottyOrScala3(key.scalaVersion)) "2.13." else key.scalaVersion
).path.toIO),
compilerJars = combinedCompilerJars,
allJars = combinedCompilerJars,
@@ -218,7 +218,7 @@ class ZincWorkerImpl(
compilerClasspath: Agg[PathRef],
scalacPluginClasspath: Agg[PathRef],
args: Seq[String]
- )(implicit ctx: ZincWorkerApi.Ctx): Boolean = {
+ )(implicit ctx: JvmWorkerApi.Ctx): Boolean = {
withCompilers(
scalaVersion,
scalaOrganization,
@@ -229,9 +229,7 @@ class ZincWorkerImpl(
// Not sure why dotty scaladoc is flaky, but add retries to workaround it
// https://github.com/com-lihaoyi/mill/issues/4556
mill.api.Retry(count = 2) {
- if (
- ZincWorkerUtil.isDotty(scalaVersion) || ZincWorkerUtil.isScala3Milestone(scalaVersion)
- ) {
+ if (JvmWorkerUtil.isDotty(scalaVersion) || JvmWorkerUtil.isScala3Milestone(scalaVersion)) {
// dotty 0.x and scala 3 milestones use the dotty-doc tool
val dottydocClass =
compilers.scalac().scalaInstance().loader().loadClass("dotty.tools.dottydoc.DocDriver")
@@ -240,7 +238,7 @@ class ZincWorkerImpl(
dottydocMethod.invoke(dottydocClass.getConstructor().newInstance(), args.toArray)
val hasErrorsMethod = reporter.getClass().getMethod("hasErrors")
!hasErrorsMethod.invoke(reporter).asInstanceOf[Boolean]
- } else if (ZincWorkerUtil.isScala3(scalaVersion)) {
+ } else if (JvmWorkerUtil.isScala3(scalaVersion)) {
// DottyDoc makes use of `com.fasterxml.jackson.databind.Module` which
// requires the ContextClassLoader to be set appropriately
mill.api.ClassLoader.withContextClassLoader(getClass.getClassLoader) {
@@ -269,7 +267,7 @@ class ZincWorkerImpl(
/** Compile the SBT/Zinc compiler bridge in the `compileDest` directory */
def compileZincBridge(
- ctx0: ZincWorkerApi.Ctx,
+ ctx0: JvmWorkerApi.Ctx,
workingDir: os.Path,
compileDest: os.Path,
scalaVersion: String,
@@ -330,7 +328,7 @@ class ZincWorkerImpl(
(Seq(javacExe) ++ argsArray).!
} else if (allScala) {
val compilerMain = classloader.loadClass(
- if (ZincWorkerUtil.isDottyOrScala3(scalaVersion)) "dotty.tools.dotc.Main"
+ if (JvmWorkerUtil.isDottyOrScala3(scalaVersion)) "dotty.tools.dotc.Main"
else "scala.tools.nsc.Main"
)
compilerMain
@@ -404,7 +402,7 @@ class ZincWorkerImpl(
reporter: Option[CompileProblemReporter],
reportCachedProblems: Boolean,
incrementalCompilation: Boolean
- )(implicit ctx: ZincWorkerApi.Ctx): Result[CompilationResult] = {
+ )(implicit ctx: JvmWorkerApi.Ctx): Result[CompilationResult] = {
javaOnlyCompilerCache.withValue(javacOptions.filter(filterJavacRuntimeOptions)) { compilers =>
compileInternal(
upstreamCompileOutput = upstreamCompileOutput,
@@ -435,7 +433,7 @@ class ZincWorkerImpl(
reportCachedProblems: Boolean,
incrementalCompilation: Boolean,
auxiliaryClassFileExtensions: Seq[String]
- )(implicit ctx: ZincWorkerApi.Ctx): Result[CompilationResult] = {
+ )(implicit ctx: JvmWorkerApi.Ctx): Result[CompilationResult] = {
withCompilers(
scalaVersion = scalaVersion,
scalaOrganization = scalaOrganization,
@@ -502,7 +500,7 @@ class ZincWorkerImpl(
incrementalCompilation: Boolean,
auxiliaryClassFileExtensions: Seq[String],
zincCache: os.SubPath = os.sub / "zinc"
- )(implicit ctx: ZincWorkerApi.Ctx): Result[CompilationResult] = {
+ )(implicit ctx: JvmWorkerApi.Ctx): Result[CompilationResult] = {
os.makeDir.all(ctx.dest)
val classesDir =
diff --git a/scalanativelib/src/mill/scalanativelib/ScalaNativeModule.scala b/scalanativelib/src/mill/scalanativelib/ScalaNativeModule.scala
index 483a32572ad..a1db430f857 100644
--- a/scalanativelib/src/mill/scalanativelib/ScalaNativeModule.scala
+++ b/scalanativelib/src/mill/scalanativelib/ScalaNativeModule.scala
@@ -5,7 +5,7 @@ import mainargs.Flag
import mill.api.Loose.Agg
import mill.api.{Result, internal}
import mill.define.{Command, Task}
-import mill.scalalib.api.ZincWorkerUtil
+import mill.scalalib.api.JvmWorkerUtil
import mill.scalalib.bsp.{ScalaBuildTarget, ScalaPlatform}
import mill.scalalib.{CrossVersion, Dep, DepSyntax, Lib, SbtModule, ScalaModule, TestModule}
import mill.testrunner.{TestResult, TestRunner, TestRunnerUtils}
@@ -33,10 +33,10 @@ trait ScalaNativeModule extends ScalaModule { outer =>
}
def scalaNativeBinaryVersion =
- Task { ZincWorkerUtil.scalaNativeBinaryVersion(scalaNativeVersion()) }
+ Task { JvmWorkerUtil.scalaNativeBinaryVersion(scalaNativeVersion()) }
def scalaNativeWorkerVersion =
- Task { ZincWorkerUtil.scalaNativeWorkerVersion(scalaNativeVersion()) }
+ Task { JvmWorkerUtil.scalaNativeWorkerVersion(scalaNativeVersion()) }
def scalaNativeWorkerClasspath = Task {
defaultResolver().classpath(Seq(
@@ -65,7 +65,7 @@ trait ScalaNativeModule extends ScalaModule { outer =>
if (scalaNativeVersion().startsWith("0.4")) scalaNativeVersion()
else s"${scalaVersion()}+${scalaNativeVersion()}"
- if (ZincWorkerUtil.isScala3(scalaVersion()))
+ if (JvmWorkerUtil.isScala3(scalaVersion()))
Agg(ivy"org.scala-native::scala3lib::$version")
else Agg(ivy"org.scala-native::scalalib::$version")
}
@@ -295,7 +295,7 @@ trait ScalaNativeModule extends ScalaModule { outer =>
ScalaBuildTarget(
scalaOrganization = scalaOrganization(),
scalaVersion = scalaVersion(),
- scalaBinaryVersion = ZincWorkerUtil.scalaBinaryVersion(scalaVersion()),
+ scalaBinaryVersion = JvmWorkerUtil.scalaBinaryVersion(scalaVersion()),
ScalaPlatform.Native,
jars = scalaCompilerClasspath().map(_.path.toNIO.toUri.toString).iterator.toSeq,
jvmBuildTarget = None
diff --git a/testkit/src/mill/testkit/UnitTester.scala b/testkit/src/mill/testkit/UnitTester.scala
index d8e9cf791bc..ea4c206612b 100644
--- a/testkit/src/mill/testkit/UnitTester.scala
+++ b/testkit/src/mill/testkit/UnitTester.scala
@@ -55,14 +55,14 @@ class UnitTester(
env: Map[String, String],
resetSourcePath: Boolean
)(implicit fullName: sourcecode.FullName) extends AutoCloseable {
- val outPath: os.Path = module.millSourcePath / "out"
+ val outPath: os.Path = module.moduleDir / "out"
if (resetSourcePath) {
- os.remove.all(module.millSourcePath)
- os.makeDir.all(module.millSourcePath)
+ os.remove.all(module.moduleDir)
+ os.makeDir.all(module.moduleDir)
for (sourceFileRoot <- Option(sourceRoot)) {
- os.copy.over(sourceFileRoot, module.millSourcePath, createFolders = true)
+ os.copy.over(sourceFileRoot, module.moduleDir, createFolders = true)
}
}
@@ -88,7 +88,7 @@ class UnitTester(
}
val evaluator: EvaluatorImpl = mill.eval.EvaluatorImpl.make(
mill.api.Ctx.defaultHome,
- module.millSourcePath,
+ module.moduleDir,
outPath,
outPath,
module,
@@ -135,7 +135,7 @@ class UnitTester(
evaluated.rawValues.map(_.asInstanceOf[Result.Success[Val]].value.value),
evaluated.evaluated.collect {
case t: TargetImpl[_]
- if module.millInternal.targets.contains(t)
+ if module.moduleInternal.targets.contains(t)
&& !t.ctx.external => t
case t: mill.define.Command[_] => t
}.size
@@ -176,7 +176,7 @@ class UnitTester(
val evaluated = evaluator.evaluate(targets)
.evaluated
.flatMap(_.asTarget)
- .filter(module.millInternal.targets.contains)
+ .filter(module.moduleInternal.targets.contains)
.filter(!_.isInstanceOf[InputImpl[_]])
assert(
evaluated.toSet == expected.toSet,