From 09f5a5d698aee846144e37edd4326e7aff567586 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Thu, 11 Nov 2021 15:17:45 +0100 Subject: [PATCH] Backported --import cli option and support empty plugin versions (#1562) With this PR applied, you can run targets from external modules from the command-line without touching the `build.sc`. Example: ``` dev-mill --plugin ivy:de.tototec::de.tobiasroeser.mill.vcs.version_mill0.10.0-M2:0.1.2 show de.tobiasroeser.mill.vcs.version.VcsVersion/vcsState ``` This PR also add support for empty versions in $ivy-imports, which will get expanded to the exact mill version. This makes consuming mill contrib plugins even more easy. Example: Generating Bloop Config without modifying the `build.sc` ``` mill --plugin ivy:com.lihaoyi::mill-contrib-bloop: mill.contrib.bloop.Bloop/install ``` See pull request: https://github.com/com-lihaoyi/mill/pull/1526 Pull request: https://github.com/com-lihaoyi/mill/pull/1562 --- .../modules/ROOT/pages/Contrib_Plugins.adoc | 62 ++++++++------- .../modules/ROOT/pages/Extending_Mill.adoc | 15 +++- main/src/MillMain.scala | 79 ++++++++++++------- main/src/main/MillIvyHook.scala | 5 +- main/test/src/main/MillIvyHookTest.scala | 66 ++++++++++++++++ 5 files changed, 168 insertions(+), 59 deletions(-) create mode 100644 main/test/src/main/MillIvyHookTest.scala diff --git a/docs/antora/modules/ROOT/pages/Contrib_Plugins.adoc b/docs/antora/modules/ROOT/pages/Contrib_Plugins.adoc index 97afc791015..fb93ba3a45b 100644 --- a/docs/antora/modules/ROOT/pages/Contrib_Plugins.adoc +++ b/docs/antora/modules/ROOT/pages/Contrib_Plugins.adoc @@ -8,6 +8,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: @@ -15,6 +16,13 @@ For instance: ---- import $ivy.`com.lihaoyi::mill-contrib-bloop:$MILL_VERSION` ---- + +or + +[source,scala] +---- +import $ivy.`com.lihaoyi::mill-contrib-bloop:` +---- -- == Artifactory @@ -25,7 +33,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 { @@ -53,7 +61,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 { @@ -72,7 +80,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 { @@ -120,7 +128,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 : @@ -173,7 +181,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 { @@ -223,7 +231,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 { @@ -254,7 +262,7 @@ In the simplest configuration just extend `DockerModule` and declare a `DockerCo ---- import mill._, scalalib._ -import $ivy.`com.lihaoyi::mill-contrib-docker:$MILL_VERSION` +import $ivy.`com.lihaoyi::mill-contrib-docker:` import contrib.docker.DockerModule object foo extends JavaModule with DockerModule { @@ -302,7 +310,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 { @@ -378,7 +386,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 @@ -448,7 +456,7 @@ The `PlayApiModule` trait behaves the same as the `PlayModule` trait but it won' [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 PlayApiModule { //config @@ -485,7 +493,7 @@ like in the following example 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 PlayApiModule { //config @@ -526,7 +534,7 @@ Looking back at the sample build definition in <<_using_playmodule>>: [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 @@ -567,7 +575,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 @@ -612,7 +620,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"} @@ -667,7 +675,7 @@ To add additional imports to all of the routes: ---- import mill.scalalib._ -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 = "2.7.0" @@ -695,7 +703,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 { @@ -720,7 +728,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 { @@ -759,7 +767,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 { @@ -774,7 +782,7 @@ If you'd like to pass additional arguments to the ScalaPB compiler directly, you .`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 { @@ -799,7 +807,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 { @@ -904,7 +912,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 { // ... @@ -941,7 +949,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" @@ -978,7 +986,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" @@ -1020,7 +1028,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" @@ -1058,7 +1066,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 @@ -1095,7 +1103,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 @@ -1115,7 +1123,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 06273eca642..3aff710c7e6 100644 --- a/docs/antora/modules/ROOT/pages/Extending_Mill.adoc +++ b/docs/antora/modules/ROOT/pages/Extending_Mill.adoc @@ -199,9 +199,18 @@ This is typical required for Mill contrib modules, which are developed in the Mi .Example: Use `mill-contrib-bloop` plugin matching the current Mill version ---- -import $ivy:`com.lihaoyi:mill-contrib-bloop:$MILL_VERSION` +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` :: + @@ -210,7 +219,7 @@ to substitute the currently used Mill binary platform. .Example: Using `mill-vcs-version` plugin matching the current Mill Binary Platfrom ---- -import $ivy:`de.tototec::de.tobiasroeser.mill.vcs.version::0.1.2` +import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.1.2` ---- -- diff --git a/main/src/MillMain.scala b/main/src/MillMain.scala index d678eea9725..79016fb6511 100644 --- a/main/src/MillMain.scala +++ b/main/src/MillMain.scala @@ -76,6 +76,11 @@ case class MillConfig( 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 = @@ -119,7 +124,7 @@ object MillMain { ): (Boolean, Option[Evaluator.State]) = { val parser = mainargs.ParserForClass[MillConfig] - val customName = "Mill Build Tool" + val customName = s"Mill Build Tool, version ${BuildInfo.millVersion}" val customDoc = "usage: mill [mill-options] [target [target-options]]" if (args.take(1).toSeq == Seq("--help")) { stdout.println( @@ -220,39 +225,57 @@ 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 - .replaceAllLiterally("$", "$$") - ) - .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 = customName, noRemoteLogging = Flag(), classBased = Flag() ) diff --git a/main/src/main/MillIvyHook.scala b/main/src/main/MillIvyHook.scala index fbc24d2a309..479f1fb2d08 100644 --- a/main/src/main/MillIvyHook.scala +++ b/main/src/main/MillIvyHook.scala @@ -18,6 +18,7 @@ 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( @@ -25,7 +26,7 @@ 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 { signature.split("[:]") match { case Array(org, "", pname, "", version) @@ -34,6 +35,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 new file mode 100644 index 00000000000..f5b35a41226 --- /dev/null +++ b/main/test/src/main/MillIvyHookTest.scala @@ -0,0 +1,66 @@ +package mill.main + +import java.io.File + +import scala.util.Try + +import ammonite.runtime.ImportHook.InterpreterInterface +import coursierapi.{Dependency => CDependency, Module => CModule, ScalaVersion => CScalaVersion} +import utest.{TestSuite, Tests, _} + +object MillIvyHookTest extends TestSuite { + val wd = os.root / "tmp" + def mapDep(d: CDependency): Seq[File] = + Seq( + (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 { + def loadIvy(coordinates: CDependency*): Either[String, Seq[File]] = + Right(coordinates.flatMap(mapDep)) + def watch(p: os.Path): Unit = ??? + def scalaVersion: String = "2.13.6" + } + 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(CModule.parse("a::b", CScalaVersion.of("2.13.6")), "c"), + wd / "a__b_2.13__c__b_2.13-c.jar" + ), + ( + "a::b::c", + CDependency.of( + CModule.parse( + s"a::b_mill${mill.BuildInfo.millBinPlatform}", + CScalaVersion.of("2.13.6") + ), + "c" + ), + 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) => + Try { + val expected: Either[String, (Seq[CDependency], Seq[File])] = + Right(Seq(dep), Seq(path.toIO)) + val resolved = MillIvyHook.resolve(interp, Seq(coord)) + assert( + // first check only adds context to the exception message + coord.nonEmpty && dep.toString.nonEmpty && resolved == expected + ) + } + } + assert(checks.forall(_.isSuccess)) + } + } +}