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"))) + } + } } }