Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Print out CLI help message when inspecting commands #2522

Merged
merged 6 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions integration/feature/docannotations/repo/build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ object core extends JavaModule {
object test extends Tests with JUnitTests

/**
* Core Task Docz!
* Core Target Docz!
*/
def task = T {
def target = T {
import collection.JavaConverters._
println(this.getClass.getClassLoader.getResources("scalac-plugin.xml").asScala.toList)
"Hello!"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,102 @@ package mill.integration
import utest._

object DocAnnotationsTests extends IntegrationTestSuite {
def globMatches(glob: String, input: String) = {
StringContext.glob(glob.stripMargin.split("\\.\\.\\."), input).isDefined
}
val tests = Tests {
initWorkspace()
test("test") - {
val res = eval("inspect", "core.test.ivyDeps")
assert(res == true)

val inheritedIvyDeps = ujson.read(meta("inspect"))("value").str
assert(
inheritedIvyDeps.contains("core.test.ivyDeps"),
inheritedIvyDeps.contains("Overriden ivyDeps Docs!!!"),
inheritedIvyDeps.contains("Any ivy dependencies you want to add to this Module")
globMatches(
"""core.test.ivyDeps(build.sc:...)
Copy link
Member Author

@lihaoyi lihaoyi May 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are a bit fragile, but it's probably worth having at some some test where we assert on the overall shape of the inspect output rather than just looking for substrings. These docs and task inputs don't change very often anyway, so I expect the maintenance burden of keeping the tests up to date should be tolerable

| Overriden ivyDeps Docs!!!
|
| Any ivy dependencies you want to add to this Module, in the format
| ivy"org::name:version" for Scala dependencies or ivy"org:name:version"
| for Java dependencies
|
|Inputs:
|""".stripMargin,
inheritedIvyDeps
)
)

assert(eval("inspect", "core.task"))
val task = ujson.read(meta("inspect"))("value").str
assert(eval("inspect", "core.target"))
val target = ujson.read(meta("inspect"))("value").str
pprint.log(target)
assert(
task.contains("Core Task Docz!")
globMatches(
"""core.target(build.sc:...)
| Core Target Docz!
|
|Inputs:
|""",
target
)
)

assert(eval("inspect", "inspect"))
val doc = ujson.read(meta("inspect"))("value").str
assert(
doc.contains("Displays metadata about the given task without actually running it.")
globMatches(
"""inspect(MainModule.scala:...)
| Displays metadata about the given task without actually running it.
|
|Inputs:
|""".stripMargin,
doc
)
)

assert(eval("inspect", "core.run"))
val run = ujson.read(meta("inspect"))("value").str

assert(
globMatches(
"""core.run(JavaModule.scala:...)
| Runs this module's code in a subprocess and waits for it to finish
|
| args <str>...
|
|Inputs:
| core.finalMainClass
| core.runClasspath
| core.forkArgs
| core.forkEnv
| core.forkWorkingDir
| core.runUseArgsFile
|""",
run
)
)

assert(eval("inspect", "core.ivyDepsTree"))

val ivyDepsTree = ujson.read(meta("inspect"))("value").str
assert(
globMatches(
"""core.ivyDepsTree(JavaModule.scala:...)
| Command to print the transitive dependency tree to STDOUT.
|
| --inverse Invert the tree representation, so that the root is on the bottom val
| inverse (will be forced when used with whatDependsOn)
| --whatDependsOn <str> Possible list of modules (org:artifact) to target in the tree in order to
| see where a dependency stems from.
| --withCompile Include the compile-time only dependencies (`compileIvyDeps`, provided
| scope) into the tree.
| --withRuntime Include the runtime dependencies (`runIvyDeps`, runtime scope) into the
| tree.
|
|Inputs:
| core.transitiveIvyDeps
|""".stripMargin,
ivyDepsTree
)
)
}
}
Expand Down
27 changes: 11 additions & 16 deletions main/define/src/mill/define/Discover.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,17 @@ import scala.reflect.macros.blackbox
* the `T.command` methods we find. This mapping from `Class[_]` to `MainData`
* can then be used later to look up the `MainData` for any module.
*/
case class Discover[T] private (value: Map[Class[_], Seq[(Int, mainargs.MainData[_, _])]]) {
private[mill] def copy(value: Map[Class[_], Seq[(Int, mainargs.MainData[_, _])]] = value)
: Discover[T] =
case class Discover[T] private (value: Map[Class[_], Seq[mainargs.MainData[_, _]]]) {
private[mill] def copy(value: Map[Class[_], Seq[mainargs.MainData[_, _]]] = value): Discover[T] =
new Discover[T](value)
}
object Discover {
def apply[T](value: Map[Class[_], Seq[(Int, mainargs.MainData[_, _])]]): Discover[T] =
def apply[T](value: Map[Class[_], Seq[mainargs.MainData[_, _]]]): Discover[T] =
new Discover[T](value)
def apply[T]: Discover[T] = macro Router.applyImpl[T]

private def unapply[T](discover: Discover[T])
: Option[Map[Class[_], Seq[(Int, mainargs.MainData[_, _])]]] = Some(discover.value)
: Option[Map[Class[_], Seq[mainargs.MainData[_, _]]]] = Some(discover.value)

private class Router(val ctx: blackbox.Context) extends mainargs.Macros(ctx) {
import c.universe._
Expand Down Expand Up @@ -90,18 +89,14 @@ object Discover {
for {
m <- methods.toList
if m.returnType <:< weakTypeOf[mill.define.Command[_]]
} yield (
m.overrides.length,
extractMethod(
m.name,
m.paramLists.flatten,
m.pos,
m.annotations.find(_.tree.tpe =:= typeOf[mainargs.main]),
curCls,
weakTypeOf[Any]
)
} yield extractMethod(
m.name,
m.paramLists.flatten,
m.pos,
m.annotations.find(_.tree.tpe =:= typeOf[mainargs.main]),
curCls,
weakTypeOf[Any]
)

}
if overridesRoutes.nonEmpty
} yield {
Expand Down
9 changes: 4 additions & 5 deletions main/resolve/src/mill/resolve/Resolve.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ object Resolve {
(cls, entryPoints) <- discover.value
if cls.isAssignableFrom(target.getClass)
ep <- entryPoints
if ep._2.name == name
if ep.name == name
} yield {
def withNullDefault(a: mainargs.ArgSig): mainargs.ArgSig = {
if (a.default.nonEmpty) a
Expand All @@ -133,7 +133,6 @@ object Resolve {
}

val flattenedArgSigsWithDefaults = ep
._2
.flattenedArgSigs
.map { case (arg, term) => (withNullDefault(arg), term) }

Expand All @@ -142,9 +141,9 @@ object Resolve {
flattenedArgSigsWithDefaults,
allowPositional = true,
allowRepeats = false,
allowLeftover = ep._2.argSigs0.exists(_.reader.isLeftover)
allowLeftover = ep.argSigs0.exists(_.reader.isLeftover)
).flatMap { (grouped: TokenGrouping[_]) =>
val mainData = ep._2.asInstanceOf[MainData[_, Any]]
val mainData = ep.asInstanceOf[MainData[_, Any]]
val mainDataWithDefaults = mainData
.copy(argSigs0 = mainData.argSigs0.map(withNullDefault))

Expand All @@ -159,7 +158,7 @@ object Resolve {
case f: mainargs.Result.Failure =>
Left(
mainargs.Renderer.renderResult(
ep._2,
ep,
f,
totalWidth = 100,
printHelpOnError = true,
Expand Down
64 changes: 50 additions & 14 deletions main/src/mill/main/MainModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ package mill.main

import java.util.concurrent.LinkedBlockingQueue
import mill.{BuildInfo, T}
import mill.api.{Ctx, Logger, PathRef, Result, internal}
import mill.define.{Command, NamedTask, Segments, TargetImpl, Task}
import mill.api.{Ctx, Logger, PathRef, Result}
import mill.define.{Command, NamedTask, Segments, Task}
import mill.eval.{Evaluator, EvaluatorPaths}
import mill.resolve.{Resolve, SelectMode}
import mill.resolve.SelectMode.Separated
import mill.util.{PrintLogger, Watchable}
import pprint.{Renderer, Tree, Truncated}
import ujson.Value

import scala.collection.mutable
import scala.util.chaining.scalaUtilChainingOps

object MainModule {

Expand Down Expand Up @@ -234,7 +232,42 @@ trait MainModule extends mill.Module {
for (a <- annots.distinct)
yield mill.util.Util.cleanupScaladoc(a.value).map("\n " + _).mkString

pprint.Tree.Lazy(ctx =>
pprint.Tree.Lazy { ctx =>
val mainMethodSig =
if (t.asCommand.isEmpty) List()
else {
val mainDataOpt = evaluator
.rootModule
.millDiscover
.value
.get(t.ctx.enclosingCls)
.flatMap(_.find(_.name == t.ctx.segments.parts.last))

mainDataOpt match {
case Some(mainData) if mainData.renderedArgSigs.nonEmpty =>
val rendered = mainargs.Renderer.formatMainMethodSignature(
mainDataOpt.get,
leftIndent = 2,
totalWidth = 100,
leftColWidth = mainargs.Renderer.getLeftColWidth(mainData.renderedArgSigs),
docsOnNewLine = false,
customName = None,
customDoc = None
)

// trim first line containing command name, since we already render
// the command name below with the filename and line num
val trimmedRendered = rendered
.linesIterator
.drop(1)
.mkString("\n")

List("\n", trimmedRendered, "\n")

case _ => List()
}
}

Iterator(
ctx.applyPrefixColor(t.toString).toString,
"(",
Expand All @@ -244,12 +277,15 @@ trait MainModule extends mill.Module {
t.ctx.lineNum.toString,
")",
allDocs.mkString("\n"),
"\n",
"\n",
ctx.applyPrefixColor("Inputs").toString,
":"
) ++ t.inputs.distinct.iterator.flatMap(rec).map("\n " + _.render)
)
"\n"
) ++
mainMethodSig.iterator ++
Iterator(
"\n",
ctx.applyPrefixColor("Inputs").toString,
":"
) ++ t.inputs.distinct.iterator.flatMap(rec).map("\n " + _.render)
}
}

MainModule.resolveTasks(evaluator, targets, SelectMode.Multi) { tasks =>
Expand All @@ -266,9 +302,9 @@ trait MainModule extends mill.Module {
rendered = renderer.rec(tree, 0, 0).iter
truncated = new Truncated(rendered, defaults.defaultWidth, defaults.defaultHeight)
} yield {
new StringBuilder().tap { sb =>
for { str <- truncated ++ Iterator("\n") } sb.append(str)
}.toString()
val sb = new StringBuilder()
for { str <- truncated ++ Iterator("\n") } sb.append(str)
sb.toString()
}).mkString("\n")
T.log.outputStream.println(output)
fansi.Str(output).plainText
Expand Down