diff --git a/.config/mill-version b/.config/mill-version
index b30ea878bb5..825bbb0a69e 100644
--- a/.config/mill-version
+++ b/.config/mill-version
@@ -1 +1 @@
-0.12.0-RC3-32-b4a0bf
\ No newline at end of file
+0.12.0-RC3-46-80d164
diff --git a/build.mill b/build.mill
index f2691ab2b41..60b3afa4be7 100644
--- a/build.mill
+++ b/build.mill
@@ -120,7 +120,7 @@ object Deps {
val asmTree = ivy"org.ow2.asm:asm-tree:9.7"
val bloopConfig = ivy"ch.epfl.scala::bloop-config:1.5.5"
- val coursier = ivy"io.get-coursier::coursier:2.1.13"
+ val coursier = ivy"io.get-coursier::coursier:2.1.14"
val coursierInterface = ivy"io.get-coursier:interface:1.0.19"
val cask = ivy"com.lihaoyi::cask:0.9.4"
@@ -151,8 +151,8 @@ object Deps {
val log4j2Core = ivy"org.apache.logging.log4j:log4j-core:2.23.1"
val osLib = ivy"com.lihaoyi::os-lib:0.11.1"
val pprint = ivy"com.lihaoyi::pprint:0.9.0"
- val mainargs = ivy"com.lihaoyi::mainargs:0.7.4"
- val millModuledefsVersion = "0.11.0"
+ val mainargs = ivy"com.lihaoyi::mainargs:0.7.6"
+ val millModuledefsVersion = "0.11.1"
val millModuledefsString = s"com.lihaoyi::mill-moduledefs:${millModuledefsVersion}"
val millModuledefs = ivy"${millModuledefsString}"
val millModuledefsPlugin =
diff --git a/contrib/proguard/src/mill/contrib/proguard/Proguard.scala b/contrib/proguard/src/mill/contrib/proguard/Proguard.scala
index 7c577ff2549..662ab690b09 100644
--- a/contrib/proguard/src/mill/contrib/proguard/Proguard.scala
+++ b/contrib/proguard/src/mill/contrib/proguard/Proguard.scala
@@ -65,18 +65,8 @@ trait Proguard extends ScalaModule {
* Keep in sync with [[javaHome]].
*/
def java9RtJar: T[Seq[PathRef]] = Task {
- if (mill.main.client.Util.isJava9OrAbove) {
- val rt = T.dest / Export.rtJarName
- if (!os.exists(rt)) {
- T.log.outputStream.println(
- s"Preparing Java runtime JAR; this may take a minute or two ..."
- )
- Export.rtTo(rt.toIO, false)
- }
- Seq(PathRef(rt))
- } else {
- Seq()
- }
+ if (mill.main.client.Util.isJava9OrAbove) Seq(PathRef(T.home / Export.rtJarName))
+ else Seq()
}
/**
diff --git a/example/kotlinlib/web/3-hello-kotlinjs/build.mill b/example/kotlinlib/web/3-hello-kotlinjs/build.mill
index 950fec1eb2a..cd69d50d31c 100644
--- a/example/kotlinlib/web/3-hello-kotlinjs/build.mill
+++ b/example/kotlinlib/web/3-hello-kotlinjs/build.mill
@@ -1,9 +1,9 @@
// KotlinJS support on Mill is still Work In Progress (WIP). As of time of writing it
-// does not support third-party dependencies, Kotlin 2.x with KMP KLIB files, Node.js/Webpack
-// test runners and reporting, etc.
+// Node.js/Webpack test runners and reporting, etc.
//
-// The example below demonstrates only the minimal compilation, running, and testing of a single KotlinJS
-// module. For more details in fully developing KotlinJS support, see the following ticket:
+// The example below demonstrates only the minimal compilation, running, and testing of
+// a single KotlinJS module using a single third-party dependency. For more details in
+// fully developing KotlinJS support, see the following ticket:
//
// * https://github.com/com-lihaoyi/mill/issues/3611
@@ -14,6 +14,9 @@ object foo extends KotlinJSModule {
def moduleKind = ModuleKind.ESModule
def kotlinVersion = "1.9.25"
def kotlinJSRunTarget = Some(RunTarget.Node)
+ def ivyDeps = Agg(
+ ivy"org.jetbrains.kotlinx:kotlinx-html-js:0.11.0",
+ )
object test extends KotlinJSModule with KotlinJSKotlinXTests
}
@@ -22,7 +25,7 @@ object foo extends KotlinJSModule {
> mill foo.run
Compiling 1 Kotlin sources to .../out/foo/compile.dest/classes...
-Hello, world
+
Hello World
stringifiedJsObject: ["hello","world","!"]
> mill foo.test # Test is incorrect, `foo.test`` fails
@@ -30,13 +33,13 @@ Compiling 1 Kotlin sources to .../out/foo/test/compile.dest/classes...
Linking IR to .../out/foo/test/linkBinary.dest/binaries
produce executable: .../out/foo/test/linkBinary.dest/binaries
...
-error: AssertionError: Expected , actual .
+error: AssertionError: Expected <Hello World
>, actual <Hello World Wrong
>.
> cat out/foo/test/linkBinary.dest/binaries/test.js # Generated javascript on disk
-...assertEquals_0(getString(), 'Not hello, world');...
+...assertEquals_0(..., 'Hello World Wrong<\/h1>');...
...
-> sed -i.bak 's/Not hello, world/Hello, world/g' foo/test/src/foo/HelloTests.kt
+> sed -i.bak 's/Hello World Wrong/Hello World/g' foo/test/src/foo/HelloTests.kt
> mill foo.test # passes after fixing test
diff --git a/example/kotlinlib/web/3-hello-kotlinjs/foo/src/foo/Hello.kt b/example/kotlinlib/web/3-hello-kotlinjs/foo/src/foo/Hello.kt
index 09f3ccd16af..b3348c98139 100644
--- a/example/kotlinlib/web/3-hello-kotlinjs/foo/src/foo/Hello.kt
+++ b/example/kotlinlib/web/3-hello-kotlinjs/foo/src/foo/Hello.kt
@@ -1,11 +1,16 @@
package foo
-fun getString() = "Hello, world"
+import kotlinx.html.*
+import kotlinx.html.stream.createHTML
fun main() {
- println(getString())
+ println(hello())
val parsedJsonStr: dynamic = JSON.parse("""{"helloworld": ["hello", "world", "!"]}""")
val stringifiedJsObject = JSON.stringify(parsedJsonStr.helloworld)
println("stringifiedJsObject: " + stringifiedJsObject)
}
+
+fun hello(): String {
+ return createHTML().h1 { +"Hello World" }.toString()
+}
\ No newline at end of file
diff --git a/example/kotlinlib/web/3-hello-kotlinjs/foo/test/src/foo/HelloTests.kt b/example/kotlinlib/web/3-hello-kotlinjs/foo/test/src/foo/HelloTests.kt
index 7526f739947..fc33731c87a 100644
--- a/example/kotlinlib/web/3-hello-kotlinjs/foo/test/src/foo/HelloTests.kt
+++ b/example/kotlinlib/web/3-hello-kotlinjs/foo/test/src/foo/HelloTests.kt
@@ -6,8 +6,10 @@ import kotlin.test.assertEquals
class HelloTests {
@Test
- fun failure() {
- assertEquals(getString(), "Not hello, world")
+ fun testHello() {
+ val result = hello()
+ assertEquals(result.trim(), "Hello World Wrong
")
+ result
}
}
diff --git a/integration/feature/docannotations/src/DocAnnotationsTests.scala b/integration/feature/docannotations/src/DocAnnotationsTests.scala
index 77bdb767d1e..4ef2900e86c 100644
--- a/integration/feature/docannotations/src/DocAnnotationsTests.scala
+++ b/integration/feature/docannotations/src/DocAnnotationsTests.scala
@@ -93,7 +93,7 @@ object DocAnnotationsTests extends UtestIntegrationTestSuite {
assert(
globMatches(
- """core.ivyDepsTree(JavaModule.scala:884)
+ """core.ivyDepsTree(JavaModule.scala:896)
| Command to print the transitive dependency tree to STDOUT.
|
| --inverse Invert the tree representation, so that the root is on the bottom val
diff --git a/integration/feature/output-directory/src/OutputDirectoryLockTests.scala b/integration/feature/output-directory/src/OutputDirectoryLockTests.scala
index bcfce43b82f..13799e273aa 100644
--- a/integration/feature/output-directory/src/OutputDirectoryLockTests.scala
+++ b/integration/feature/output-directory/src/OutputDirectoryLockTests.scala
@@ -16,7 +16,7 @@ object OutputDirectoryLockTests extends UtestIntegrationTestSuite {
override def utestAfterAll(): Unit = {
pool.shutdown()
}
- implicit val retryMax: RetryMax = RetryMax(30000.millis)
+ implicit val retryMax: RetryMax = RetryMax(60000.millis)
implicit val retryInterval: RetryInterval = RetryInterval(50.millis)
def tests: Tests = Tests {
test("basic") - integrationTest { tester =>
diff --git a/kotlinlib/src/mill/kotlinlib/js/KotlinJSModule.scala b/kotlinlib/src/mill/kotlinlib/js/KotlinJSModule.scala
index 3540c099642..07f284a4a37 100644
--- a/kotlinlib/src/mill/kotlinlib/js/KotlinJSModule.scala
+++ b/kotlinlib/src/mill/kotlinlib/js/KotlinJSModule.scala
@@ -453,45 +453,45 @@ trait KotlinJSModule extends KotlinModule { outer =>
sealed trait ModuleKind { def extension: String }
object ModuleKind {
- object NoModule extends ModuleKind { val extension = "js" }
+ case object NoModule extends ModuleKind { val extension = "js" }
implicit val rwNoModule: RW[NoModule.type] = macroRW
- object UMDModule extends ModuleKind { val extension = "js" }
+ case object UMDModule extends ModuleKind { val extension = "js" }
implicit val rwUMDModule: RW[UMDModule.type] = macroRW
- object CommonJSModule extends ModuleKind { val extension = "js" }
+ case object CommonJSModule extends ModuleKind { val extension = "js" }
implicit val rwCommonJSModule: RW[CommonJSModule.type] = macroRW
- object AMDModule extends ModuleKind { val extension = "js" }
+ case object AMDModule extends ModuleKind { val extension = "js" }
implicit val rwAMDModule: RW[AMDModule.type] = macroRW
- object ESModule extends ModuleKind { val extension = "mjs" }
+ case object ESModule extends ModuleKind { val extension = "mjs" }
implicit val rwESModule: RW[ESModule.type] = macroRW
- object PlainModule extends ModuleKind { val extension = "js" }
+ case object PlainModule extends ModuleKind { val extension = "js" }
implicit val rwPlainModule: RW[PlainModule.type] = macroRW
}
sealed trait SourceMapEmbedSourcesKind
object SourceMapEmbedSourcesKind {
- object Always extends SourceMapEmbedSourcesKind
+ case object Always extends SourceMapEmbedSourcesKind
implicit val rwAlways: RW[Always.type] = macroRW
- object Never extends SourceMapEmbedSourcesKind
+ case object Never extends SourceMapEmbedSourcesKind
implicit val rwNever: RW[Never.type] = macroRW
- object Inlining extends SourceMapEmbedSourcesKind
+ case object Inlining extends SourceMapEmbedSourcesKind
implicit val rwInlining: RW[Inlining.type] = macroRW
}
sealed trait SourceMapNamesPolicy
object SourceMapNamesPolicy {
- object SimpleNames extends SourceMapNamesPolicy
+ case object SimpleNames extends SourceMapNamesPolicy
implicit val rwSimpleNames: RW[SimpleNames.type] = macroRW
- object FullyQualifiedNames extends SourceMapNamesPolicy
+ case object FullyQualifiedNames extends SourceMapNamesPolicy
implicit val rwFullyQualifiedNames: RW[FullyQualifiedNames.type] = macroRW
- object No extends SourceMapNamesPolicy
+ case object No extends SourceMapNamesPolicy
implicit val rwNo: RW[No.type] = macroRW
}
sealed trait BinaryKind
object BinaryKind {
- object Library extends BinaryKind
+ case object Library extends BinaryKind
implicit val rwLibrary: RW[Library.type] = macroRW
- object Executable extends BinaryKind
+ case object Executable extends BinaryKind
implicit val rwExecutable: RW[Executable.type] = macroRW
implicit val rw: RW[BinaryKind] = macroRW
}
@@ -499,14 +499,14 @@ object BinaryKind {
sealed trait RunTarget
object RunTarget {
// TODO rely on the node version installed in the env or fetch a specific one?
- object Node extends RunTarget
+ case object Node extends RunTarget
implicit val rwNode: RW[Node.type] = macroRW
implicit val rw: RW[RunTarget] = macroRW
}
private[kotlinlib] sealed trait OutputMode
private[kotlinlib] object OutputMode {
- object Js extends OutputMode
- object KlibDir extends OutputMode
- object KlibFile extends OutputMode
+ case object Js extends OutputMode
+ case object KlibDir extends OutputMode
+ case object KlibFile extends OutputMode
}
diff --git a/main/api/src/mill/api/Logger.scala b/main/api/src/mill/api/Logger.scala
index 430b6352f99..b9a5dd64ddf 100644
--- a/main/api/src/mill/api/Logger.scala
+++ b/main/api/src/mill/api/Logger.scala
@@ -58,7 +58,7 @@ trait Logger extends AutoCloseable {
): Unit =
ticker(s"${key.mkString("-")} $message")
private[mill] def setPromptLine(): Unit = ()
- private[mill] def setPromptLeftHeader(s: String): Unit = ()
+ private[mill] def setPromptHeaderPrefix(s: String): Unit = ()
private[mill] def clearPromptStatuses(): Unit = ()
private[mill] def removePromptLine(key: Seq[String]): Unit = ()
private[mill] def removePromptLine(): Unit = ()
diff --git a/main/define/src/mill/define/Discover.scala b/main/define/src/mill/define/Discover.scala
index c3136c2a0a0..03316412f80 100644
--- a/main/define/src/mill/define/Discover.scala
+++ b/main/define/src/mill/define/Discover.scala
@@ -141,11 +141,20 @@ object Discover {
}
if overridesRoutes._1.nonEmpty || overridesRoutes._2.nonEmpty || overridesRoutes._3.nonEmpty
} yield {
+ val lhs0 = discoveredModuleType match {
+ // Explicitly do not de-alias type refs, so type aliases to deprecated
+ // types do not result in spurious deprecation warnings appearing
+ case tr: TypeRef => tr
+ // Other types are fine
+ case _ => discoveredModuleType.typeSymbol.asClass.toType
+ }
+
+ val lhs = q"classOf[$lhs0]"
+
// by wrapping the `overridesRoutes` in a lambda function we kind of work around
// the problem of generating a *huge* macro method body that finally exceeds the
// JVM's maximum allowed method size
val overridesLambda = q"(() => $overridesRoutes)()"
- val lhs = q"classOf[${discoveredModuleType.typeSymbol.asClass}]"
q"$lhs -> $overridesLambda"
}
diff --git a/main/eval/src/mill/eval/EvaluatorCore.scala b/main/eval/src/mill/eval/EvaluatorCore.scala
index bfc25731686..932d73092bd 100644
--- a/main/eval/src/mill/eval/EvaluatorCore.scala
+++ b/main/eval/src/mill/eval/EvaluatorCore.scala
@@ -111,7 +111,7 @@ private[mill] trait EvaluatorCore extends GroupEvaluator {
)
val verboseKeySuffix = s"/${terminals0.size}"
- logger.setPromptLeftHeader(s"$countMsg$verboseKeySuffix")
+ logger.setPromptHeaderPrefix(s"$countMsg$verboseKeySuffix")
if (failed.get()) None
else {
val upstreamResults = upstreamValues
diff --git a/main/util/src/mill/util/CoursierSupport.scala b/main/util/src/mill/util/CoursierSupport.scala
index c6629b520d2..d8198773306 100644
--- a/main/util/src/mill/util/CoursierSupport.scala
+++ b/main/util/src/mill/util/CoursierSupport.scala
@@ -6,7 +6,7 @@ import coursier.error.ResolutionError.CantDownloadModule
import coursier.params.ResolutionParams
import coursier.parse.RepositoryParser
import coursier.util.{Artifact, Task}
-import coursier.{Artifacts, Classifier, Dependency, Repository, Resolution, Resolve}
+import coursier.{Artifacts, Classifier, Dependency, Repository, Resolution, Resolve, Type}
import mill.api.Loose.Agg
import mill.api.{Ctx, PathRef, Result}
@@ -46,7 +46,8 @@ trait CoursierSupport {
customizer: Option[Resolution => Resolution] = None,
ctx: Option[mill.api.Ctx.Log] = None,
coursierCacheCustomizer: Option[FileCache[Task] => FileCache[Task]] = None,
- resolveFilter: os.Path => Boolean = _ => true
+ resolveFilter: os.Path => Boolean = _ => true,
+ artifactTypes: Option[Set[Type]] = None
): Result[Agg[PathRef]] = {
def isLocalTestDep(dep: Dependency): Option[Seq[PathRef]] = {
val org = dep.module.organization.value
@@ -88,6 +89,7 @@ trait CoursierSupport {
if (sources) Set(Classifier("sources"))
else Set.empty
)
+ .withArtifactTypesOpt(artifactTypes)
.eitherResult()
artifactsResultOrError match {
@@ -106,7 +108,7 @@ trait CoursierSupport {
Agg.from(
res.files
.map(os.Path(_))
- .filter(path => path.ext == "jar" && resolveFilter(path))
+ .filter(resolveFilter)
.map(PathRef(_, quick = true))
) ++ localTestDeps.flatten
)
@@ -114,6 +116,30 @@ trait CoursierSupport {
}
}
+ @deprecated("Use the override accepting artifactTypes", "Mill after 0.12.0-RC3")
+ def resolveDependencies(
+ repositories: Seq[Repository],
+ deps: IterableOnce[Dependency],
+ force: IterableOnce[Dependency],
+ sources: Boolean,
+ mapDependencies: Option[Dependency => Dependency],
+ customizer: Option[Resolution => Resolution],
+ ctx: Option[mill.api.Ctx.Log],
+ coursierCacheCustomizer: Option[FileCache[Task] => FileCache[Task]],
+ resolveFilter: os.Path => Boolean
+ ): Result[Agg[PathRef]] =
+ resolveDependencies(
+ repositories,
+ deps,
+ force,
+ sources,
+ mapDependencies,
+ customizer,
+ ctx,
+ coursierCacheCustomizer,
+ resolveFilter
+ )
+
@deprecated(
"Prefer resolveDependenciesMetadataSafe instead, which returns a Result instead of throwing exceptions",
"0.12.0"
diff --git a/main/util/src/mill/util/MultiLogger.scala b/main/util/src/mill/util/MultiLogger.scala
index 9dda1ea4e4b..357763657b3 100644
--- a/main/util/src/mill/util/MultiLogger.scala
+++ b/main/util/src/mill/util/MultiLogger.scala
@@ -81,9 +81,9 @@ class MultiLogger(
logger1.removePromptLine()
logger2.removePromptLine()
}
- private[mill] override def setPromptLeftHeader(s: String): Unit = {
- logger1.setPromptLeftHeader(s)
- logger2.setPromptLeftHeader(s)
+ private[mill] override def setPromptHeaderPrefix(s: String): Unit = {
+ logger1.setPromptHeaderPrefix(s)
+ logger2.setPromptHeaderPrefix(s)
}
private[mill] override def withPromptPaused[T](t: => T): T = {
diff --git a/main/util/src/mill/util/PrefixLogger.scala b/main/util/src/mill/util/PrefixLogger.scala
index 68cd02e7321..7ee393516a9 100644
--- a/main/util/src/mill/util/PrefixLogger.scala
+++ b/main/util/src/mill/util/PrefixLogger.scala
@@ -107,7 +107,8 @@ class PrefixLogger(
private[mill] override def removePromptLine(callKey: Seq[String]): Unit =
logger0.removePromptLine(callKey)
private[mill] override def removePromptLine(): Unit = removePromptLine(logPrefixKey)
- private[mill] override def setPromptLeftHeader(s: String): Unit = logger0.setPromptLeftHeader(s)
+ private[mill] override def setPromptHeaderPrefix(s: String): Unit =
+ logger0.setPromptHeaderPrefix(s)
override def enableTicker = logger0.enableTicker
private[mill] override def subLogger(
diff --git a/main/util/src/mill/util/PromptLogger.scala b/main/util/src/mill/util/PromptLogger.scala
index 8846355f433..1a08d6cefcc 100644
--- a/main/util/src/mill/util/PromptLogger.scala
+++ b/main/util/src/mill/util/PromptLogger.scala
@@ -88,16 +88,16 @@ private[mill] class PromptLogger(
def error(s: String): Unit = synchronized { systemStreams.err.println(s) }
- override def setPromptLeftHeader(s: String): Unit =
- synchronized { promptLineState.updateGlobal(s) }
+ override def setPromptHeaderPrefix(s: String): Unit =
+ synchronized { promptLineState.setHeaderPrefix(s) }
override def clearPromptStatuses(): Unit = synchronized { promptLineState.clearStatuses() }
override def removePromptLine(key: Seq[String]): Unit = synchronized {
- promptLineState.updateCurrent(key, None)
+ promptLineState.setCurrent(key, None)
}
def ticker(s: String): Unit = ()
override def setPromptDetail(key: Seq[String], s: String): Unit = synchronized {
- promptLineState.updateDetail(key, s)
+ promptLineState.setDetail(key, s)
}
override def reportKey(key: Seq[String]): Unit = synchronized {
@@ -116,7 +116,7 @@ private[mill] class PromptLogger(
private val reportedIdentifiers = collection.mutable.Set.empty[Seq[String]]
override def setPromptLine(key: Seq[String], verboseKeySuffix: String, message: String): Unit =
synchronized {
- promptLineState.updateCurrent(key, Some(s"[${key.mkString("-")}] $message"))
+ promptLineState.setCurrent(key, Some(s"[${key.mkString("-")}] $message"))
seenIdentifiers(key) = (verboseKeySuffix, message)
}
@@ -273,19 +273,8 @@ private[mill] object PromptLogger {
) {
private var lastRenderedPromptHash = 0
- private implicit def seqOrdering = new Ordering[Seq[String]] {
- def compare(xs: Seq[String], ys: Seq[String]): Int = {
- val iter = xs.iterator.zip(ys)
- while (iter.nonEmpty) {
- val (x, y) = iter.next()
- if (x > y) return 1
- else if (y > x) return -1
- }
-
- return xs.lengthCompare(ys)
- }
- }
- private val statuses = collection.mutable.SortedMap.empty[Seq[String], Status]
+ private val statuses = collection.mutable.SortedMap
+ .empty[Seq[String], Status](PromptLoggerUtil.seqStringOrdering)
private var headerPrefix = ""
// Pre-compute the prelude and current prompt as byte arrays so that
@@ -308,6 +297,7 @@ private[mill] object PromptLogger {
if (ending) statuses.clear()
val (termWidth0, termHeight0) = consoleDims()
+ val interactive = consoleDims()._1.nonEmpty
// don't show prompt for non-interactive terminal
val currentPromptLines = renderPrompt(
termWidth0.getOrElse(defaultTermWidth),
@@ -317,38 +307,23 @@ private[mill] object PromptLogger {
s"[$headerPrefix]",
titleText,
statuses.toSeq.map { case (k, v) => (k.mkString("-"), v) },
- interactive = consoleDims()._1.nonEmpty,
+ interactive = interactive,
infoColor = infoColor,
ending = ending
)
- val currentPromptStr =
- if (termWidth0.isEmpty) currentPromptLines.mkString("\n") + "\n"
- else {
- // For the ending prompt, leave the cursor at the bottom on a new line rather than
- // scrolling back left/up. We do not want further output to overwrite the header as
- // it will no longer re-render
- val backUp =
- if (ending) "\n"
- else AnsiNav.left(9999) + AnsiNav.up(currentPromptLines.length - 1)
-
- AnsiNav.clearScreen(0) +
- currentPromptLines.mkString("\n") +
- backUp
- }
-
- currentPromptBytes = currentPromptStr.getBytes
+ currentPromptBytes = renderPromptWrapped(currentPromptLines, interactive, ending).getBytes
}
def clearStatuses(): Unit = synchronized { statuses.clear() }
- def updateGlobal(s: String): Unit = synchronized { headerPrefix = s }
+ def setHeaderPrefix(s: String): Unit = synchronized { headerPrefix = s }
- def updateDetail(key: Seq[String], detail: String): Unit = synchronized {
+ def setDetail(key: Seq[String], detail: String): Unit = synchronized {
statuses.updateWith(key)(_.map(se => se.copy(next = se.next.map(_.copy(detail = detail)))))
}
- def updateCurrent(key: Seq[String], sOpt: Option[String]): Unit = synchronized {
+ def setCurrent(key: Seq[String], sOpt: Option[String]): Unit = synchronized {
val now = currentTimeMillis()
def stillTransitioning(status: Status) = {
@@ -357,7 +332,7 @@ private[mill] object PromptLogger {
val sOptEntry = sOpt.map(StatusEntry(_, now, ""))
statuses.updateWith(key) {
case None =>
- statuses.find { case (k, v) => v.next.isEmpty && stillTransitioning(v) } match {
+ statuses.find { case (k, v) => v.next.isEmpty } match {
case Some((reusableKey, reusableValue)) =>
statuses.remove(reusableKey)
Some(reusableValue.copy(next = sOptEntry))
@@ -369,13 +344,7 @@ private[mill] object PromptLogger {
// If still performing a transition, do not update the `prevTransitionTime`
// since we do not want to delay the transition that is already in progress
if (stillTransitioning(existing)) existing.copy(next = sOptEntry)
- else {
- existing.copy(
- next = sOptEntry,
- beginTransitionTime = now,
- prev = existing.next
- )
- }
+ else existing.copy(next = sOptEntry, beginTransitionTime = now, prev = existing.next)
)
}
}
diff --git a/main/util/src/mill/util/PromptLoggerUtil.scala b/main/util/src/mill/util/PromptLoggerUtil.scala
index fd261ed1b70..128e93e158b 100644
--- a/main/util/src/mill/util/PromptLoggerUtil.scala
+++ b/main/util/src/mill/util/PromptLoggerUtil.scala
@@ -149,6 +149,28 @@ private object PromptLoggerUtil {
header :: body ::: footer
}
+ // Wrap the prompt in the necessary clear-screens/newlines/move-cursors
+ // according to whether it is interactive or ending
+ def renderPromptWrapped(
+ currentPromptLines: Seq[String],
+ interactive: Boolean,
+ ending: Boolean
+ ): String = {
+ if (!interactive) currentPromptLines.mkString("\n") + "\n"
+ else {
+ // For the ending prompt, leave the cursor at the bottom on a new line rather than
+ // scrolling back left/up. We do not want further output to overwrite the header as
+ // it will no longer re-render
+ val backUp =
+ if (ending) "\n"
+ else AnsiNav.left(9999) + AnsiNav.up(currentPromptLines.length - 1)
+
+ AnsiNav.clearScreen(0) +
+ currentPromptLines.mkString("\n") +
+ backUp
+ }
+ }
+
def renderHeader(
headerPrefix0: String,
titleText0: String,
@@ -204,4 +226,17 @@ private object PromptLoggerUtil {
}
???
}
+
+ private[mill] val seqStringOrdering = new Ordering[Seq[String]] {
+ def compare(xs: Seq[String], ys: Seq[String]): Int = {
+ val iter = xs.iterator.zip(ys)
+ while (iter.nonEmpty) {
+ val (x, y) = iter.next()
+ if (x > y) return 1
+ else if (y > x) return -1
+ }
+
+ return xs.lengthCompare(ys)
+ }
+ }
}
diff --git a/main/util/src/mill/util/ProxyLogger.scala b/main/util/src/mill/util/ProxyLogger.scala
index 240fcabd685..bb1f1d1d27d 100644
--- a/main/util/src/mill/util/ProxyLogger.scala
+++ b/main/util/src/mill/util/ProxyLogger.scala
@@ -35,7 +35,8 @@ class ProxyLogger(logger: Logger) extends Logger {
override def rawOutputStream: PrintStream = logger.rawOutputStream
private[mill] override def removePromptLine(key: Seq[String]): Unit = logger.removePromptLine(key)
private[mill] override def removePromptLine(): Unit = logger.removePromptLine()
- private[mill] override def setPromptLeftHeader(s: String): Unit = logger.setPromptLeftHeader(s)
+ private[mill] override def setPromptHeaderPrefix(s: String): Unit =
+ logger.setPromptHeaderPrefix(s)
private[mill] override def withPromptPaused[T](t: => T): T = logger.withPromptPaused(t)
private[mill] override def withPromptUnpaused[T](t: => T): T = logger.withPromptUnpaused(t)
diff --git a/main/util/test/src/mill/util/PromptLoggerTests.scala b/main/util/test/src/mill/util/PromptLoggerTests.scala
index b9f95ef7b4d..044247339f6 100644
--- a/main/util/test/src/mill/util/PromptLoggerTests.scala
+++ b/main/util/test/src/mill/util/PromptLoggerTests.scala
@@ -59,7 +59,7 @@ object PromptLoggerTests extends TestSuite {
val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp())
- promptLogger.setPromptLeftHeader("123/456")
+ promptLogger.setPromptHeaderPrefix("123/456")
promptLogger.setPromptLine(Seq("1"), "/456", "my-task")
now += 10000
@@ -108,7 +108,7 @@ object PromptLoggerTests extends TestSuite {
var now = 0L
val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp("80 40"))
- promptLogger.setPromptLeftHeader("123/456")
+ promptLogger.setPromptHeaderPrefix("123/456")
promptLogger.refreshPrompt()
check(promptLogger, baos)(
" [123/456] ========================== TITLE =================================="
@@ -278,7 +278,7 @@ object PromptLoggerTests extends TestSuite {
@volatile var now = 0L
val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp("80 40"))
- promptLogger.setPromptLeftHeader("123/456")
+ promptLogger.setPromptHeaderPrefix("123/456")
promptLogger.refreshPrompt()
check(promptLogger, baos)(
" [123/456] ========================== TITLE =================================="
@@ -329,7 +329,7 @@ object PromptLoggerTests extends TestSuite {
@volatile var now = 0L
val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp("80 40"))
- promptLogger.setPromptLeftHeader("123/456")
+ promptLogger.setPromptHeaderPrefix("123/456")
promptLogger.refreshPrompt()
promptLogger.setPromptLine(Seq("1"), "/456", "my-task")
diff --git a/runner/src/mill/runner/CodeGen.scala b/runner/src/mill/runner/CodeGen.scala
index fa75337cb4c..58d549d69a5 100644
--- a/runner/src/mill/runner/CodeGen.scala
+++ b/runner/src/mill/runner/CodeGen.scala
@@ -161,11 +161,7 @@ object CodeGen {
newScriptCode = objectData.name.applyTo(newScriptCode, wrapperObjectName)
newScriptCode = objectData.obj.applyTo(newScriptCode, "abstract class")
- val millDiscover =
- if (segments.nonEmpty) ""
- else
- """@_root_.scala.annotation.nowarn
- | override lazy val millDiscover: _root_.mill.define.Discover = _root_.mill.define.Discover[this.type]""".stripMargin
+ val millDiscover = discoverSnippet(segments)
s"""$pkgLine
|$aliasImports
@@ -224,11 +220,7 @@ object CodeGen {
s"extends _root_.mill.main.RootModule.Subfolder "
}
- val millDiscover =
- if (segments.nonEmpty) ""
- else
- """@_root_.scala.annotation.nowarn
- | override lazy val millDiscover: _root_.mill.define.Discover = _root_.mill.define.Discover[this.type]""".stripMargin
+ val millDiscover = discoverSnippet(segments)
// User code needs to be put in a separate class for proper submodule
// object initialization due to https://github.com/scala/scala3/issues/21444
@@ -240,6 +232,14 @@ object CodeGen {
}
+ def discoverSnippet(segments: Seq[String]): String = {
+ if (segments.nonEmpty) ""
+ else
+ """override lazy val millDiscover: _root_.mill.define.Discover = _root_.mill.define.Discover[this.type]
+ |""".stripMargin
+
+ }
+
private case class Snippet(var text: String = null, var start: Int = -1, var end: Int = -1) {
def applyTo(s: String, replacement: String): String =
s.patch(start, replacement.padTo(end - start, ' '), end - start)
diff --git a/scalalib/src/mill/scalalib/Assembly.scala b/scalalib/src/mill/scalalib/Assembly.scala
index 8b389863b2d..13801a86ef9 100644
--- a/scalalib/src/mill/scalalib/Assembly.scala
+++ b/scalalib/src/mill/scalalib/Assembly.scala
@@ -6,9 +6,8 @@ import mill.api.{Ctx, IO, PathRef}
import os.Generator
import java.io.{ByteArrayInputStream, InputStream, SequenceInputStream}
-import java.net.URI
import java.nio.file.attribute.PosixFilePermission
-import java.nio.file.{FileSystems, Files, StandardOpenOption}
+import java.nio.file.StandardOpenOption
import java.util.Collections
import java.util.jar.JarFile
import java.util.regex.Pattern
@@ -222,44 +221,46 @@ object Assembly {
os.remove(rawJar)
// use the `base` (the upstream assembly) as a start
- val baseUri = "jar:" + rawJar.toIO.getCanonicalFile.toURI.toASCIIString
- val hm = base.fold(Map("create" -> "true")) { b =>
- os.copy(b, rawJar)
- Map.empty
- }
+ base.foreach(os.copy.over(_, rawJar))
var addedEntryCount = 0
// Add more files by copying files to a JAR file system
- Using.resource(FileSystems.newFileSystem(URI.create(baseUri), hm.asJava)) { zipFs =>
- val manifestPath = zipFs.getPath(JarFile.MANIFEST_NAME)
- Files.createDirectories(manifestPath.getParent)
- val manifestOut = Files.newOutputStream(
+ Using.resource(os.zip.open(rawJar)) { zipRoot =>
+ val manifestPath = zipRoot / os.SubPath(JarFile.MANIFEST_NAME)
+ os.makeDir.all(manifestPath / os.up)
+ Using.resource(os.write.outputStream(
manifestPath,
- StandardOpenOption.TRUNCATE_EXISTING,
- StandardOpenOption.CREATE
- )
- manifest.build.write(manifestOut)
- manifestOut.close()
+ openOptions = Seq(
+ StandardOpenOption.TRUNCATE_EXISTING,
+ StandardOpenOption.CREATE
+ )
+ )) { manifestOut =>
+ manifest.build.write(manifestOut)
+ }
val (mappings, resourceCleaner) = Assembly.loadShadedClasspath(inputPaths, assemblyRules)
try {
Assembly.groupAssemblyEntries(mappings, assemblyRules).foreach {
case (mapping, entry) =>
- val path = zipFs.getPath(mapping).toAbsolutePath
+ val path = zipRoot / os.SubPath(mapping)
entry match {
case entry: AppendEntry =>
val separated = entry.inputStreams
.flatMap(inputStream =>
Seq(new ByteArrayInputStream(entry.separator.getBytes), inputStream())
)
- val cleaned = if (Files.exists(path)) separated else separated.drop(1)
- val concatenated = new SequenceInputStream(Collections.enumeration(cleaned.asJava))
- addedEntryCount += 1
- writeEntry(path, concatenated, append = true)
+ val cleaned = if (os.exists(path)) separated else separated.drop(1)
+ Using.resource(new SequenceInputStream(Collections.enumeration(cleaned.asJava))) {
+ concatenated =>
+ addedEntryCount += 1
+ writeEntry(path, concatenated, append = true)
+ }
case entry: WriteOnceEntry =>
addedEntryCount += 1
- writeEntry(path, entry.inputStream(), append = false)
+ Using.resource(entry.inputStream()) { stream =>
+ writeEntry(path, stream, append = false)
+ }
}
}
} finally {
@@ -297,15 +298,14 @@ object Assembly {
Assembly(PathRef(destJar), addedEntryCount)
}
- private def writeEntry(p: java.nio.file.Path, inputStream: InputStream, append: Boolean): Unit = {
- if (p.getParent != null) Files.createDirectories(p.getParent)
+ private def writeEntry(p: os.Path, inputStream: InputStream, append: Boolean): Unit = {
+ if (p.segmentCount != 0) os.makeDir.all(p / os.up)
val options =
if (append) Seq(StandardOpenOption.APPEND, StandardOpenOption.CREATE)
else Seq(StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)
- val outputStream = java.nio.file.Files.newOutputStream(p, options: _*)
- IO.stream(inputStream, outputStream)
- outputStream.close()
- inputStream.close()
+ Using.resource(os.write.outputStream(p, openOptions = options)) { outputStream =>
+ IO.stream(inputStream, outputStream)
+ }
}
}
diff --git a/scalalib/src/mill/scalalib/CoursierModule.scala b/scalalib/src/mill/scalalib/CoursierModule.scala
index a68d4dc7f24..199c82da16d 100644
--- a/scalalib/src/mill/scalalib/CoursierModule.scala
+++ b/scalalib/src/mill/scalalib/CoursierModule.scala
@@ -1,7 +1,7 @@
package mill.scalalib
import coursier.cache.FileCache
-import coursier.{Dependency, Repository, Resolve}
+import coursier.{Dependency, Repository, Resolve, Type}
import coursier.core.Resolution
import mill.define.Task
import mill.api.PathRef
@@ -58,14 +58,20 @@ trait CoursierModule extends mill.Module {
*
* @param deps The dependencies to resolve.
* @param sources If `true`, resolve source dependencies instead of binary dependencies (JARs).
+ * @param artifactTypes If non-empty, pull the passed artifact types rather than the default ones from coursier
* @return The [[PathRef]]s to the resolved files.
*/
- def resolveDeps(deps: Task[Agg[BoundDep]], sources: Boolean = false): Task[Agg[PathRef]] =
+ def resolveDeps(
+ deps: Task[Agg[BoundDep]],
+ sources: Boolean = false,
+ artifactTypes: Option[Set[Type]] = None
+ ): Task[Agg[PathRef]] =
Task.Anon {
Lib.resolveDependencies(
repositories = repositoriesTask(),
deps = deps(),
sources = sources,
+ artifactTypes = artifactTypes,
mapDependencies = Some(mapDependencies()),
customizer = resolutionCustomizer(),
coursierCacheCustomizer = coursierCacheCustomizer(),
@@ -73,6 +79,13 @@ trait CoursierModule extends mill.Module {
)
}
+ @deprecated("Use the override accepting artifactTypes", "Mill after 0.12.0-RC3")
+ def resolveDeps(
+ deps: Task[Agg[BoundDep]],
+ sources: Boolean
+ ): Task[Agg[PathRef]] =
+ resolveDeps(deps, sources, None)
+
/**
* Map dependencies before resolving them.
* Override this to customize the set of dependencies.
@@ -144,18 +157,27 @@ object CoursierModule {
def resolveDeps[T: CoursierModule.Resolvable](
deps: IterableOnce[T],
- sources: Boolean = false
+ sources: Boolean = false,
+ artifactTypes: Option[Set[coursier.Type]] = None
): Agg[PathRef] = {
Lib.resolveDependencies(
repositories = repositories,
deps = deps.map(implicitly[CoursierModule.Resolvable[T]].bind(_, bind)),
sources = sources,
+ artifactTypes = artifactTypes,
mapDependencies = mapDependencies,
customizer = customizer,
coursierCacheCustomizer = coursierCacheCustomizer,
ctx = ctx
).getOrThrow
}
+
+ @deprecated("Use the override accepting artifactTypes", "Mill after 0.12.0-RC3")
+ def resolveDeps[T: CoursierModule.Resolvable](
+ deps: IterableOnce[T],
+ sources: Boolean
+ ): Agg[PathRef] =
+ resolveDeps(deps, sources, None)
}
sealed trait Resolvable[T] {
diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala
index a5e215231ac..d37fe86aede 100644
--- a/scalalib/src/mill/scalalib/JavaModule.scala
+++ b/scalalib/src/mill/scalalib/JavaModule.scala
@@ -1,11 +1,11 @@
package mill
package scalalib
-import coursier.Repository
import coursier.core.Resolution
import coursier.parse.JavaOrScalaModule
import coursier.parse.ModuleParser
import coursier.util.ModuleMatcher
+import coursier.{Repository, Type}
import mainargs.{Flag, arg}
import mill.Agg
import mill.api.{Ctx, JarManifest, MillException, PathRef, Result, internal}
@@ -155,6 +155,12 @@ trait JavaModule
*/
def runIvyDeps: T[Agg[Dep]] = Task { Agg.empty[Dep] }
+ /**
+ * Default artifact types to fetch and put in the classpath. Add extra types
+ * here if you'd like fancy artifact extensions to be fetched.
+ */
+ def artifactTypes: T[Set[Type]] = Task { coursier.core.Resolution.defaultTypes }
+
/**
* Options to pass to the java compiler
*/
@@ -539,7 +545,10 @@ trait JavaModule
* Resolved dependencies based on [[transitiveIvyDeps]] and [[transitiveCompileIvyDeps]].
*/
def resolvedIvyDeps: T[Agg[PathRef]] = Task {
- defaultResolver().resolveDeps(transitiveCompileIvyDeps() ++ transitiveIvyDeps())
+ defaultResolver().resolveDeps(
+ transitiveCompileIvyDeps() ++ transitiveIvyDeps(),
+ artifactTypes = Some(artifactTypes())
+ )
}
/**
@@ -551,7 +560,10 @@ trait JavaModule
}
def resolvedRunIvyDeps: T[Agg[PathRef]] = Task {
- defaultResolver().resolveDeps(runIvyDeps().map(bindDependency()) ++ transitiveIvyDeps())
+ defaultResolver().resolveDeps(
+ runIvyDeps().map(bindDependency()) ++ transitiveIvyDeps(),
+ artifactTypes = Some(artifactTypes())
+ )
}
/**
diff --git a/scalalib/src/mill/scalalib/Lib.scala b/scalalib/src/mill/scalalib/Lib.scala
index c606a519c4a..07a10461524 100644
--- a/scalalib/src/mill/scalalib/Lib.scala
+++ b/scalalib/src/mill/scalalib/Lib.scala
@@ -2,7 +2,7 @@ package mill
package scalalib
import coursier.util.Task
-import coursier.{Dependency, Repository, Resolution}
+import coursier.{Dependency, Repository, Resolution, Type}
import mill.api.{Ctx, Loose, PathRef, Result}
import mill.main.BuildInfo
import mill.main.client.EnvVars
@@ -89,7 +89,8 @@ object Lib {
ctx: Option[Ctx.Log] = None,
coursierCacheCustomizer: Option[
coursier.cache.FileCache[Task] => coursier.cache.FileCache[Task]
- ] = None
+ ] = None,
+ artifactTypes: Option[Set[Type]] = None
): Result[Agg[PathRef]] = {
val depSeq = deps.iterator.toSeq
mill.util.Jvm.resolveDependencies(
@@ -97,6 +98,7 @@ object Lib {
deps = depSeq.map(_.dep),
force = depSeq.filter(_.force).map(_.dep),
sources = sources,
+ artifactTypes = artifactTypes,
mapDependencies = mapDependencies,
customizer = customizer,
ctx = ctx,
@@ -104,6 +106,29 @@ object Lib {
).map(_.map(_.withRevalidateOnce))
}
+ @deprecated("Use the override accepting artifactTypes", "Mill after 0.12.0-RC3")
+ def resolveDependencies(
+ repositories: Seq[Repository],
+ deps: IterableOnce[BoundDep],
+ sources: Boolean,
+ mapDependencies: Option[Dependency => Dependency],
+ customizer: Option[coursier.core.Resolution => coursier.core.Resolution],
+ ctx: Option[Ctx.Log],
+ coursierCacheCustomizer: Option[
+ coursier.cache.FileCache[Task] => coursier.cache.FileCache[Task]
+ ]
+ ): Result[Agg[PathRef]] =
+ resolveDependencies(
+ repositories,
+ deps,
+ sources,
+ mapDependencies,
+ customizer,
+ ctx,
+ coursierCacheCustomizer,
+ None
+ )
+
def scalaCompilerIvyDeps(scalaOrganization: String, scalaVersion: String): Loose.Agg[Dep] =
if (ZincWorkerUtil.isDotty(scalaVersion))
Agg(
diff --git a/scalalib/src/mill/scalalib/ZincWorkerModule.scala b/scalalib/src/mill/scalalib/ZincWorkerModule.scala
index 9b64978d77b..bfefd8016a1 100644
--- a/scalalib/src/mill/scalalib/ZincWorkerModule.scala
+++ b/scalalib/src/mill/scalalib/ZincWorkerModule.scala
@@ -166,8 +166,8 @@ trait ZincWorkerModule extends mill.Module with OfflineSupportModule { self: Cou
val bridgeJar = resolveDependencies(
repositories,
Seq(bridgeDep.bindDep("", "", "")),
- useSources,
- Some(overrideScalaLibrary(scalaVersion, scalaOrganization))
+ sources = useSources,
+ mapDependencies = Some(overrideScalaLibrary(scalaVersion, scalaOrganization))
).map(deps =>
ZincWorkerUtil.grepJar(deps, bridgeName, bridgeVersion, useSources)
)
diff --git a/scalalib/test/resources/pomArtifactType/.placeholder b/scalalib/test/resources/pomArtifactType/.placeholder
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala b/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala
index cd55d1b14b5..a54a2d023f9 100644
--- a/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala
+++ b/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala
@@ -4,6 +4,7 @@ import coursier.maven.MavenRepository
import mill.api.Result.{Failure, Success}
import mill.api.{PathRef, Result}
import mill.api.Loose.Agg
+import mill.testkit.{UnitTester, TestBaseModule}
import utest._
object ResolveDepsTests extends TestSuite {
@@ -28,6 +29,21 @@ object ResolveDepsTests extends TestSuite {
assert(upickle.default.read[Dep](upickle.default.write(dep)) == dep)
}
}
+
+ object TestCase extends TestBaseModule {
+ object pomStuff extends JavaModule {
+ def ivyDeps = Agg(
+ // Dependency whose packaging is "pom", as it's meant to be used
+ // as a "parent dependency" by other dependencies, rather than be pulled
+ // as we do here. We do it anyway, to check that pulling the "pom" artifact
+ // type brings that dependency POM file in the class path. We need a dependency
+ // that has a "pom" packaging for that.
+ ivy"org.apache.hadoop:hadoop-yarn-server:3.4.0"
+ )
+ def artifactTypes = super.artifactTypes() + coursier.Type("pom")
+ }
+ }
+
val tests = Tests {
test("resolveValidDeps") {
val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3")
@@ -95,5 +111,18 @@ object ResolveDepsTests extends TestSuite {
val Failure(errMsg, _) = evalDeps(deps)
assert(errMsg.contains("fake"))
}
+
+ test("pomArtifactType") {
+ val sources = os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "pomArtifactType"
+ UnitTester(TestCase, sourceRoot = sources).scoped { eval =>
+ val Right(compileResult) = eval(TestCase.pomStuff.compileClasspath)
+ val compileCp = compileResult.value.toSeq.map(_.path)
+ assert(compileCp.exists(_.lastOpt.contains("hadoop-yarn-server-3.4.0.pom")))
+
+ val Right(runResult) = eval(TestCase.pomStuff.runClasspath)
+ val runCp = runResult.value.toSeq.map(_.path)
+ assert(runCp.exists(_.lastOpt.contains("hadoop-yarn-server-3.4.0.pom")))
+ }
+ }
}
}