From d551ee9d751201491a1d64e4da76ab03e3815df6 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 20 Sep 2018 14:15:50 +0800 Subject: Include scaladoc as part of mill inspect (#435) --- .appveyor.yml | 2 +- .travis.yml | 2 +- build.sc | 8 +- ci/test-mill-dev.sh | 4 +- integration/test/resources/docannotations/build.sc | 30 ++++ .../src/mill/integration/DocAnnotationsTests.scala | 34 +++++ .../test/src/mill/integration/forked/Tests.scala | 1 + .../test/src/mill/integration/local/Tests.scala | 1 + main/core/src/mill/define/BaseModule.scala | 10 +- main/core/src/mill/define/Caller.scala | 13 ++ main/core/src/mill/define/Ctx.scala | 9 +- main/core/src/mill/define/Task.scala | 9 -- main/core/src/mill/util/ClassLoader.scala | 36 +++-- .../src/mill/moduledefs/AutoOverridePlugin.scala | 114 +++++++++++++++ main/moduledefs/src/mill/moduledefs/Scaladoc.java | 11 ++ main/src/mill/main/MainModule.scala | 13 +- main/src/mill/main/MainRunner.scala | 4 +- main/src/mill/main/ReplApplyHandler.scala | 24 ++- main/src/mill/main/Resolve.scala | 1 + main/src/mill/main/VisualizeModule.scala | 1 - main/src/mill/modules/Util.scala | 13 ++ main/test/src/mill/util/TestUtil.scala | 3 +- scalalib/src/mill/scalalib/JavaModule.scala | 161 ++++++++++++++++++++- scalalib/src/mill/scalalib/ScalaModule.scala | 49 ++++++- .../src/mill/scalalib/worker/ZincWorkerImpl.scala | 18 ++- scratch/build.sc | 27 +++- scratch/scalapb/protobuf/scratch.proto | 29 ---- 27 files changed, 535 insertions(+), 92 deletions(-) create mode 100644 integration/test/resources/docannotations/build.sc create mode 100644 integration/test/src/mill/integration/DocAnnotationsTests.scala create mode 100644 main/core/src/mill/define/Caller.scala create mode 100644 main/moduledefs/src/mill/moduledefs/Scaladoc.java delete mode 100644 scratch/scalapb/protobuf/scratch.proto diff --git a/.appveyor.yml b/.appveyor.yml index ce4ba80a..86234df4 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -21,7 +21,7 @@ environment: JAVA_HOME: C:\Program Files\Java\jdk10 install: - - SET MILL_URL=https://github.com/lihaoyi/mill/releases/download/0.2.0/0.2.0-36-f8d1e7 + - SET MILL_URL=https://github.com/lihaoyi/mill/releases/download/0.2.7/0.2.7 build_script: - if [%COMPILER%]==[default] ( diff --git a/.travis.yml b/.travis.yml index b9ed5217..d6e954a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ matrix: jdk: oraclejdk8 script: - - curl -L -o ~/bin/mill https://github.com/lihaoyi/mill/releases/download/0.2.0/0.2.0-36-f8d1e7 && chmod +x ~/bin/mill + - curl -L -o ~/bin/mill https://github.com/lihaoyi/mill/releases/download/0.2.7/0.2.7 && chmod +x ~/bin/mill - export PATH=~/bin/mill:$PATH - "$CI_SCRIPT" diff --git a/build.sc b/build.sc index 932af6fe..a31e2b62 100755 --- a/build.sc +++ b/build.sc @@ -33,7 +33,6 @@ trait MillModule extends MillPublishModule with ScalaModule{ outer => def compileIvyDeps = Agg(ivy"com.lihaoyi::acyclic:0.1.7") def scalacOptions = Seq("-P:acyclic:force") def scalacPluginIvyDeps = Agg(ivy"com.lihaoyi::acyclic:0.1.7") - def repositories = super.repositories ++ Seq( MavenRepository("https://oss.sonatype.org/content/repositories/releases") ) @@ -51,7 +50,8 @@ trait MillModule extends MillPublishModule with ScalaModule{ outer => else Seq(outer, main.test) def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.4") def testFrameworks = Seq("mill.UTestFramework") - def scalacPluginClasspath = super.scalacPluginClasspath() ++ Seq(main.moduledefs.jar()) + def scalacPluginClasspath = + super.scalacPluginClasspath() ++ Seq(main.moduledefs.jar()) } } @@ -101,11 +101,10 @@ object main extends MillModule { def scalaVersion = T{ "2.12.6" } def ivyDeps = Agg( ivy"org.scala-lang:scala-compiler:${scalaVersion()}", - ivy"com.lihaoyi::sourcecode:0.1.4" + ivy"com.lihaoyi::sourcecode:0.1.4", ) } - object client extends MillPublishModule{ def ivyDeps = Agg( ivy"org.scala-sbt.ipcsocket:ipcsocket:1.0.0".exclude( @@ -362,6 +361,7 @@ def launcherScript(shellJvmArgs: Seq[String], object dev extends MillModule{ def moduleDeps = Seq(scalalib, scalajslib, scalanativelib, contrib.scalapblib) + def forkArgs = ( scalalib.testArgs() ++ diff --git a/ci/test-mill-dev.sh b/ci/test-mill-dev.sh index ab4a4d19..1a5c5243 100755 --- a/ci/test-mill-dev.sh +++ b/ci/test-mill-dev.sh @@ -11,5 +11,7 @@ mill -i dev.assembly rm -rf ~/.mill # Second build & run tests -out/dev/assembly/dest/mill -i all {main,scalalib,scalajslib,contrib.twirllib,contrib.scalapblib}.test +out/dev/assembly/dest/mill -i main.test.compile + +#out/dev/assembly/dest/mill -i all {main,scalalib,scalajslib,contrib.twirllib,contrib.scalapblib}.test diff --git a/integration/test/resources/docannotations/build.sc b/integration/test/resources/docannotations/build.sc new file mode 100644 index 00000000..aa3fc113 --- /dev/null +++ b/integration/test/resources/docannotations/build.sc @@ -0,0 +1,30 @@ +import mill.Agg +import mill.scalalib._ + +trait JUnitTests extends TestModule{ + def testFrameworks = Seq("com.novocode.junit.JUnitFramework") + + /** + * Overriden ivyDeps Docs!!! + */ + def ivyDeps = Agg(ivy"com.novocode:junit-interface:0.11") + def task = T{ + "???" + } +} + +/** + * The Core Module Docz! + */ +object core extends JavaModule{ + object test extends Tests with JUnitTests + + /** + * Core Task Docz! + */ + def task = T{ + import collection.JavaConverters._ + println(this.getClass.getClassLoader.getResources("scalac-plugin.xml").asScala.toList) + "Hello!" + } +} diff --git a/integration/test/src/mill/integration/DocAnnotationsTests.scala b/integration/test/src/mill/integration/DocAnnotationsTests.scala new file mode 100644 index 00000000..75579689 --- /dev/null +++ b/integration/test/src/mill/integration/DocAnnotationsTests.scala @@ -0,0 +1,34 @@ +package mill.integration + +import ammonite.ops.{Path, pwd} +import mill.util.ScriptTestSuite +import utest._ + +class DocAnnotationsTests(fork: Boolean) extends ScriptTestSuite(fork) { + def workspaceSlug: String = "docannotations" + def scriptSourcePath: Path = pwd / 'integration / 'test / 'resources / workspaceSlug + val tests = Tests{ + initWorkspace() + 'test - { + assert(eval("inspect", "core.test.ivyDeps")) + 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"), + ) + + assert(eval("inspect", "core.task")) + val task = ujson.read(meta("inspect"))("value").str + assert( + task.contains("Core Task Docz!") + ) + + 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.") + ) + } + } +} diff --git a/integration/test/src/mill/integration/forked/Tests.scala b/integration/test/src/mill/integration/forked/Tests.scala index 38c739ab..41844b58 100644 --- a/integration/test/src/mill/integration/forked/Tests.scala +++ b/integration/test/src/mill/integration/forked/Tests.scala @@ -7,3 +7,4 @@ object JawnTests extends mill.integration.JawnTests(fork = true) object UpickleTests extends mill.integration.UpickleTests(fork = true) object PlayJsonTests extends mill.integration.PlayJsonTests(fork = true) object CaffeineTests extends mill.integration.CaffeineTests(fork = true) +object DocAnnotationsTests extends mill.integration.DocAnnotationsTests(fork = true) diff --git a/integration/test/src/mill/integration/local/Tests.scala b/integration/test/src/mill/integration/local/Tests.scala index c1eba757..e95aac54 100644 --- a/integration/test/src/mill/integration/local/Tests.scala +++ b/integration/test/src/mill/integration/local/Tests.scala @@ -7,3 +7,4 @@ object JawnTests extends mill.integration.JawnTests(fork = false) object UpickleTests extends mill.integration.UpickleTests(fork = false) object PlayJsonTests extends mill.integration.PlayJsonTests(fork = false) object CaffeineTests extends mill.integration.CaffeineTests(fork = false) +object DocAnnotationsTests extends mill.integration.DocAnnotationsTests(fork = false) \ No newline at end of file diff --git a/main/core/src/mill/define/BaseModule.scala b/main/core/src/mill/define/BaseModule.scala index 70826be7..03bdeccc 100644 --- a/main/core/src/mill/define/BaseModule.scala +++ b/main/core/src/mill/define/BaseModule.scala @@ -12,7 +12,8 @@ abstract class BaseModule(millSourcePath0: Path, (implicit millModuleEnclosing0: sourcecode.Enclosing, millModuleLine0: sourcecode.Line, millName0: sourcecode.Name, - millFile0: sourcecode.File) + millFile0: sourcecode.File, + caller: Caller) extends Module()( mill.define.Ctx.make( implicitly, @@ -23,7 +24,8 @@ abstract class BaseModule(millSourcePath0: Path, mill.util.Router.Overrides(0), Ctx.External(external0), Ctx.Foreign(foreign0), - millFile0 + millFile0, + caller ) ){ // A BaseModule should provide an empty Segments list to it's children, since @@ -40,7 +42,9 @@ abstract class BaseModule(millSourcePath0: Path, abstract class ExternalModule(implicit millModuleEnclosing0: sourcecode.Enclosing, millModuleLine0: sourcecode.Line, millName0: sourcecode.Name) - extends BaseModule(ammonite.ops.pwd, external0 = true, foreign0 = false){ + extends BaseModule(ammonite.ops.pwd, external0 = true, foreign0 = false)( + implicitly, implicitly, implicitly, implicitly, Caller(()) + ){ implicit def millDiscoverImplicit: Discover[_] = millDiscover assert( diff --git a/main/core/src/mill/define/Caller.scala b/main/core/src/mill/define/Caller.scala new file mode 100644 index 00000000..6d2d4d1d --- /dev/null +++ b/main/core/src/mill/define/Caller.scala @@ -0,0 +1,13 @@ +package mill.define + +import sourcecode.Compat.Context +import language.experimental.macros +case class Caller(value: Any) +object Caller { + def apply()(implicit c: Caller) = c.value + implicit def generate: Caller = macro impl + def impl(c: Context): c.Tree = { + import c.universe._ + q"new _root_.mill.define.Caller(this)" + } +} \ No newline at end of file diff --git a/main/core/src/mill/define/Ctx.scala b/main/core/src/mill/define/Ctx.scala index fb15dc19..ba4fae9e 100644 --- a/main/core/src/mill/define/Ctx.scala +++ b/main/core/src/mill/define/Ctx.scala @@ -69,7 +69,8 @@ case class Ctx(enclosing: String, overrides: Int, external: Boolean, foreign: Boolean, - fileName: String){ + fileName: String, + enclosingCls: Class[_]){ } object Ctx{ @@ -83,7 +84,8 @@ object Ctx{ overrides0: mill.util.Router.Overrides, external0: External, foreign0: Foreign, - fileName: sourcecode.File): Ctx = { + fileName: sourcecode.File, + enclosing: Caller): Ctx = { Ctx( millModuleEnclosing0.value, millModuleLine0.value, @@ -93,7 +95,8 @@ object Ctx{ overrides0.value, external0.value, foreign0.value, - fileName.value + fileName.value, + enclosing.value.getClass ) } } diff --git a/main/core/src/mill/define/Task.scala b/main/core/src/mill/define/Task.scala index d5f8680e..64e868f8 100644 --- a/main/core/src/mill/define/Task.scala +++ b/main/core/src/mill/define/Task.scala @@ -254,15 +254,6 @@ object Target extends TargetGenerated with Applicative.Applyer[Task, Task, Resul def zip[A, B](a: Task[A], b: Task[B]) = a.zip(b) } -case class Caller[A](value: A) -object Caller { - def apply[T]()(implicit c: Caller[T]) = c.value - implicit def generate[T]: Caller[T] = macro impl[T] - def impl[T: c.WeakTypeTag](c: Context): c.Tree = { - import c.universe._ - q"new _root_.mill.define.Caller[${weakTypeOf[T]}](this)" - } -} abstract class NamedTaskImpl[+T](ctx0: mill.define.Ctx, t: Task[T]) extends NamedTask[T]{ def evaluate(args: mill.util.Ctx) = args[T](0) val ctx = ctx0.copy(segments = ctx0.segments ++ Seq(ctx0.segment)) diff --git a/main/core/src/mill/util/ClassLoader.scala b/main/core/src/mill/util/ClassLoader.scala index 17268fb0..ebe8e50b 100644 --- a/main/core/src/mill/util/ClassLoader.scala +++ b/main/core/src/mill/util/ClassLoader.scala @@ -5,15 +5,18 @@ import java.net.{URL, URLClassLoader} import ammonite.ops._ import io.github.retronym.java9rtexport.Export -object ClassLoader { - - def create(urls: Seq[URL], parent: java.lang.ClassLoader)( - implicit ctx: Ctx.Home): URLClassLoader = create(urls, parent, _ => None) +import scala.util.Try +object ClassLoader { + def create(urls: Seq[URL], + parent: java.lang.ClassLoader) + (implicit ctx: Ctx.Home): URLClassLoader = { + create(urls, parent, _ => None) + } def create(urls: Seq[URL], parent: java.lang.ClassLoader, - customFindClass: String => Option[Class[_]])( - implicit ctx: Ctx.Home): URLClassLoader = { + customFindClass: String => Option[Class[_]]) + (implicit ctx: Ctx.Home): URLClassLoader = { new URLClassLoader( makeUrls(urls).toArray, refinePlatformParent(parent) @@ -25,6 +28,7 @@ object ClassLoader { } } + /** * Return `ClassLoader.getPlatformClassLoader` for java 9 and above, if parent class loader is null, * otherwise return same parent class loader. @@ -34,15 +38,17 @@ object ClassLoader { * mill could be compiled only with jdk 9 or above. We don't want to introduce this restriction now. */ private def refinePlatformParent(parent: java.lang.ClassLoader): ClassLoader = { - if (ammonite.util.Util.java9OrAbove) { - if (parent == null) - classOf[ClassLoader] - .getMethod("getPlatformClassLoader") - .invoke(null) - .asInstanceOf[ClassLoader] - else parent - } else { - parent + if (!ammonite.util.Util.java9OrAbove || parent != null) parent + else { + // Make sure when `parent == null`, we only delegate java.* classes + // to the parent getPlatformClassLoader. This is necessary because + // in Java 9+, somehow the getPlatformClassLoader ends up with all + // sorts of other non-java stuff on it's classpath, which is not what + // we want for an "isolated" classloader! + classOf[ClassLoader] + .getMethod("getPlatformClassLoader") + .invoke(null) + .asInstanceOf[ClassLoader] } } diff --git a/main/moduledefs/src/mill/moduledefs/AutoOverridePlugin.scala b/main/moduledefs/src/mill/moduledefs/AutoOverridePlugin.scala index 5b33abbe..a870e7ec 100644 --- a/main/moduledefs/src/mill/moduledefs/AutoOverridePlugin.scala +++ b/main/moduledefs/src/mill/moduledefs/AutoOverridePlugin.scala @@ -1,10 +1,14 @@ package mill.moduledefs + +import scala.collection.mutable.ListBuffer import scala.reflect.internal.Flags +import scala.tools.nsc.doc.ScaladocSyntaxAnalyzer import scala.tools.nsc.io.VirtualFile import scala.tools.nsc.util.BatchSourceFile import scala.tools.nsc.{Global, Phase} import scala.tools.nsc.plugins.{Plugin, PluginComponent} +import scala.tools.nsc.transform.Transform class AutoOverridePlugin(val global: Global) extends Plugin { import global._ @@ -12,7 +16,117 @@ class AutoOverridePlugin(val global: Global) extends Plugin { val name = "auto-override-plugin" val description = "automatically inserts `override` keywords for you" + val components = List[PluginComponent]( + new PluginComponent with Transform { + type GT = AutoOverridePlugin.this.global.type + override val global: GT = AutoOverridePlugin.this.global + override val phaseName: String = "EmbedScaladocAnnotation" + override val runsAfter: List[String] = List("parser") + override def newTransformer(unit: global.CompilationUnit): global.Transformer = { + new ScaladocTransformer + } + import global._ + + + class ScaladocTransformer extends global.Transformer { + + val comments = new Comments() + + override def transformUnit(unit: CompilationUnit)= { + if (unit.source.file.name.endsWith(".scala") || + unit.source.file.name.endsWith(".sc")){ + comments.parseComments(unit) + super.transformUnit(unit) + } + } + + override def transform(tree: global.Tree): global.Tree = { + super.transform(tree match { + case x: global.ClassDef => + comments.getComment(x.pos) match { + case Some(comment) => + global.treeCopy.ClassDef(tree, newMods(x.mods, comment), x.name, x.tparams, x.impl) + case None => x + } + + case x: global.ModuleDef => + comments.getComment(x.pos) match { + case Some(comment) => + global.treeCopy.ModuleDef(tree, newMods(x.mods, comment), x.name, x.impl) + case None => x + } + + case x: global.DefDef => + comments.getComment(x.pos) match { + case Some(comment) => + global.treeCopy.DefDef(tree, newMods(x.mods, comment), x.name, x.tparams, x.vparamss, x.tpt, x.rhs) + case None => x + } + + case x: global.ValDef => + comments.getComment(x.pos) match { + case Some(comment) => + global.treeCopy.ValDef(tree, newMods(x.mods, comment), x.name, x.tpt, x.rhs) + case None => x + } + + case x => x + }) + } + + def newMods(old: global.Modifiers, comment: String) = { + old.copy( + annotations = createAnnotation(comment) :: old.annotations + ) + } + + private def createAnnotation(comment: String): global.Tree = + global.Apply( + global.Select( + global.New( + global.Select( + global.Select( + global.Ident( + global.newTermName("mill") + ), + global.newTermName("moduledefs") + ), + global.newTypeName("Scaladoc") + ) + ), + global.nme.CONSTRUCTOR + ), + List(Literal(Constant(comment))) + ) + + } + + class Comments extends ScaladocSyntaxAnalyzer[global.type](global){ + val comments = ListBuffer[(Position, String)]() + + def getComment(pos: Position): Option[String] = { + val tookComments = comments.takeWhile { case (x, _) => x.end < pos.start } + comments --= (tookComments) + tookComments.lastOption.map(_._2) + } + + def parseComments(unit: CompilationUnit): Unit = { + comments.clear() + + new ScaladocUnitParser(unit, Nil) { + override def newScanner = new ScaladocUnitScanner(unit, Nil) { + override def registerDocComment(str: String, pos: Position) = { + comments += ((pos, str)) + } + } + }.parse() + } + + override val runsAfter: List[String] = Nil + override val runsRightAfter: Option[String] = None + } + }, new PluginComponent { val global = AutoOverridePlugin.this.global diff --git a/main/moduledefs/src/mill/moduledefs/Scaladoc.java b/main/moduledefs/src/mill/moduledefs/Scaladoc.java new file mode 100644 index 00000000..7a7d700b --- /dev/null +++ b/main/moduledefs/src/mill/moduledefs/Scaladoc.java @@ -0,0 +1,11 @@ +package mill.moduledefs; + + +import java.lang.annotation.*; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Scaladoc { + String value(); +} diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index 834d5609..7e326860 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -135,6 +135,7 @@ trait MainModule extends mill.Module{ */ def inspect(evaluator: Evaluator, targets: String*) = mill.T.command{ MainModule.resolveTasks(evaluator, targets, multiSelect = true){ tasks => + val output = new StringBuilder for{ task <- tasks tree = ReplApplyHandler.pprintTask(task, evaluator) @@ -149,8 +150,10 @@ trait MainModule extends mill.Module{ val truncated = new Truncated(rendered, defaults.defaultWidth, defaults.defaultHeight) str <- truncated ++ Iterator("\n") } { - print(str) + output.append(str) } + println(output) + output.toString } } @@ -222,10 +225,17 @@ trait MainModule extends mill.Module{ } } + + /** + * Renders the dependencies between the given tasks as a SVG for you to look at + */ def visualize(evaluator: Evaluator, targets: String*) = mill.T.command{ visualize0(evaluator, targets, T.ctx(), mill.main.VisualizeModule.worker()) } + /** + * Renders the dependencies between the given tasks, and all their dependencies, as a SVG + */ def visualizePlan(evaluator: Evaluator, targets: String*) = mill.T.command{ plan0(evaluator, targets) match { case Left(err) => Result.Failure(err) @@ -258,5 +268,4 @@ trait MainModule extends mill.Module{ } } } - } diff --git a/main/src/mill/main/MainRunner.scala b/main/src/mill/main/MainRunner.scala index 30a23632..58c47998 100644 --- a/main/src/mill/main/MainRunner.scala +++ b/main/src/mill/main/MainRunner.scala @@ -144,7 +144,9 @@ class MainRunner(val config: ammonite.main.Cli.Config, |$imports |import mill._ |object $wrapName - |extends mill.define.BaseModule(ammonite.ops.Path($literalPath), foreign0 = $external) + |extends mill.define.BaseModule(ammonite.ops.Path($literalPath), foreign0 = $external)( + | implicitly, implicitly, implicitly, implicitly, mill.define.Caller(()) + |) |with $wrapName{ | // Stub to make sure Ammonite has something to call after it evaluates a script, | // even if it does nothing... diff --git a/main/src/mill/main/ReplApplyHandler.scala b/main/src/mill/main/ReplApplyHandler.scala index c40b82c9..59a6780b 100644 --- a/main/src/mill/main/ReplApplyHandler.scala +++ b/main/src/mill/main/ReplApplyHandler.scala @@ -6,6 +6,7 @@ import mill.define.Applicative.ApplyHandler import mill.define.Segment.Label import mill.define._ import mill.eval.{Evaluator, Result} + import mill.util.Strict.Agg import scala.collection.mutable @@ -69,6 +70,11 @@ object ReplApplyHandler{ ) } + + def resolveParents(c: Class[_]): Seq[Class[_]] = { + Seq(c) ++ Option(c.getSuperclass).toSeq.flatMap(resolveParents) ++ c.getInterfaces.flatMap(resolveParents) + } + def pprintTask(t: NamedTask[_], evaluator: Evaluator) = { val seen = mutable.Set.empty[Task[_]] def rec(t: Task[_]): Seq[Segments] = { @@ -81,10 +87,24 @@ object ReplApplyHandler{ t.inputs.flatMap(rec) } } + + val annots = for { + c <- resolveParents(t.ctx.enclosingCls) + m <- c.getMethods + if m.getName == t.ctx.segment.pathSegments.head + a = m.getAnnotation(classOf[mill.moduledefs.Scaladoc]) + if a != null + }yield a + + val allDocs = + for(a <- annots.distinct) + yield mill.modules.Util.cleanupScaladoc(a.value).map("\n " + _).mkString + pprint.Tree.Lazy(ctx => Iterator( - t.toString, "(", t.ctx.fileName.split('/').last, ":", t.ctx.lineNum.toString, ")", - "\n", ctx.applyPrefixColor("Inputs:").toString + ctx.applyPrefixColor(t.toString).toString, "(", t.ctx.fileName.split('/').last, ":", t.ctx.lineNum.toString, ")", + allDocs.mkString("\n"), "\n", + "\n", ctx.applyPrefixColor("Inputs").toString, ":" ) ++ t.inputs.iterator.flatMap(rec).map("\n " + _.render) ) } diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala index d0a08c87..a2c186ed 100644 --- a/main/src/mill/main/Resolve.scala +++ b/main/src/mill/main/Resolve.scala @@ -219,6 +219,7 @@ object ResolveTasks extends Resolve[NamedTask[Any]]{ } } } + object Resolve{ def minimum(i1: Int, i2: Int, i3: Int)= math.min(math.min(i1, i2), i3) diff --git a/main/src/mill/main/VisualizeModule.scala b/main/src/mill/main/VisualizeModule.scala index 6945cf2f..ea4d46c6 100644 --- a/main/src/mill/main/VisualizeModule.scala +++ b/main/src/mill/main/VisualizeModule.scala @@ -10,7 +10,6 @@ import mill.T import mill.define.{Discover, ExternalModule} import mill.eval.{PathRef, Result} - object VisualizeModule extends ExternalModule with VisualizeModule { def repositories = Seq( Cache.ivy2Local, diff --git a/main/src/mill/modules/Util.scala b/main/src/mill/modules/Util.scala index 2d71c13e..38cd056b 100644 --- a/main/src/mill/modules/Util.scala +++ b/main/src/mill/modules/Util.scala @@ -7,6 +7,19 @@ import mill.eval.PathRef import mill.util.{Ctx, IO, Loose} object Util { + def cleanupScaladoc(v: String) = { + v.lines.map( + _.dropWhile(_.isWhitespace) + .stripPrefix("/**") + .stripPrefix("*/") + .stripPrefix("*") + .dropWhile(_.isWhitespace) + ).toArray + .dropWhile(_.isEmpty) + .reverse + .dropWhile(_.isEmpty) + .reverse + } def download(url: String, dest: RelPath = "download")(implicit ctx: Ctx.Dest) = { val out = ctx.dest / dest diff --git a/main/test/src/mill/util/TestUtil.scala b/main/test/src/mill/util/TestUtil.scala index dede8bcf..b4e3af89 100644 --- a/main/test/src/mill/util/TestUtil.scala +++ b/main/test/src/mill/util/TestUtil.scala @@ -31,7 +31,8 @@ object TestUtil { millModuleLine0: sourcecode.Line, millName0: sourcecode.Name, overrides: Overrides) - extends mill.define.BaseModule(getSrcPathBase() / millModuleEnclosing0.value.split("\\.| |#")){ + extends mill.define.BaseModule(getSrcPathBase() / millModuleEnclosing0.value.split("\\.| |#"))( + implicitly, implicitly, implicitly, implicitly, implicitly){ lazy val millDiscover: Discover[this.type] = Discover[this.type] } diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala index fc01dceb..b15e0bdb 100644 --- a/scalalib/src/mill/scalalib/JavaModule.scala +++ b/scalalib/src/mill/scalalib/JavaModule.scala @@ -34,6 +34,11 @@ trait JavaModule extends mill.Module with TaskModule { outer => Lib.depToDependencyJava(_: Dep) } + /** + * Allows you to specify an explicit main class to use for the `run` command. + * If none is specified, the classpath is searched for an appropriate main + * class to use if one exists + */ def mainClass: T[Option[String]] = None def finalMainClassOpt: T[Either[String, String]] = T{ @@ -59,10 +64,29 @@ trait JavaModule extends mill.Module with TaskModule { outer => } } + /** + * 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 + */ def ivyDeps = T{ Agg.empty[Dep] } + + /** + * Same as `ivyDeps`, but only present at compile time. Useful for e.g. + * macro-related dependencies like `scala-reflect` that doesn't need to be + * present at runtime + */ def compileIvyDeps = T{ Agg.empty[Dep] } + /** + * Same as `ivyDeps`, but only present at runtime. Useful for e.g. + * selecting different versions of a dependency to use at runtime after your + * code has already been compiled + */ def runIvyDeps = T{ Agg.empty[Dep] } + /** + * Options to pass to the java compiler + */ def javacOptions = T{ Seq.empty[String] } /** The direct dependencies of this module */ @@ -78,17 +102,30 @@ trait JavaModule extends mill.Module with TaskModule { outer => Seq(this) ++ recursiveModuleDeps } + /** + * Additional jars, classfiles or resources to add to the classpath directly + * from disk rather than being downloaded from Maven Central or other package + * repositories + */ def unmanagedClasspath = T{ Agg.empty[PathRef] } - + /** + * The transitive ivy dependencies of this module and all it's upstream modules + */ def transitiveIvyDeps: T[Agg[Dep]] = T{ ivyDeps() ++ Task.traverse(moduleDeps)(_.transitiveIvyDeps)().flatten } + /** + * The upstream compilation output of all this module's upstream modules + */ def upstreamCompileOutput = T{ Task.traverse(recursiveModuleDeps)(_.compile) } + /** + * The transitive version of `localClasspath` + */ def transitiveLocalClasspath: T[Agg[PathRef]] = T{ Task.traverse(moduleDeps)(m => T.task{m.localClasspath() ++ m.transitiveLocalClasspath()} @@ -110,10 +147,19 @@ trait JavaModule extends mill.Module with TaskModule { outer => def repositories: Seq[Repository] = zincWorker.repositories + /** + * What platform suffix to use for publishing, e.g. `_sjs` for Scala.js + * projects + */ def platformSuffix = T{ "" } private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r + /** + * What shell script to use to launch the executable generated by `assembly`. + * Defaults to a generic "universal" launcher that should work for Windows, + * OS-X and Linux + */ def prependShellScript: T[String] = T{ mainClass() match{ case None => "" @@ -129,11 +175,29 @@ trait JavaModule extends mill.Module with TaskModule { outer => def assemblyRules: Seq[Assembly.Rule] = Assembly.defaultRules + /** + * The folders where the source files for this module live + */ def sources = T.sources{ millSourcePath / 'src } + /** + * The folders where the resource files for this module live + */ def resources = T.sources{ millSourcePath / 'resources } + /** + * Folders containing source files that are generated rather than + * hand-written; these files can be generated in this target itself, + * or can refer to files generated from other targets + */ def generatedSources = T{ Seq.empty[PathRef] } + + /** + * The folders containing all source files fed into the compiler + */ def allSources = T{ sources() ++ generatedSources() } + /** + * All individual source files fed into the compiler + */ def allSourceFiles = T{ def isHiddenFile(path: Path) = path.segments.last.startsWith(".") for { @@ -144,6 +208,9 @@ trait JavaModule extends mill.Module with TaskModule { outer => } yield PathRef(path) } + /** + * Compiles the current module to generate compiled classfiles/bytecode + */ def compile: T[CompilationResult] = T.persistent{ zincWorker.worker().compileJava( upstreamCompileOutput(), @@ -153,9 +220,18 @@ trait JavaModule extends mill.Module with TaskModule { outer => ) } + /** + * The output classfiles/resources from this module, excluding upstream + * modules and third-party dependencies + */ def localClasspath = T{ resources() ++ Agg(compile().classes) } + + /** + * All classfiles and resources from upstream modules and dependencies + * necessary to compile this module + */ def compileClasspath = T{ transitiveLocalClasspath() ++ resources() ++ @@ -163,19 +239,28 @@ trait JavaModule extends mill.Module with TaskModule { outer => resolveDeps(T.task{compileIvyDeps() ++ transitiveIvyDeps()})() } + /** + * All upstream classfiles and resources necessary to build and executable + * assembly, but without this module's contribution + */ def upstreamAssemblyClasspath = T{ transitiveLocalClasspath() ++ unmanagedClasspath() ++ resolveDeps(T.task{runIvyDeps() ++ transitiveIvyDeps()})() } + /** + * All classfiles and resources from upstream modules and dependencies + * necessary to run this module's code after compilation + */ def runClasspath = T{ localClasspath() ++ upstreamAssemblyClasspath() } /** - * Build the assembly for upstream dependencies separate from the current classpath + * Build the assembly for upstream dependencies separate from the current + * classpath * * This should allow much faster assembly creation in the common case where * upstream dependencies do not change @@ -188,6 +273,10 @@ trait JavaModule extends mill.Module with TaskModule { outer => ) } + /** + * An executable uber-jar/assembly containing all the resources and compiled + * classfiles from this module and all it's upstream modules and dependencies + */ def assembly = T{ createAssembly( Agg.from(localClasspath().map(_.path)), @@ -198,6 +287,10 @@ trait JavaModule extends mill.Module with TaskModule { outer => ) } + /** + * A jar containing only this module's resources and compiled classfiles, + * without those from upstream modules and dependencies + */ def jar = T{ createJar( localClasspath().map(_.path).filter(exists), @@ -205,6 +298,10 @@ trait JavaModule extends mill.Module with TaskModule { outer => ) } + /** + * The documentation jar, containing all the Javadoc/Scaladoc HTML files, for + * publishing to Maven Central + */ def docJar = T[PathRef] { val outDir = T.ctx().dest @@ -239,14 +336,30 @@ trait JavaModule extends mill.Module with TaskModule { outer => createJar(Agg(javadocDir))(outDir) } + /** + * The source jar, containing only source code for publishing to Maven Central + */ def sourceJar = T { createJar((allSources() ++ resources()).map(_.path).filter(exists)) } + /** + * Any command-line parameters you want to pass to the forked JVM under `run`, + * `test` or `repl` + */ def forkArgs = T{ Seq.empty[String] } + /** + * Any environment variables you want to pass to the forked JVM under `run`, + * `test` or `repl` + */ def forkEnv = T{ sys.env.toMap } + /** + * Builds a command-line "launcher" file that can be used to run this module's + * code, without the Mill process. Useful for deployment & other places where + * you do not want a build tool running + */ def launcher = T{ Result.Success( Jvm.createLauncher( @@ -271,6 +384,12 @@ trait JavaModule extends mill.Module with TaskModule { outer => Result.Success() } + /** + * Runs this module's code in-process within an isolated classloader. This is + * faster than `run`, but in exchange you have less isolation between runs + * since the code can dirty the parent Mill process and potentially leave it + * in a bad state. + */ def runLocal(args: String*) = T.command { Jvm.runLocal( finalMainClass(), @@ -279,6 +398,9 @@ trait JavaModule extends mill.Module with TaskModule { outer => ) } + /** + * Runs this module's code in a subprocess and waits for it to finish + */ def run(args: String*) = T.command{ try Result.Success(Jvm.interactiveSubprocess( finalMainClass(), @@ -324,6 +446,18 @@ trait JavaModule extends mill.Module with TaskModule { outer => write(procTombstone, token) (procId, procTombstone, token) } + + /** + * Runs this module's code in a background process, until it dies or + * `runBackground` is used again. This lets you continue using Mill while + * the process is running in the background: editing files, compiling, and + * only re-starting the background process when you're ready. + * + * You can also use `-w foo.runBackground` to make Mill watch for changes + * and automatically recompile your code & restart the background process + * when ready. This is useful when working on long-running server processes + * that would otherwise run forever + */ def runBackground(args: String*) = T.command{ val (procId, procTombstone, token) = backgroundSetup(T.ctx().dest) try Result.Success(Jvm.interactiveSubprocess( @@ -339,6 +473,9 @@ trait JavaModule extends mill.Module with TaskModule { outer => } } + /** + * Same as `runBackground`, but lets you specify a main class to run + */ def runMainBackground(mainClass: String, args: String*) = T.command{ val (procId, procTombstone, token) = backgroundSetup(T.ctx().dest) try Result.Success(Jvm.interactiveSubprocess( @@ -354,6 +491,9 @@ trait JavaModule extends mill.Module with TaskModule { outer => } } + /** + * Same as `runLocal`, but lets you specify a main class to run + */ def runMainLocal(mainClass: String, args: String*) = T.command { Jvm.runLocal( mainClass, @@ -362,6 +502,9 @@ trait JavaModule extends mill.Module with TaskModule { outer => ) } + /** + * Same as `run`, but lets you specify a main class to run + */ def runMain(mainClass: String, args: String*) = T.command{ try Result.Success(Jvm.interactiveSubprocess( mainClass, @@ -388,8 +531,14 @@ trait JavaModule extends mill.Module with TaskModule { outer => trait TestModule extends JavaModule with TaskModule { override def defaultCommandName() = "test" + /** + * What test frameworks to use. + */ def testFrameworks: T[Seq[String]] - + /** + * Discovers and runs the module's tests in a subprocess, reporting the + * results to the console + */ def test(args: String*) = T.command{ val outputPath = T.ctx().dest/"out.json" @@ -418,6 +567,11 @@ trait TestModule extends JavaModule with TaskModule { } } + + /** + * Discovers and runs the module's tests in-process in an isolated classloader, + * reporting the results to the console + */ def testLocal(args: String*) = T.command{ val outputPath = T.ctx().dest/"out.json" @@ -448,3 +602,4 @@ object TestModule{ } } } + diff --git a/scalalib/src/mill/scalalib/ScalaModule.scala b/scalalib/src/mill/scalalib/ScalaModule.scala index 3a4a9d45..74656818 100644 --- a/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/scalalib/src/mill/scalalib/ScalaModule.scala @@ -27,6 +27,10 @@ trait ScalaModule extends JavaModule { outer => override def moduleDeps: Seq[JavaModule] = Seq(outer) } + /** + * What Scala organization to use + * @return + */ def scalaOrganization: T[String] = T { if (isDotty(scalaVersion())) "ch.epfl.lamp" @@ -34,6 +38,9 @@ trait ScalaModule extends JavaModule { outer => "org.scala-lang" } + /** + * What version of Scala to use + */ def scalaVersion: T[String] override def mapDependencies = T.task{ d: coursier.Dependency => @@ -59,10 +66,16 @@ trait ScalaModule extends JavaModule { outer => ) } + /** + * Allows you to make use of Scala compiler plugins from maven central + */ def scalacPluginIvyDeps = T{ Agg.empty[Dep] } def scalaDocPluginIvyDeps = T{ scalacPluginIvyDeps() } + /** + * Command-line options to pass to the Scala compiler + */ def scalacOptions = T{ Seq.empty[String] } def scalaDocOptions = T{ scalacOptions() } @@ -98,22 +111,33 @@ trait ScalaModule extends JavaModule { outer => ) } + /** + * The local classpath of Scala compiler plugins on-disk; you can add + * additional jars here if you have some copiler plugin that isn't present + * on maven central + */ def scalacPluginClasspath: T[Agg[PathRef]] = T { resolveDeps(scalacPluginIvyDeps)() } + /** + * The ivy coordinates of Scala's own standard library + */ def scalaDocPluginClasspath: T[Agg[PathRef]] = T { resolveDeps(scalaDocPluginIvyDeps)() } def scalaLibraryIvyDeps = T{ scalaRuntimeIvyDeps(scalaOrganization(), scalaVersion()) } + /** * Classpath of the Scala Compiler & any compiler plugins */ def scalaCompilerClasspath: T[Agg[PathRef]] = T{ resolveDeps( - T.task{scalaCompilerIvyDeps(scalaOrganization(), scalaVersion()) ++ - scalaRuntimeIvyDeps(scalaOrganization(), scalaVersion())} + T.task{ + scalaCompilerIvyDeps(scalaOrganization(), scalaVersion()) ++ + scalaRuntimeIvyDeps(scalaOrganization(), scalaVersion()) + } )() } override def compileClasspath = T{ @@ -168,6 +192,10 @@ trait ScalaModule extends JavaModule { outer => createJar(Agg(javadocDir))(outDir) } + /** + * Opens up a Scala console with your module and all dependencies present, + * for you to test and operate your code interactively + */ def console() = T.command{ if (T.ctx().log.inStream == DummyInputStream){ Result.Failure("repl needs to be run with the -i/--interactive flag") @@ -186,6 +214,9 @@ trait ScalaModule extends JavaModule { outer => } } + /** + * Dependencies that are necessary to run the Ammonite Scala REPL + */ def ammoniteReplClasspath = T{ localClasspath() ++ transitiveLocalClasspath() ++ @@ -196,6 +227,10 @@ trait ScalaModule extends JavaModule { outer => })() } + /** + * Opens up an Ammonite Scala REPL with your module and all dependencies present, + * for you to test and operate your code interactively + */ def repl(replOptions: String*) = T.command{ if (T.ctx().log.inStream == DummyInputStream){ Result.Failure("repl needs to be run with the -i/--interactive flag") @@ -211,14 +246,22 @@ trait ScalaModule extends JavaModule { outer => } - // publish artifact with name "mill_2.12.4" instead of "mill_2.12" + /** + * Whether to publish artifacts with name "mill_2.12.4" instead of "mill_2.12" + */ def crossFullScalaVersion: T[Boolean] = false + /** + * What Scala version string to use when publishing + */ def artifactScalaVersion: T[String] = T { if (crossFullScalaVersion()) scalaVersion() else Lib.scalaBinaryVersion(scalaVersion()) } + /** + * The suffix appended to the artifact IDs during publishing + */ def artifactSuffix: T[String] = s"_${artifactScalaVersion()}" override def artifactId: T[String] = artifactName() + artifactSuffix() diff --git a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala index 6b21de84..d2be35e9 100644 --- a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala +++ b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala @@ -125,12 +125,20 @@ class ZincWorkerImpl(ctx0: mill.util.Ctx, compilerClasspath: Agg[Path], scalacPluginClasspath: Agg[Path]) (implicit ctx: mill.util.Ctx): mill.eval.Result[CompilationResult] = { - val compilerJars = compilerClasspath.toArray.map(_.toIO) + val combinedCompilerClasspath = compilerClasspath ++ scalacPluginClasspath + val combinedCompilerJars = combinedCompilerClasspath.toArray.map(_.toIO) - val compilerBridge = compileZincBridgeIfNeeded(scalaVersion, compilerBridgeSources, compilerJars) + val compilerBridge = compileZincBridgeIfNeeded( + scalaVersion, + compilerBridgeSources, + compilerClasspath.toArray.map(_.toIO) + ) val compilerBridgeSig = compilerBridge.mtime.toMillis - val compilersSig = compilerBridgeSig + compilerClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum + val compilersSig = + compilerBridgeSig + + combinedCompilerClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum + val compilers = mixedCompilersCache match { case Some((k, v)) if k == compilersSig => v case _ => @@ -141,10 +149,10 @@ class ZincWorkerImpl(ctx0: mill.util.Ctx, "scala-compiler" val scalaInstance = new ScalaInstance( version = scalaVersion, - loader = mill.util.ClassLoader.create(compilerJars.map(_.toURI.toURL), null), + loader = mill.util.ClassLoader.create(combinedCompilerJars.map(_.toURI.toURL), null), libraryJar = grepJar(compilerClasspath, "scala-library", scalaVersion).toIO, compilerJar = grepJar(compilerClasspath, compilerName, scalaVersion).toIO, - allJars = compilerJars, + allJars = combinedCompilerJars, explicitActual = None ) val compilers = ic.compilers( diff --git a/scratch/build.sc b/scratch/build.sc index 46c271b5..0a33c86e 100644 --- a/scratch/build.sc +++ b/scratch/build.sc @@ -3,18 +3,29 @@ import mill.scalalib._ trait JUnitTests extends TestModule{ def testFrameworks = Seq("com.novocode.junit.JUnitFramework") + + /** + * Overriden ivyDeps Docs!!! + */ def ivyDeps = Agg(ivy"com.novocode:junit-interface:0.11") + def task = T{ + "???" + } } +/** + * The Core Module Docz! + */ object core extends JavaModule{ object test extends Tests with JUnitTests -} -object app extends JavaModule{ - def moduleDeps = Seq(core) - object test extends Tests with JUnitTests -} -object scalapb extends mill.contrib.scalapblib.ScalaPBModule { - def scalaVersion = "2.12.4" - def scalaPBVersion = "0.7.4" + /** + * Core Task Docz! + */ + def task = T{ + import collection.JavaConverters._ + println(this.getClass.getClassLoader.getResources("scalac-plugin.xml").asScala.toList) + "Hello!" + } } + diff --git a/scratch/scalapb/protobuf/scratch.proto b/scratch/scalapb/protobuf/scratch.proto deleted file mode 100644 index 3153ad54..00000000 --- a/scratch/scalapb/protobuf/scratch.proto +++ /dev/null @@ -1,29 +0,0 @@ -syntax = "proto2"; - -package tutorial; - -option java_package = "com.example.tutorial"; -option java_outer_classname = "AddressBookProtos"; - -message Person { - required string name = 1; - required int32 id = 2; - optional string email = 3; - - enum PhoneType { - MOBILE = 0; - HOME = 1; - WORK = 2; - } - - message PhoneNumber { - required string number = 1; - optional PhoneType type = 2 [default = HOME]; - } - - repeated PhoneNumber phones = 4; -} - -message AddressBook { - repeated Person people = 1; -} \ No newline at end of file -- cgit v1.2.3