diff --git a/docs/antora/modules/ROOT/pages/Contrib_Plugins.adoc b/docs/antora/modules/ROOT/pages/Contrib_Plugins.adoc index 0b9b6857c85..d0cea507f04 100644 --- a/docs/antora/modules/ROOT/pages/Contrib_Plugins.adoc +++ b/docs/antora/modules/ROOT/pages/Contrib_Plugins.adoc @@ -9,6 +9,7 @@ For details about including plugins in your `build.sc` read xref:Extending_Mill. -- When using one of these contribution modules, it is important that the versions you load match your mill version. To facilitate this, Mill will automatically replace the `$MILL_VERSION` literal in your ivy imports with the correct value. +You can also leave the version completely empty to default to the mill version (but don't forget to keep the trailing colon). For instance: @@ -16,6 +17,14 @@ For instance: ---- import $ivy.`com.lihaoyi::mill-contrib-bloop:$MILL_VERSION` ---- + +or + +[source,scala] +---- +import $ivy.`com.lihaoyi::mill-contrib-bloop:` +---- + -- == Artifactory @@ -26,7 +35,7 @@ This plugin allows publishing to Artifactory. [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-artifactory:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-artifactory:` import mill.contrib.artifactory.ArtifactoryPublishModule object mymodule extends ArtifactoryPublishModule { @@ -54,7 +63,7 @@ Make sure your module extends from `BintrayPublishModule`: [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-bintray:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-bintray:` import mill.contrib.bintray.BintrayPublishModule object mymodule extends BintrayPublishModule { @@ -73,7 +82,7 @@ the package used, you can do that like this: [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-bintray:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-bintray:` import mill.contrib.bintray.BintrayPublishModule object mymodule extends BintrayPublishModule { @@ -121,7 +130,7 @@ your scala code editable in https://scalameta.org/metals/[Metals] [source,scala] ---- // build.sc (or any other .sc file it depends on, including predef) -import $ivy.`com.lihaoyi::mill-contrib-bloop:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-bloop:` ---- Then in your terminal : @@ -174,7 +183,7 @@ Quickstart: .`build.sc` [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-buildinfo:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-buildinfo:` import mill.contrib.buildinfo.BuildInfo object project extends BuildInfo { @@ -224,7 +233,7 @@ This plugin allows publishing to AWS Codeartifact. [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-codeartifact:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-codeartifact:` import mill.contrib.codeartifact.CodeartifactPublishModule object mymodule extends CodeartifactPublishModule { @@ -322,7 +331,7 @@ Configure flyway by overriding settings in your module. For example ---- import mill._, scalalib._ -import $ivy.`com.lihaoyi::mill-contrib-flyway:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-flyway:` import contrib.flyway.FlywayModule object foo extends ScalaModule with FlywayModule { @@ -398,7 +407,7 @@ Twirl versions. You also need to define your own test object which extends the p [source,scala] ---- import mill._ -import $ivy.`com.lihaoyi::mill-contrib-playlib:$MILL_VERSION`, mill.playlib._ +import $ivy.`com.lihaoyi::mill-contrib-playlib:`, mill.playlib._ object core extends PlayModule { //config @@ -587,7 +596,7 @@ by mixing in the `SingleModule` trait in your build: [source,scala] ---- import mill._ -import $ivy.`com.lihaoyi::mill-contrib-playlib:$MILL_VERSION`, mill.playlib._ +import $ivy.`com.lihaoyi::mill-contrib-playlib:`, mill.playlib._ object core extends PlayModule with SingleModule { //config @@ -632,7 +641,7 @@ define `playVersion` and `scalaVersion`. [source,scala] ---- import mill._ -import $ivy.`com.lihaoyi::mill-contrib-playlib:$MILL_VERSION`, mill.playlib._ +import $ivy.`com.lihaoyi::mill-contrib-playlib:`, mill.playlib._ object app extends ScalaModule with RouterModule { def playVersion= T{"2.7.0"} @@ -715,7 +724,7 @@ Here is a simple example: .`build.sc` [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-proguard:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-proguard:` import contrib.proguard._ object foo extends ScalaModule with Proguard { @@ -740,7 +749,7 @@ This creates a Scala module which compiles `.proto` files in the `protobuf` fold .`build.sc` [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-scalapblib:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-scalapblib:` import contrib.scalapblib._ object example extends ScalaPBModule { @@ -779,7 +788,7 @@ If you'd like to configure the https://scalapb.github.io/docs/scalapbc#passing-g .`build.sc` [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-scalapblib:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-scalapblib:` import contrib.scalapblib._ object example extends ScalaPBModule { @@ -819,7 +828,7 @@ module. Additionally, you must define a submodule that extends the .`build.sc` [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-scoverage:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-scoverage:` import mill.contrib.scoverage.ScoverageModule object foo extends ScoverageModule { @@ -924,7 +933,7 @@ Also note that twirl templates get compiled into scala code, so you also need to ---- import mill.scalalib._ -import $ivy.`com.lihaoyi::mill-contrib-twirllib:$MILL_VERSION`, mill.twirllib._ +import $ivy.`com.lihaoyi::mill-contrib-twirllib:`, mill.twirllib._ object app extends ScalaModule with TwirlModule { // ... @@ -961,7 +970,7 @@ directory. This directory must be added to the generated sources of the module t ---- import mill.scalalib._ -import $ivy.`com.lihaoyi::mill-contrib-twirllib:$MILL_VERSION`, mill.twirllib._ +import $ivy.`com.lihaoyi::mill-contrib-twirllib:`, mill.twirllib._ object app extends ScalaModule with TwirlModule { def twirlVersion = "1.3.15" @@ -998,7 +1007,7 @@ To add additional imports to all of the twirl templates, override `twirlImports` ---- import mill.scalalib._ -import $ivy.`com.lihaoyi::mill-contrib-twirllib:$MILL_VERSION`, mill.twirllib._ +import $ivy.`com.lihaoyi::mill-contrib-twirllib:`, mill.twirllib._ object app extends ScalaModule with TwirlModule { def twirlVersion = "1.3.15" @@ -1040,7 +1049,7 @@ To add additional formats, override `twirlFormats` in your build: ---- import mill.scalalib._ -import $ivy.`com.lihaoyi::mill-contrib-twirllib:$MILL_VERSION`, mill.twirllib._ +import $ivy.`com.lihaoyi::mill-contrib-twirllib:`, mill.twirllib._ object app extends ScalaModule with TwirlModule { def twirlVersion = "1.3.15" @@ -1078,7 +1087,7 @@ Add a `VersionFileModule` to the `build.sc` file: [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-versionfile:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-versionfile:` import mill.contrib.versionfile.VersionFileModule object versionFile extends VersionFileModule @@ -1115,7 +1124,7 @@ If you want to use the version file for publishing, you can do it like this: [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-versionfile:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-versionfile:` import mill.contrib.versionfile.VersionFileModule object versionFile extends VersionFileModule @@ -1135,7 +1144,7 @@ at the root of the project, you can override `millSourcePath`: [source,scala] ---- -import $ivy.`com.lihaoyi::mill-contrib-versionfile:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-versionfile:` import mill.contrib.versionfile.VersionFileModule object versionFile extends VersionFileModule { diff --git a/docs/antora/modules/ROOT/pages/Extending_Mill.adoc b/docs/antora/modules/ROOT/pages/Extending_Mill.adoc index 8ca3bc48b60..e5ef706406d 100644 --- a/docs/antora/modules/ROOT/pages/Extending_Mill.adoc +++ b/docs/antora/modules/ROOT/pages/Extending_Mill.adoc @@ -201,6 +201,15 @@ This is typical required for Mill contrib modules, which are developed in the Mi ---- import $ivy:`com.lihaoyi:mill-contrib-bloop:$MILL_VERSION` ---- + +There is the even more convenient option to leave the version completely empty. +Mill will substitute it with its current version. +But don't forget to provide the trailing colon! + +.Example: Use `mill-contrib-bloop` plugin matching the current Mill version +---- +import $ivy:`com.lihaoyi:mill-contrib-bloop:` +---- -- `$MILL_BIN_PLATFORM` :: diff --git a/main/src/mill/MillConfig.scala b/main/src/mill/MillConfig.scala new file mode 100644 index 00000000000..cd3ba3233f2 --- /dev/null +++ b/main/src/mill/MillConfig.scala @@ -0,0 +1,82 @@ +package mill + +import mainargs.{Flag, Leftover, arg} + +case class MillConfig( + ammoniteCore: ammonite.main.Config.Core, + @arg( + short = 'h', + doc = + "The home directory of the REPL; where it looks for config and caches" + ) + home: os.Path = mill.api.Ctx.defaultHome, + @arg( + doc = + """Run Mill in interactive mode and start a build REPL. In this mode, no + mill server will be used. Must be the first argument.""" + ) + repl: Flag, + @arg( + name = "no-server", + doc = + """Run Mill in interactive mode, suitable for opening REPLs and taking user + input. In this mode, no mill server will be used. Must be the first argument.""" + ) + noServer: Flag, + @arg( + short = 'i', + doc = + """Run Mill in interactive mode, suitable for opening REPLs and taking user + input. In this mode, no mill server will be used. Must be the first argument.""" + ) + interactive: Flag, + @arg(name = "version", short = 'v', doc = "Show mill version and exit.") + showVersion: Flag, + @arg( + name = "bell", + short = 'b', + doc = + "Ring the bell once if the run completes successfully, twice if it fails." + ) + ringBell: Flag, + @arg( + name = "disable-ticker", + doc = + "Disable ticker log (e.g. short-lived prints of stages and progress bars)" + ) + disableTicker: Flag, + @arg(name = "debug", short = 'd', doc = "Show debug output on STDOUT") + debugLog: Flag, + @arg( + name = "keep-going", + short = 'k', + doc = "Continue build, even after build failures" + ) + keepGoing: Flag, + @arg( + name = "define", + short = 'D', + doc = "Define (or overwrite) a system property" + ) + extraSystemProperties: Map[String, String], + @arg( + name = "jobs", + short = 'j', + doc = + """Allow processing N targets in parallel. Use 1 to disable parallel and 0 to + use as much threads as available processors.""" + ) + threadCountRaw: Option[Int], + @arg( + name = "import", + doc = """Additional ivy dependencies to load into mill, e.g. plugins.""" + ) + imports: Seq[String], + @arg( + name = "rest", + doc = + """The name of the targets you want to build, followed by any parameters + you wish to pass to those targets.""" + ) + leftoverArgs: Leftover[String] +) diff --git a/main/src/mill/MillConfigParser.scala b/main/src/mill/MillConfigParser.scala new file mode 100644 index 00000000000..2ab343d4d6d --- /dev/null +++ b/main/src/mill/MillConfigParser.scala @@ -0,0 +1,30 @@ +package mill + +import mainargs.ParserForClass +import ammonite.repl.tools.Util.PathRead + +object MillConfigParser { + + val customName = s"Mill Build Tool, version ${BuildInfo.millVersion}" + val customDoc = "usage: mill [options] [[target [target-options]] [+ [target ...]]]" + + private[this] lazy val parser: ParserForClass[MillConfig] = mainargs.ParserForClass[MillConfig] + + lazy val usageText = parser.helpText(customName = customName, customDoc = customDoc) + + def parse(args: Array[String]): Either[String, MillConfig] = { + parser.constructEither( + args, + allowRepeats = true, + autoPrintHelpAndExit = None, + customName = customName, + customDoc = customDoc + ) + .map { config => + config.copy( + ammoniteCore = config.ammoniteCore.copy(home = config.home) + ) + } + } + +} diff --git a/main/src/mill/MillMain.scala b/main/src/mill/MillMain.scala index 343cc157a53..e574c10bb1c 100644 --- a/main/src/mill/MillMain.scala +++ b/main/src/mill/MillMain.scala @@ -7,111 +7,10 @@ import scala.jdk.CollectionConverters._ import scala.util.Properties import io.github.retronym.java9rtexport.Export -import mainargs.{Flag, Leftover, ParserForClass, arg} +import mainargs.Flag import mill.api.DummyInputStream import mill.main.EvaluatorState -case class MillConfig( - ammoniteCore: ammonite.main.Config.Core, - @arg( - short = 'h', - doc = - "The home directory of the REPL; where it looks for config and caches" - ) - home: os.Path = mill.api.Ctx.defaultHome, - @arg( - doc = - """Run Mill in interactive mode and start a build REPL. In this mode, no - mill server will be used. Must be the first argument.""" - ) - repl: Flag, - @arg( - name = "no-server", - doc = - """Run Mill in interactive mode, suitable for opening REPLs and taking user - input. In this mode, no mill server will be used. Must be the first argument.""" - ) - noServer: Flag, - @arg( - short = 'i', - doc = - """Run Mill in interactive mode, suitable for opening REPLs and taking user - input. In this mode, no mill server will be used. Must be the first argument.""" - ) - interactive: Flag, - @arg(name = "version", short = 'v', doc = "Show mill version and exit.") - showVersion: Flag, - @arg( - name = "bell", - short = 'b', - doc = - "Ring the bell once if the run completes successfully, twice if it fails." - ) - ringBell: Flag, - @arg( - name = "disable-ticker", - doc = - "Disable ticker log (e.g. short-lived prints of stages and progress bars)" - ) - disableTicker: Flag, - @arg(name = "debug", short = 'd', doc = "Show debug output on STDOUT") - debugLog: Flag, - @arg( - name = "keep-going", - short = 'k', - doc = "Continue build, even after build failures" - ) - keepGoing: Flag, - @arg( - name = "define", - short = 'D', - doc = "Define (or overwrite) a system property" - ) - extraSystemProperties: Map[String, String], - @arg( - name = "jobs", - short = 'j', - doc = - """Allow processing N targets in parallel. Use 1 to disable parallel and 0 to - use as much threads as available processors.""" - ) - threadCountRaw: Option[Int], - @arg( - name = "rest", - doc = - """The name of the targets you want to build, followed by any parameters - you wish to pass to those targets.""" - ) - leftoverArgs: Leftover[String] -) - -object MillConfigParser { - import ammonite.repl.tools.Util.PathRead - - val customName = s"Mill Build Tool, version ${BuildInfo.millVersion}" - val customDoc = "usage: mill [options] [[target [target-options]] [+ [target ...]]]" - - private[this] lazy val parser: ParserForClass[MillConfig] = mainargs.ParserForClass[MillConfig] - - lazy val usageText = parser.helpText(customName = customName, customDoc = customDoc) - - def parse(args: Array[String]): Either[String, MillConfig] = { - parser.constructEither( - args, - allowRepeats = true, - autoPrintHelpAndExit = None, - customName = customName, - customDoc = customDoc - ) - .map { config => - config.copy( - ammoniteCore = config.ammoniteCore.copy(home = config.home) - ) - } - } - -} - object MillMain { def main(args: Array[String]): Unit = { @@ -221,39 +120,50 @@ object MillMain { case Some(0) => None case Some(n) => Some(n) } + + val predefCode = + if (!useRepl) "" + else + s"""import $$file.build, build._ + |implicit val replApplyHandler = mill.main.ReplApplyHandler( + | os.Path(${pprint + .apply( + config.ammoniteCore.home.toIO.getCanonicalPath + .replace("$", "$$") + ) + .plainText}), + | ${config.disableTicker.value}, + | interp.colors(), + | repl.pprinter(), + | build.millSelf.get, + | build.millDiscover, + | debugLog = ${config.debugLog.value}, + | keepGoing = ${config.keepGoing.value}, + | systemProperties = ${systemProps.toSeq + .map(p => s""""${p._1}" -> "${p._2}"""") + .mkString("Map[String,String](", ",", ")")}, + | threadCount = ${threadCount} + |) + |repl.pprinter() = replApplyHandler.pprinter + |import replApplyHandler.generatedEval._ + |""".stripMargin + + val importsPredefCode: String = config.imports.map { + _.split("[:]", 2) match { + case Array("ivy", dep) => + s"""import $$ivy.`${dep}`""" + case x => throw new Exception(s"Unsupported plugin declaration: '$x'.") + } + }.mkString("\n") + val ammConfig = ammonite.main.Config( core = config.ammoniteCore, predef = ammonite.main.Config.Predef( - predefCode = - if (!useRepl) "" - else - s"""import $$file.build, build._ - |implicit val replApplyHandler = mill.main.ReplApplyHandler( - | os.Path(${pprint - .apply( - config.ammoniteCore.home.toIO.getCanonicalPath - .replace("$", "$$") - ) - .plainText}), - | ${config.disableTicker.value}, - | interp.colors(), - | repl.pprinter(), - | build.millSelf.get, - | build.millDiscover, - | debugLog = ${config.debugLog.value}, - | keepGoing = ${config.keepGoing.value}, - | systemProperties = ${systemProps.toSeq - .map(p => s""""${p._1}" -> "${p._2}"""") - .mkString("Map[String,String](", ",", ")")}, - | threadCount = ${threadCount} - |) - |repl.pprinter() = replApplyHandler.pprinter - |import replApplyHandler.generatedEval._ - |""".stripMargin, + predefCode = Seq(predefCode, importsPredefCode).filter(_.nonEmpty).mkString("\n"), noHomePredef = Flag() ), repl = ammonite.main.Config.Repl( - banner = "", + banner = MillConfigParser.customName, noRemoteLogging = Flag(), classBased = Flag() ) diff --git a/main/src/mill/main/MillIvyHook.scala b/main/src/mill/main/MillIvyHook.scala index 3c8f056dd99..89824235ee7 100644 --- a/main/src/mill/main/MillIvyHook.scala +++ b/main/src/mill/main/MillIvyHook.scala @@ -17,6 +17,8 @@ import mill.BuildInfo * * - supports the format `org:::name::version` for mill plugins; * which is equivalent to `org:::name_mill$MILL_BIN_PLATFORM:version` + * + * - replaces the empty version for scala dependencies as $MILL_VERSION */ object MillIvyHook extends BaseIvy(plugin = false) { override def resolve( @@ -24,8 +26,10 @@ object MillIvyHook extends BaseIvy(plugin = false) { signatures: Seq[String] ): Either[String, (Seq[coursierapi.Dependency], Seq[File])] = { - // replace platform notation + // replace platform notation and empty version val millSigs: Seq[String] = for (signature <- signatures) yield { +// if (signature.endsWith(":") && signature.count(_ == ":") == 4) signature + "$MILL_VERSION" +// else signature.split("[:]") match { case Array(org, "", pname, "", version) if org.length > 0 && pname.length > 0 && version.length > 0 => @@ -33,6 +37,8 @@ object MillIvyHook extends BaseIvy(plugin = false) { case Array(org, "", "", pname, "", version) if org.length > 0 && pname.length > 0 && version.length > 0 => s"${org}:::${pname}_mill$$MILL_BIN_PLATFORM:${version}" + case Array(org, "", name) if org.length > 0 && name.length > 0 && signature.endsWith(":") => + s"${org}::${name}:$$MILL_VERSION" case _ => signature } } diff --git a/main/test/src/main/MillIvyHookTest.scala b/main/test/src/main/MillIvyHookTest.scala index db598b1b824..69f1eeea44b 100644 --- a/main/test/src/main/MillIvyHookTest.scala +++ b/main/test/src/main/MillIvyHookTest.scala @@ -9,10 +9,10 @@ import coursierapi.{Dependency => CDependency, Module => CModule, ScalaVersion = import utest.{TestSuite, Tests, _} object MillIvyHookTest extends TestSuite { - val wd = os.pwd + val wd = os.root / "tmp" def mapDep(d: CDependency): Seq[File] = Seq( - (wd / d.getModule.getOrganization / d.getModule.getName / d.getVersion / s"${d.getModule.getName}-${d.getVersion}.jar").toIO + (wd / s"${d.getModule.getOrganization}__${d.getModule.getName}__${d.getVersion}__${d.getModule.getName}-${d.getVersion}.jar").toIO ) override def tests: Tests = Tests { val interp = new InterpreterInterface { @@ -23,11 +23,11 @@ object MillIvyHookTest extends TestSuite { } test("simple") { val deps = Seq( - ("a:b:c", CDependency.of("a", "b", "c"), wd / "a" / "b" / "c" / "b-c.jar"), + ("a:b:c", CDependency.of("a", "b", "c"), wd / "a__b__c__b-c.jar"), ( "a::b:c", CDependency.of(CModule.parse("a::b", CScalaVersion.of("2.13.6")), "c"), - wd / "a" / "b_2.13" / "c" / "b_2.13-c.jar" + wd / "a__b_2.13__c__b_2.13-c.jar" ), ( "a::b::c", @@ -38,7 +38,15 @@ object MillIvyHookTest extends TestSuite { ), "c" ), - wd / "a" / s"b_mill${mill.BuildInfo.millBinPlatform}_2.13" / "c" / s"b_mill${mill.BuildInfo.millBinPlatform}_2.13-c.jar" + wd / s"a__b_mill${mill.BuildInfo.millBinPlatform}_2.13__c__b_mill${mill.BuildInfo.millBinPlatform}_2.13-c.jar" + ), + ( + s"a::b:", + CDependency.of( + CModule.parse("a::b", CScalaVersion.of("2.13.6")), + mill.BuildInfo.millVersion + ), + wd / s"a__b_2.13__${mill.BuildInfo.millVersion}__b_2.13-${mill.BuildInfo.millVersion}.jar" ) ) val checks = deps.map { case (coord, dep, path) => @@ -48,7 +56,7 @@ object MillIvyHookTest extends TestSuite { val resolved = MillIvyHook.resolve(interp, Seq(coord)) assert( // first check only adds context to the exception message - !coord.isEmpty && resolved == expected + coord.nonEmpty && dep.toString.nonEmpty && resolved == expected ) } }