diff --git a/docs/antora/modules/ROOT/pages/Extending_Mill.adoc b/docs/antora/modules/ROOT/pages/Extending_Mill.adoc index c9f53fe2bd3..8ca3bc48b60 100644 --- a/docs/antora/modules/ROOT/pages/Extending_Mill.adoc +++ b/docs/antora/modules/ROOT/pages/Extending_Mill.adoc @@ -236,7 +236,6 @@ def idea(ev: Evaluator) = T.command { ---- Many built-in tools are implemented as custom evaluator commands: -xref:Intro_to_Mill.adoc#_all[all], xref:Intro_to_Mill.adoc#_inspect[inspect], xref:Intro_to_Mill.adoc#_resolve[resolve], xref:Intro_to_Mill.adoc#_show[show]. diff --git a/docs/antora/modules/ROOT/pages/Intro_to_Mill.adoc b/docs/antora/modules/ROOT/pages/Intro_to_Mill.adoc index e9c974b7e45..631b7a8ddd6 100644 --- a/docs/antora/modules/ROOT/pages/Intro_to_Mill.adoc +++ b/docs/antora/modules/ROOT/pages/Intro_to_Mill.adoc @@ -426,22 +426,77 @@ This is currently the default. Please note that the maximal possible parallelism depends on your project. Tasks that depend on each other can't be processed in parallel. -== Command-line Tools - -Mill comes with a small number of useful command-line utilities built into it: +== Command-line usage + +Mill is a command-line tool and supports various options. + +Run `mill --help` for a complete list of options + +.Output of `mill --help` +---- +Mill Build Tool +usage: mill [options] [[target [target-options]] [+ [target ...]]] + --no-default-predef Disable the default predef and run Ammonite with the minimal predef possible + -s --silent Make ivy logs go silent instead of printing though failures will still throw + exception + -w --watch Watch and re-run your scripts when they change + --bsp Run a BSP server against the passed scripts + -c --code Pass in code to be run immediately in the REPL + -h --home The home directory of the REPL; where it looks for config and caches + -p --predef Lets you load your predef from a custom location, rather than the "default + location in your Ammonite home + --color Enable or disable colored output; by default colors are enabled in both REPL + and scripts if the console is interactive, and disabled otherwise + --thin Hide parts of the core of Ammonite and some of its dependencies. By default, + the core of Ammonite and all of its dependencies can be seen by users from + the Ammonite session. This option mitigates that via class loader isolation. + --help Print this message + -h --home The home directory of the REPL; where it looks for config and caches + --repl Run Mill in interactive mode and start a build REPL. In this mode, no mill + server will be used. Must be the first argument. + --no-server 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. + -i --interactive 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. + -v --version Show mill version and exit. + -b --bell Ring the bell once if the run completes successfully, twice if it fails. + --disable-ticker Disable ticker log (e.g. short-lived prints of stages and progress bars) + -d --debug Show debug output on STDOUT + -k --keep-going Continue build, even after build failures + -D --define Define (or overwrite) a system property + -j --jobs Allow processing N targets in parallel. Use 1 to disable parallel and 0 to + use as much threads as available processors. + rest ... The name of the targets you want to build, followed by any parameters you + wish to pass to those targets. +---- + +All _options_ must be given before the first target. + +A _target_ is a fully qualified task or command optionally followed by target specific arguments. +You can use wildcards and brace-expansion to select multiple targets at once or to shorten the path to deeply nested targets. +If you provide optional target arguments and your wildcard or brace-expansion is resolved to multiple targets, the arguments will be applied to each of the targets. + +.Wildcards and brace-expansion +|=== +| Wildcard | Function +|`_` | matches a single segment of the target path +| `__` | matches arbitrary segments of the target path +| `{a,b}` | is equal to specifying two targets `a` and `b` +|=== + +You can use the `+` symbol to add another target with optional arguments. +If you need to feed a `+` as argument to your target, you can mask it by preceding it with a backslash (`\`). + +=== Examples + +`+mill foo._.compile+`:: Runs `compile` for all direct sub-modules of `foo` +`+mill foo.__.test+` :: Runs `test` for all sub-modules of `foo` +`+mill {foo,bar}.__.testCached+` :: Runs `testCached` for all sub-modules of `foo` and `bar` +`+mill __.compile + foo.__.test+` :: Runs all `compile` -=== all - -[source,bash] ----- -mill all foo.{compile,run} -mill all "foo.{compile,run}" -mill all foo.compile foo.run -mill all _.compile # run compile for every top-level module -mill all __.compile # run compile for every module ----- +== Command-line Tools -`all` runs multiple tasks in a single command +Mill comes with a few useful command-line utilities built into it: === resolve diff --git a/main/src/mill/MillMain.scala b/main/src/mill/MillMain.scala index 70cffaf1fbc..343cc157a53 100644 --- a/main/src/mill/MillMain.scala +++ b/main/src/mill/MillMain.scala @@ -7,8 +7,7 @@ import scala.jdk.CollectionConverters._ import scala.util.Properties import io.github.retronym.java9rtexport.Export -import mainargs.{Flag, Leftover, arg} -import ammonite.repl.tools.Util.PathRead +import mainargs.{Flag, Leftover, ParserForClass, arg} import mill.api.DummyInputStream import mill.main.EvaluatorState @@ -86,6 +85,33 @@ case class MillConfig( 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 = { @@ -121,199 +147,172 @@ object MillMain { initialSystemProperties: Map[String, String] ): (Boolean, Option[EvaluatorState]) = { - val parser = mainargs.ParserForClass[MillConfig] - val customName = "Mill Build Tool" - val customDoc = "usage: mill [mill-options] [target [target-options]]" - if (args.take(1).toSeq == Seq("--help")) { - stdout.println( - parser.helpText(customName = customName, customDoc = customDoc) - ) - (true, None) - } else - parser - .constructEither( - args, - allowRepeats = true, - autoPrintHelpAndExit = None, - customName = customName, - customDoc = customDoc + MillConfigParser.parse(args) match { + case Left(msg) => + stderr.println(msg) + (false, None) + case Right(config) if config.ammoniteCore.help.value => + stdout.println(MillConfigParser.usageText) + (true, None) + case Right(config) if config.showVersion.value => + def p(k: String, d: String = "") = System.getProperty(k, d) + stdout.println( + s"""Mill Build Tool version ${BuildInfo.millVersion} + |Java version: ${p("java.version", "" + )} + |OS name: "${p("os.name")}", version: ${p("os.version")}, arch: ${p( + "os.arch" + )}""".stripMargin ) - .map { config => - config.copy( - ammoniteCore = config.ammoniteCore.copy(home = config.home) - ) - } match { - case Left(msg) => - stderr.println(msg) - (false, None) - case Right(config) => + (true, None) + case Right(config) if ( - (config.interactive.value || config.repl.value || config.noServer.value) && - stdin == DummyInputStream - ) { + config.interactive.value || config.repl.value || config.noServer.value + ) && stdin == DummyInputStream => + // because we have stdin as dummy, we assume we were already started in server process + stderr.println( + "-i/--interactive/--repl/--no-server must be passed in as the first argument" + ) + (false, None) + case Right(config) + if Seq( + config.interactive.value, + config.repl.value, + config.noServer.value + ).count(identity) > 1 => + stderr.println( + "Only one of -i/--interactive, --repl, or --no-server may be given" + ) + (false, None) + case Right(config) => + val useRepl = + config.repl.value || (config.interactive.value && config.leftoverArgs.value.isEmpty) + val (success, nextStateCache) = + if (config.repl.value && config.leftoverArgs.value.nonEmpty) { + stderr.println("No target may be provided with the --repl flag") + (false, stateCache) + } else if (config.leftoverArgs.value.isEmpty && config.noServer.value) { stderr.println( - "-i/--interactive/--repl/--no-server must be passed in as the first argument" + "A target must be provided when not starting a build REPL" ) - (false, None) - } else if ( - Seq( - config.interactive.value, - config.repl.value, - config.noServer.value - ).count(identity) > 1 - ) { + (false, stateCache) + } else if (useRepl && stdin == DummyInputStream) { stderr.println( - "Only one of -i/--interactive, --repl, or --no-server may be given" - ) - (false, None) - } else if (config.showVersion.value) { - def p(k: String, d: String = "") = System.getProperty(k, d) - stdout.println( - s"""Mill Build Tool version ${p( - "MILL_VERSION", - "" - )} - |Java version: ${p( - "java.version", - "" - )} - |OS name: "${p("os.name")}", version: ${p( - "os.version" - )}, arch: ${p("os.arch")}""".stripMargin + "Build REPL needs to be run with the -i/--interactive/--repl flag" ) - (true, None) + (false, stateCache) } else { - val useRepl = - config.repl.value || (config.interactive.value && config.leftoverArgs.value.isEmpty) - val (success, nextStateCache) = - if (config.repl.value && config.leftoverArgs.value.nonEmpty) { - stderr.println("No target may be provided with the --repl flag") - (false, stateCache) - } else if (config.leftoverArgs.value.isEmpty && config.noServer.value) { - stderr.println( - "A target must be provided when not starting a build REPL" - ) - (false, stateCache) - } else if (useRepl && stdin == DummyInputStream) { - stderr.println( - "Build REPL needs to be run with the -i/--interactive/--repl flag" - ) - (false, stateCache) - } else { - if (useRepl && config.interactive.value) { - stderr.println( - "WARNING: Starting a build REPL without --repl is deprecated" - ) - } - val systemProps = - systemProperties ++ config.extraSystemProperties - - val threadCount = config.threadCountRaw match { - case None => Some(1) - case Some(0) => None - case Some(n) => Some(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, - noHomePredef = Flag() - ), - repl = ammonite.main.Config.Repl( - banner = "", - noRemoteLogging = Flag(), - classBased = Flag() - ) - ) + if (useRepl && config.interactive.value) { + stderr.println( + "WARNING: Starting a build REPL without --repl is deprecated" + ) + } + val systemProps = + systemProperties ++ config.extraSystemProperties - val runner = new mill.main.MainRunner( - config = ammConfig, - mainInteractive = mainInteractive, - disableTicker = config.disableTicker.value, - outprintStream = stdout, - errPrintStream = stderr, - stdIn = stdin, - stateCache0 = stateCache, - env = env, - setIdle = setIdle, - debugLog = config.debugLog.value, - keepGoing = config.keepGoing.value, - systemProperties = systemProps, - threadCount = threadCount, - ringBell = config.ringBell.value, - wd = os.pwd, - initialSystemProperties = initialSystemProperties - ) + val threadCount = config.threadCountRaw match { + case None => Some(1) + case Some(0) => None + case Some(n) => Some(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, + noHomePredef = Flag() + ), + repl = ammonite.main.Config.Repl( + banner = "", + noRemoteLogging = Flag(), + classBased = Flag() + ) + ) - if (mill.main.client.Util.isJava9OrAbove) { - val rt = config.ammoniteCore.home / Export.rtJarName - if (!os.exists(rt)) { - runner.printInfo( - s"Preparing Java ${System.getProperty("java.version")} runtime; this may take a minute or two ..." - ) - Export.rtTo(rt.toIO, false) - } - } + val runner = new mill.main.MainRunner( + config = ammConfig, + mainInteractive = mainInteractive, + disableTicker = config.disableTicker.value, + outprintStream = stdout, + errPrintStream = stderr, + stdIn = stdin, + stateCache0 = stateCache, + env = env, + setIdle = setIdle, + debugLog = config.debugLog.value, + keepGoing = config.keepGoing.value, + systemProperties = systemProps, + threadCount = threadCount, + ringBell = config.ringBell.value, + wd = os.pwd, + initialSystemProperties = initialSystemProperties + ) - if (useRepl) { - runner.printInfo("Loading...") - ( - runner.watchLoop(isRepl = true, printing = false, _.run()), - runner.stateCache - ) - } else { - ( - runner.runScript( - os.pwd / "build.sc", - config.leftoverArgs.value.toList - ), - runner.stateCache - ) - } - } - if (config.ringBell.value) { - if (success) println("\u0007") - else { - println("\u0007") - Thread.sleep(250) - println("\u0007") + if (mill.main.client.Util.isJava9OrAbove) { + val rt = config.ammoniteCore.home / Export.rtJarName + if (!os.exists(rt)) { + runner.printInfo( + s"Preparing Java ${System.getProperty("java.version")} runtime; this may take a minute or two ..." + ) + Export.rtTo(rt.toIO, false) } } - (success, nextStateCache) + + if (useRepl) { + runner.printInfo("Loading...") + ( + runner.watchLoop(isRepl = true, printing = false, _.run()), + runner.stateCache + ) + } else { + ( + runner.runScript( + os.pwd / "build.sc", + config.leftoverArgs.value.toList + ), + runner.stateCache + ) + } } - } + if (config.ringBell.value) { + if (success) println("\u0007") + else { + println("\u0007") + Thread.sleep(250) + println("\u0007") + } + } + (success, nextStateCache) + } } } diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index e907f0b69ea..e92ff409fe6 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -202,6 +202,7 @@ trait MainModule extends mill.Module { * Runs multiple tasks in a single call. * For compatibility reasons, the tasks are executed single-threaded. */ + @deprecated("Use the + separator, wildcards, or brace-expansion to specify multiple targets.", "mill after 0.10.0-M3") def all(evaluator: Evaluator, targets: String*) = mill.T.command { MainModule.evaluateTasks( evaluator = @@ -217,6 +218,7 @@ trait MainModule extends mill.Module { /** * Runs multiple tasks in a single call in parallel. */ + @deprecated("Use the + separator, wildcards, or brace-expansion to specify multiple targets.", "mill after 0.10.0-M3") def par(evaluator: Evaluator, targets: String*) = T.command { MainModule.evaluateTasks( evaluator = evaluator,