From 64f477e36c33afe8dfd87a839ba263e9973b0669 Mon Sep 17 00:00:00 2001 From: Nathan Fischer Date: Wed, 17 Apr 2019 17:58:05 -0700 Subject: Contrib module for building docker images --- docs/pages/9 - Contrib Modules.md | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'docs') diff --git a/docs/pages/9 - Contrib Modules.md b/docs/pages/9 - Contrib Modules.md index 1b9b55fa..9adddcc5 100644 --- a/docs/pages/9 - Contrib Modules.md +++ b/docs/pages/9 - Contrib Modules.md @@ -38,6 +38,50 @@ object project extends BuildInfo { * `def buildInfoPackageName: Option[String]`, default: `None` The package name of the object. +### Docker + +Automatically build docker images from your mill project. + +Requires the docker CLI to be installed. + +In the simplest configuration just extend `DockerModule` and declare a `DockerConfig` object. + +```scala +import mill._, scalalib._ + +import ivy`com.lihaoyi::mill-contrib-docker:VERSION` +import contrib.docker.DockerModule + +object foo extends JavaModule with DockerModule { + object docker extends DockerConfig +} +``` + +Then + +``` +$ mill foo.docker.build +$ docker run foo +``` + +#### Configuration + +Configure the image by overriding tasks in the `DockerConfig` object + +```scala +object docker extends DockerConfig { + // Override tags to set the output image name + def tags = List("aws_account_id.dkr.ecr.region.amazonaws.com/hello-repository") + + def baseImage = "openjdk:11" + + // Configure whether the docker build should check the remote registry for a new version of the base image before building. + // By default this is true if the base image is using a latest tag + def pullBaseImage = true +} +``` + +Run mill in interactive mode to see the docker client output, like `mill -i foo.docker.build`. ### Flyway -- cgit v1.2.3 From c4a65baab92890d2d5682329a87469bec605fe5d Mon Sep 17 00:00:00 2001 From: Nik Vanderhoof Date: Sat, 18 May 2019 23:22:13 -0400 Subject: Add support for Scoverage (#571) * Add initial work for ScoverageModule * style: Move package scoverage from lib to contrib Suggested by @lefou > I think, it would be better to add under `mill.contrib.scoverage`. Pull request: #571 * Initial changes to non-hardcoded scoverage version * Using task context api to specify dataDir Now measurement data will be written to: PROJECT_ROOT/out/MODULE/scoverage/data/ and the html report will be written to: PROJECT_ROOT/out/MODULE/scoverage/data/htmlReport/ * Remove wild card imports in scoverage Also remove scoverage dependency from build.sc * Move htmlReport into worker Based on what I've seen in scalalib, scalajslib, scalanativelib, playlib, and twirllib modules. Still need to add tests * Add basic docs + tests for scoverage I still am working on testing the actual generation of reports. * Use cross-module for scoverage worker Now we can support multiple versions of scoverage by adding them to the crossmodule list. Also now running the local publish script succeeds. * Add scoverage to ci tests * Add detailed ScoverageModule documentation * Test scoverage dataDir * Remove
 tags in scaladoc

* Add scoverage dependency in less hacky way

* Modify scoverage tests to check classpaths

* Put docs in alphabetical order

* Test classpaths for scoverage runtime

* Remove abstract def test: ScoverageTests

* Construct classloader differently

* Revert "Construct classloader differently"

This reverts commit fccf9a94cc38fb9e2be58a9ff90b00b65f339db6.

* Revert "Construct classloader differently"

Also fixes unfound error in html report

This reverts commit fccf9a94cc38fb9e2be58a9ff90b00b65f339db6.

* Fix classpath for scoverage worker
---
 build.sc                                           |  27 ++++-
 ci/test-mill-0.sh                                  |   3 +-
 ci/test-mill-bootstrap.sh                          |   2 +-
 .../playlib/src/mill/playlib/RouterModule.scala    |   2 +-
 .../api/src/ScoverageReportWorkerApi.scala         |   7 ++
 contrib/scoverage/src/ScoverageModule.scala        | 122 +++++++++++++++++++++
 contrib/scoverage/src/ScoverageReportWorker.scala  |  39 +++++++
 .../resources/hello-world/core/src/Greet.scala     |   6 +
 contrib/scoverage/test/src/HelloWorldTests.scala   | 107 ++++++++++++++++++
 .../1.3.1/src/ScoverageReportWorkerImpl.scala      |  21 ++++
 docs/pages/9 - Contrib Modules.md                  |  44 +++++++-
 11 files changed, 375 insertions(+), 5 deletions(-)
 create mode 100644 contrib/scoverage/api/src/ScoverageReportWorkerApi.scala
 create mode 100644 contrib/scoverage/src/ScoverageModule.scala
 create mode 100644 contrib/scoverage/src/ScoverageReportWorker.scala
 create mode 100644 contrib/scoverage/test/resources/hello-world/core/src/Greet.scala
 create mode 100644 contrib/scoverage/test/src/HelloWorldTests.scala
 create mode 100644 contrib/scoverage/worker/1.3.1/src/ScoverageReportWorkerImpl.scala

(limited to 'docs')

diff --git a/build.sc b/build.sc
index fd5d4e29..215e8d37 100755
--- a/build.sc
+++ b/build.sc
@@ -287,6 +287,31 @@ object contrib extends MillModule {
     def moduleDeps = Seq(scalalib)
   }
 
+  object scoverage extends MillModule {
+    def moduleDeps = Seq(scalalib, scoverage.api)
+
+    def testArgs = T {
+      val mapping = Map(
+        "MILL_SCOVERAGE_REPORT_WORKER_1_3_1" -> worker("1.3.1").compile().classes.path
+      )
+      scalalib.worker.testArgs() ++
+        scalalib.backgroundwrapper.testArgs() ++
+        (for ((k, v) <- mapping) yield s"-D$k=$v")
+    }
+
+    object api extends MillApiModule {
+      def moduleDeps = Seq(scalalib)
+    }
+
+    object worker extends Cross[WorkerModule]("1.3.1")
+
+    class WorkerModule(scoverageVersion: String) extends MillApiModule {
+      def moduleDeps = Seq(scoverage.api)
+
+      def ivyDeps = Agg(ivy"org.scoverage::scalac-scoverage-plugin:${scoverageVersion}")
+    }
+  }
+
   object buildinfo extends MillModule {
     def moduleDeps = Seq(scalalib)
     // why do I need this?
@@ -436,7 +461,7 @@ def launcherScript(shellJvmArgs: Seq[String],
 }
 
 object dev extends MillModule{
-  def moduleDeps = Seq(scalalib, scalajslib, scalanativelib, contrib.scalapblib, contrib.tut)
+  def moduleDeps = Seq(scalalib, scalajslib, scalanativelib, contrib.scalapblib, contrib.tut, contrib.scoverage)
 
   def forkArgs =
     (
diff --git a/ci/test-mill-0.sh b/ci/test-mill-0.sh
index fa8d7604..83c361b1 100755
--- a/ci/test-mill-0.sh
+++ b/ci/test-mill-0.sh
@@ -6,4 +6,5 @@ set -eux
 git clean -xdf
 
 # Run tests
-mill -i all {main,scalalib,scalajslib,contrib.twirllib,contrib.playlib,main.client,contrib.scalapblib,contrib.flyway}.test
+
+mill -i all {main,scalalib,scalajslib,contrib.twirllib,contrib.playlib,main.client,contrib.scalapblib,contrib.flyway,contrib.scoverage}.test
\ No newline at end of file
diff --git a/ci/test-mill-bootstrap.sh b/ci/test-mill-bootstrap.sh
index f95c0646..80086df2 100755
--- a/ci/test-mill-bootstrap.sh
+++ b/ci/test-mill-bootstrap.sh
@@ -27,4 +27,4 @@ git clean -xdf
 rm -rf ~/.mill
 
 # Use second build to run tests using Mill
-~/mill-2 -i all {main,scalalib,scalajslib,contrib.twirllib,contrib.playlib,contrib.scalapblib}.test
+~/mill-2 -i all {main,scalalib,scalajslib,contrib.twirllib,contrib.playlib,contrib.scalapblib,contrib.scoverage}.test
diff --git a/contrib/playlib/src/mill/playlib/RouterModule.scala b/contrib/playlib/src/mill/playlib/RouterModule.scala
index abf3082b..62a4c16b 100644
--- a/contrib/playlib/src/mill/playlib/RouterModule.scala
+++ b/contrib/playlib/src/mill/playlib/RouterModule.scala
@@ -97,4 +97,4 @@ trait RouterModule extends ScalaModule with Version {
   override def generatedSources = T {
     super.generatedSources() ++ routerClasses()
   }
-}
\ No newline at end of file
+}
diff --git a/contrib/scoverage/api/src/ScoverageReportWorkerApi.scala b/contrib/scoverage/api/src/ScoverageReportWorkerApi.scala
new file mode 100644
index 00000000..d74e1275
--- /dev/null
+++ b/contrib/scoverage/api/src/ScoverageReportWorkerApi.scala
@@ -0,0 +1,7 @@
+package mill.contrib.scoverage.api
+
+import mill.eval.PathRef
+
+trait ScoverageReportWorkerApi {
+  def htmlReport(sources: Seq[PathRef], dataDir: String, selfDir: String): Unit
+}
diff --git a/contrib/scoverage/src/ScoverageModule.scala b/contrib/scoverage/src/ScoverageModule.scala
new file mode 100644
index 00000000..b96afa34
--- /dev/null
+++ b/contrib/scoverage/src/ScoverageModule.scala
@@ -0,0 +1,122 @@
+package mill
+package contrib
+package scoverage
+
+import coursier.{Cache, MavenRepository}
+import mill.api.Result
+import mill.eval.PathRef
+import mill.util.Ctx
+import mill.scalalib.{DepSyntax, JavaModule, Lib, ScalaModule, TestModule, Dep}
+import mill.moduledefs.Cacher
+
+
+/** Adds targets to a [[mill.scalalib.ScalaModule]] to create test coverage reports.
+ *
+ * This module allows you to generate code coverage reports for Scala projects with
+ * [[https://github.com/scoverage Scoverage]] via the
+ * [[https://github.com/scoverage/scalac-scoverage-plugin scoverage compiler plugin]].
+ *
+ * To declare a module for which you want to generate coverage reports you can
+ * Extends the `mill.contrib.scoverage.ScoverageModule` trait when defining your
+ * Module. Additionally, you must define a submodule that extends the
+ * `ScoverageTests` trait that belongs to your instance of `ScoverageModule`.
+ *
+ * {{{
+ * // You have to replace VERSION
+ * import $ivy.`com.lihaoyi::mill-contrib-buildinfo:VERSION`
+ * import mill.contrib.scoverage.ScoverageModule
+ *
+ * Object foo extends ScoverageModule  {
+ *   def scalaVersion = "2.11.8"
+ *   def scoverageVersion = "1.3.1"
+ *
+ *   object test extends ScoverageTests {
+ *     def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.5")
+ *     def testFrameworks = Seq("org.scalatest.tools.Framework")
+ *   }
+ * }
+ * }}}
+ *
+ * In addition to the normal tasks available to your Scala module, Scoverage
+ * Modules introduce a few new tasks and changes the behavior of an existing one.
+ *
+ * - mill foo.scoverage.compile      # compiles your module with test instrumentation
+ *                                 # (you don't have to run this manually, running the test task will force its invocation)
+ *
+ * - mill foo.test                   # tests your project and collects metrics on code coverage
+ * - mill foo.scoverage.htmlReport   # uses the metrics collected by a previous test run to generate a coverage report in html format
+ *
+ * The measurement data is available at `out/foo/scoverage/data/`,
+ * And the html report is saved in `out/foo/scoverage/htmlReport/`.
+ */
+trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
+  def scoverageVersion: T[String]
+  private def scoverageRuntimeDep = T {
+    ivy"org.scoverage::scalac-scoverage-runtime:${outer.scoverageVersion()}"
+  }
+  private def scoveragePluginDep = T {
+    ivy"org.scoverage::scalac-scoverage-plugin:${outer.scoverageVersion()}"
+  }
+
+  private def toolsClasspath = T {
+    scoverageReportWorkerClasspath() ++ scoverageClasspath()
+  }
+
+  def scoverageClasspath = T {
+    Lib.resolveDependencies(
+      Seq(Cache.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")),
+      Lib.depToDependency(_, outer.scalaVersion()),
+      Seq(scoveragePluginDep()),
+      ctx = Some(implicitly[mill.util.Ctx.Log])
+    )
+  }
+
+  def scoverageReportWorkerClasspath = T {
+    val workerKey = "MILL_SCOVERAGE_REPORT_WORKER_" + scoverageVersion().replace(".", "_")
+    mill.modules.Util.millProjectModule(
+      workerKey,
+      s"mill-contrib-scoverage-worker-${outer.scoverageVersion()}",
+      repositories,
+      resolveFilter = _.toString.contains("mill-contrib-scoverage-worker")
+    )
+  }
+
+  object scoverage extends ScalaModule {
+    def selfDir = T { T.ctx().dest / os.up / os.up }
+    def dataDir = T { selfDir() / "data" }
+
+    def sources = outer.sources
+    def resources = outer.resources
+    def scalaVersion = outer.scalaVersion()
+    def compileIvyDeps = outer.compileIvyDeps()
+    def ivyDeps = outer.ivyDeps() ++ Agg(outer.scoverageRuntimeDep())
+    def scalacPluginIvyDeps = outer.scalacPluginIvyDeps() ++ Agg(outer.scoveragePluginDep())
+    def scalacOptions = outer.scalacOptions() ++
+      Seq(s"-P:scoverage:dataDir:${dataDir()}")
+
+    def htmlReport() = T.command {
+      ScoverageReportWorkerApi
+        .scoverageReportWorker()
+        .bridge(toolsClasspath().map(_.path))
+        .htmlReport(sources(), dataDir().toString, selfDir().toString)
+    }
+  }
+
+  trait ScoverageTests extends outer.Tests {
+    override def upstreamAssemblyClasspath = T {
+      super.upstreamAssemblyClasspath() ++
+      resolveDeps(T.task{Agg(outer.scoverageRuntimeDep())})()
+    }
+    override def compileClasspath = T {
+      super.compileClasspath() ++
+      resolveDeps(T.task{Agg(outer.scoverageRuntimeDep())})()
+    }
+    override def runClasspath = T {
+      super.runClasspath() ++
+      resolveDeps(T.task{Agg(outer.scoverageRuntimeDep())})()
+    }
+
+    // Need the sources compiled with scoverage instrumentation to run.
+    override def moduleDeps: Seq[JavaModule] = Seq(outer.scoverage)
+  }
+}
diff --git a/contrib/scoverage/src/ScoverageReportWorker.scala b/contrib/scoverage/src/ScoverageReportWorker.scala
new file mode 100644
index 00000000..1aaa31ad
--- /dev/null
+++ b/contrib/scoverage/src/ScoverageReportWorker.scala
@@ -0,0 +1,39 @@
+package mill.contrib.scoverage
+
+import mill.{Agg, T}
+import mill.api.{ClassLoader, Ctx, Result}
+import mill.define.{Discover, ExternalModule, Worker}
+import mill.eval.PathRef
+
+class ScoverageReportWorker {
+  private var scoverageInstanceCache = Option.empty[(Long, api.ScoverageReportWorkerApi)]
+
+  def bridge(classpath: Agg[os.Path])
+                    (implicit ctx: Ctx) = {
+    val classloaderSig =
+      classpath.map(p => p.toString().hashCode + os.mtime(p)).sum
+    scoverageInstanceCache match {
+      case Some((sig, bridge)) if sig == classloaderSig => bridge
+      case _ =>
+        val toolsClassPath = classpath.map(_.toIO.toURI.toURL).toVector
+        ctx.log.debug("Loading classes from\n"+toolsClassPath.mkString("\n"))
+        val cl = ClassLoader.create(
+          toolsClassPath,
+          getClass.getClassLoader
+        )
+        val bridge = cl
+          .loadClass("mill.contrib.scoverage.worker.ScoverageReportWorkerImpl")
+          .getDeclaredConstructor()
+          .newInstance()
+          .asInstanceOf[api.ScoverageReportWorkerApi]
+        scoverageInstanceCache = Some((classloaderSig, bridge))
+        bridge
+    }
+  }
+}
+
+object ScoverageReportWorkerApi extends ExternalModule {
+
+  def scoverageReportWorker = T.worker { new ScoverageReportWorker() }
+  lazy val millDiscover = Discover[this.type]
+}
diff --git a/contrib/scoverage/test/resources/hello-world/core/src/Greet.scala b/contrib/scoverage/test/resources/hello-world/core/src/Greet.scala
new file mode 100644
index 00000000..608becc9
--- /dev/null
+++ b/contrib/scoverage/test/resources/hello-world/core/src/Greet.scala
@@ -0,0 +1,6 @@
+object Greet {
+  def greet(name: String, prefix: Option[String]): String = prefix match {
+    case Some(p) => s"Hello, ${p} ${name}!"
+    case None => s"Hello, ${name}!"
+  }
+}
diff --git a/contrib/scoverage/test/src/HelloWorldTests.scala b/contrib/scoverage/test/src/HelloWorldTests.scala
new file mode 100644
index 00000000..98a4201c
--- /dev/null
+++ b/contrib/scoverage/test/src/HelloWorldTests.scala
@@ -0,0 +1,107 @@
+package mill.contrib.scoverage
+
+import mill._
+import mill.api.Result
+import mill.scalalib._
+import mill.util.{TestEvaluator, TestUtil}
+import utest._
+import utest.framework.TestPath
+
+object HelloWorldTests extends utest.TestSuite {
+  val resourcePath = os.pwd / 'contrib / 'scoverage / 'test / 'resources / "hello-world"
+  trait HelloBase extends TestUtil.BaseModule {
+    def millSourcePath =  TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.')
+  }
+
+  object HelloWorld extends HelloBase {
+    object core extends ScoverageModule {
+      def scalaVersion = "2.12.4"
+      def scoverageVersion = "1.3.1"
+
+      object test extends ScoverageTests {
+        override def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.5")
+        def testFrameworks = Seq("org.scalatest.tools.Framework")
+      }
+    }
+  }
+
+  def workspaceTest[T](m: TestUtil.BaseModule, resourcePath: os.Path = resourcePath)
+                      (t: TestEvaluator => T)
+                      (implicit tp: TestPath): T = {
+    val eval = new TestEvaluator(m)
+    os.remove.all(m.millSourcePath)
+    os.remove.all(eval.outPath)
+    os.makeDir.all(m.millSourcePath / os.up)
+    os.copy(resourcePath, m.millSourcePath)
+    t(eval)
+  }
+
+  def tests: utest.Tests = utest.Tests {
+    "HelloWorld" - {
+      "core" - {
+        "scoverageVersion" - workspaceTest(HelloWorld) { eval =>
+          val Right((result, evalCount)) = eval.apply(HelloWorld.core.scoverageVersion)
+
+          assert(
+            result == "1.3.1",
+            evalCount > 0
+          )
+        }
+        "scoverage" - {
+          "ivyDeps" - workspaceTest(HelloWorld) { eval =>
+            val Right((result, evalCount)) =
+              eval.apply(HelloWorld.core.scoverage.ivyDeps)
+
+            assert(
+              result == Agg(ivy"org.scoverage::scalac-scoverage-runtime:1.3.1"),
+              evalCount > 0
+            )
+          }
+          "scalacPluginIvyDeps" - workspaceTest(HelloWorld) { eval =>
+            val Right((result, evalCount)) =
+              eval.apply(HelloWorld.core.scoverage.scalacPluginIvyDeps)
+
+            assert(
+              result == Agg(ivy"org.scoverage::scalac-scoverage-plugin:1.3.1"),
+              evalCount > 0
+            )
+          }
+          "dataDir" - workspaceTest(HelloWorld) { eval =>
+            val Right((result, evalCount)) = eval.apply(HelloWorld.core.scoverage.dataDir)
+
+            assert(
+              result.toString.endsWith("mill/target/workspace/mill/contrib/scoverage/HelloWorldTests/eval/HelloWorld/core/scoverage/dataDir/core/scoverage/data"),
+              evalCount > 0
+            )
+          }
+        }
+        "test" - {
+          "upstreamAssemblyClasspath" - workspaceTest(HelloWorld) { eval =>
+            val Right((result, evalCount)) = eval.apply(HelloWorld.core.scoverage.upstreamAssemblyClasspath)
+
+            assert(
+              result.map(_.toString).exists(_.contains("scalac-scoverage-runtime")),
+              evalCount > 0
+            )
+          }
+          "compileClasspath" - workspaceTest(HelloWorld) { eval =>
+            val Right((result, evalCount)) = eval.apply(HelloWorld.core.scoverage.compileClasspath)
+
+            assert(
+              result.map(_.toString).exists(_.contains("scalac-scoverage-runtime")),
+              evalCount > 0
+            )
+          }
+          "runClasspath" - TestUtil.disableInJava9OrAbove(workspaceTest(HelloWorld) { eval =>
+            val Right((result, evalCount)) = eval.apply(HelloWorld.core.scoverage.runClasspath)
+
+            assert(
+              result.map(_.toString).exists(_.contains("scalac-scoverage-runtime")),
+              evalCount > 0
+            )
+          })
+        }
+      }
+    }
+  }
+}
diff --git a/contrib/scoverage/worker/1.3.1/src/ScoverageReportWorkerImpl.scala b/contrib/scoverage/worker/1.3.1/src/ScoverageReportWorkerImpl.scala
new file mode 100644
index 00000000..44f506f7
--- /dev/null
+++ b/contrib/scoverage/worker/1.3.1/src/ScoverageReportWorkerImpl.scala
@@ -0,0 +1,21 @@
+package mill.contrib.scoverage.worker
+
+import mill.contrib.scoverage.api.ScoverageReportWorkerApi
+import mill.eval.PathRef
+import _root_.scoverage.Serializer.{ coverageFile, deserialize }
+import _root_.scoverage.IOUtils.{ findMeasurementFiles, invoked }
+import _root_.scoverage.report.ScoverageHtmlWriter
+
+class ScoverageReportWorkerImpl extends ScoverageReportWorkerApi {
+  def htmlReport(sources: Seq[PathRef], dataDir: String, selfDir: String) = {
+    val coverageFileObj = coverageFile(dataDir)
+    val coverage = deserialize(coverageFileObj)
+    coverage(invoked(findMeasurementFiles(dataDir)))
+    val Seq(PathRef(sourceFolderPath, _, _)) = sources
+    val sourceFolders = Seq(sourceFolderPath.toIO)
+    val htmlFolder = new java.io.File(s"${selfDir}/htmlReport")
+    htmlFolder.mkdir()
+    new ScoverageHtmlWriter(sourceFolders, htmlFolder, None)
+      .write(coverage)
+  }
+}
diff --git a/docs/pages/9 - Contrib Modules.md b/docs/pages/9 - Contrib Modules.md
index eca61be3..c3d4751a 100644
--- a/docs/pages/9 - Contrib Modules.md	
+++ b/docs/pages/9 - Contrib Modules.md	
@@ -543,6 +543,49 @@ object example extends ScalaPBModule {
 }
 ```
 
+
+## Scoverage
+
+This module allows you to generate code coverage reports for Scala projects with
+[Scoverage](https://github.com/scoverage) via the
+[scalac-scoverage-plugin](https://github.com/scoverage/scalac-scoverage-plugin).
+
+To declare a module for which you want to generate coverage reports you can
+extends the `mill.contrib.scoverage.ScoverageModule` trait when defining your
+module. Additionally, you must define a submodule that extends the
+`ScoverageTests` trait that belongs to your instance of `ScoverageModule`.
+
+```scala
+// You have to replace VERSION
+import $ivy.`com.lihaoyi::mill-contrib-buildinfo:VERSION`
+import mill.contrib.scoverage.ScoverageModule
+
+object foo extends ScoverageModule  {
+  def scalaVersion = "2.11.8"
+  def scoverageVersion = "1.3.1"
+
+  object test extends ScoverageTests {
+    def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.5")
+    def testFrameworks = Seq("org.scalatest.tools.Framework")
+  }
+}
+```
+
+In addition to the normal tasks available to your Scala module, Scoverage
+modules introduce a few new tasks and changes the behavior of an existing one.
+
+```
+mill foo.scoverage.compile      # compiles your module with test instrumentation
+                                # (you don't have to run this manually, running the test task will force its invocation)
+
+mill foo.test                   # tests your project and collects metrics on code coverage
+mill foo.scoverage.htmlReport   # uses the metrics collected by a previous test run to generate a coverage report in html format
+```
+
+The measurement data is available at `out/foo/scoverage/data/`,
+and the html report is saved in `out/foo/scoverage/htmlReport/`.
+
+
 ## TestNG
 
 Provides support for [TestNG](https://testng.org/doc/index.html).
@@ -722,4 +765,3 @@ These imports will always be added to every template.  You don't need to list th
 
 ### Example
 There's an [example project](https://github.com/lihaoyi/cask/tree/master/example/twirl)
-
-- 
cgit v1.2.3


From 052af24a530f8bc0532b368a2d360ff30f67d7b8 Mon Sep 17 00:00:00 2001
From: Guillaume Galy 
Date: Sun, 19 May 2019 05:23:18 +0200
Subject: Switch from scalafmt-cli to scalafmt-dynamic (#600)

---
 build.sc                                           |  3 +-
 docs/pages/2 - Configuring Mill.md                 |  6 ++++
 .../mill/scalalib/scalafmt/default.scalafmt.conf   |  0
 scalalib/src/scalafmt/ScalafmtModule.scala         | 16 ++--------
 scalalib/src/scalafmt/ScalafmtWorker.scala         | 37 +++++++++++++---------
 5 files changed, 32 insertions(+), 30 deletions(-)
 create mode 100644 scalalib/resources/mill/scalalib/scalafmt/default.scalafmt.conf

(limited to 'docs')

diff --git a/build.sc b/build.sc
index 215e8d37..c51e8195 100755
--- a/build.sc
+++ b/build.sc
@@ -143,7 +143,8 @@ object scalalib extends MillModule {
   def moduleDeps = Seq(main, scalalib.api)
 
   def ivyDeps = Agg(
-    ivy"org.scala-sbt:test-interface:1.0"
+    ivy"org.scala-sbt:test-interface:1.0",
+    ivy"org.scalameta::scalafmt-dynamic:2.0.0-RC6"
   )
 
   def genTask(m: ScalaModule) = T.task{
diff --git a/docs/pages/2 - Configuring Mill.md b/docs/pages/2 - Configuring Mill.md
index dbcbcee4..f6ca86a0 100644
--- a/docs/pages/2 - Configuring Mill.md	
+++ b/docs/pages/2 - Configuring Mill.md	
@@ -237,6 +237,12 @@ Now you can reformat code with `mill foo.reformat` command.
 You can also reformat your project's code globally with `mill mill.scalalib.scalafmt.ScalafmtModule/reformatAll __.sources` command.
 It will reformat all sources that matches `__.sources` query.
 
+If you add a `.scalafmt.conf` file at the root of you project, it will be used
+to configure formatting. It can contain a `version` key to specify the scalafmt
+version used to format your code. See the
+[scalafmt configuration documentation](https://scalameta.org/scalafmt/docs/configuration.html)
+for details.
+
 ## Common Configuration
 
 ```scala
diff --git a/scalalib/resources/mill/scalalib/scalafmt/default.scalafmt.conf b/scalalib/resources/mill/scalalib/scalafmt/default.scalafmt.conf
new file mode 100644
index 00000000..e69de29b
diff --git a/scalalib/src/scalafmt/ScalafmtModule.scala b/scalalib/src/scalafmt/ScalafmtModule.scala
index 6a81d975..ea254e6d 100644
--- a/scalalib/src/scalafmt/ScalafmtModule.scala
+++ b/scalalib/src/scalafmt/ScalafmtModule.scala
@@ -11,23 +11,12 @@ trait ScalafmtModule extends JavaModule {
       .worker()
       .reformat(
         filesToFormat(sources()),
-        scalafmtConfig().head,
-        scalafmtDeps().map(_.path)
+        scalafmtConfig().head
       )
   }
 
-  def scalafmtVersion: T[String] = "1.5.1"
-
   def scalafmtConfig: Sources = T.sources(os.pwd / ".scalafmt.conf")
 
-  def scalafmtDeps: T[Agg[PathRef]] = T {
-    Lib.resolveDependencies(
-      zincWorker.repositories,
-      Lib.depToDependency(_, "2.12.4"),
-      Seq(ivy"com.geirsson::scalafmt-cli:${scalafmtVersion()}")
-    )
-  }
-
   protected def filesToFormat(sources: Seq[PathRef]) = {
     for {
       pathRef <- sources if os.exists(pathRef.path)
@@ -46,8 +35,7 @@ object ScalafmtModule extends ExternalModule with ScalafmtModule {
         .worker()
         .reformat(
           files,
-          scalafmtConfig().head,
-          scalafmtDeps().map(_.path)
+          scalafmtConfig().head
         )
     }
 
diff --git a/scalalib/src/scalafmt/ScalafmtWorker.scala b/scalalib/src/scalafmt/ScalafmtWorker.scala
index 47d8375f..f9c7e9b4 100644
--- a/scalalib/src/scalafmt/ScalafmtWorker.scala
+++ b/scalalib/src/scalafmt/ScalafmtWorker.scala
@@ -1,9 +1,11 @@
 package mill.scalalib.scalafmt
 
+import java.nio.file.{Paths => JPaths}
+
 import mill._
 import mill.define.{Discover, ExternalModule, Worker}
-import mill.modules.Jvm
 import mill.api.Ctx
+import org.scalafmt.interfaces.Scalafmt
 
 import scala.collection.mutable
 
@@ -18,8 +20,7 @@ private[scalafmt] class ScalafmtWorker {
   private var configSig: Int = 0
 
   def reformat(input: Seq[PathRef],
-               scalafmtConfig: PathRef,
-               scalafmtClasspath: Agg[os.Path])(implicit ctx: Ctx): Unit = {
+               scalafmtConfig: PathRef)(implicit ctx: Ctx): Unit = {
     val toFormat =
       if (scalafmtConfig.sig != configSig) input
       else
@@ -28,8 +29,7 @@ private[scalafmt] class ScalafmtWorker {
     if (toFormat.nonEmpty) {
       ctx.log.info(s"Formatting ${toFormat.size} Scala sources")
       reformatAction(toFormat.map(_.path),
-                     scalafmtConfig.path,
-                     scalafmtClasspath)
+                     scalafmtConfig.path)
       reformatted ++= toFormat.map { ref =>
         val updRef = PathRef(ref.path)
         updRef.path -> updRef.sig
@@ -43,15 +43,22 @@ private[scalafmt] class ScalafmtWorker {
   private val cliFlags = Seq("--non-interactive", "--quiet")
 
   private def reformatAction(toFormat: Seq[os.Path],
-                             config: os.Path,
-                             classpath: Agg[os.Path])(implicit ctx: Ctx) = {
-    val configFlags =
-      if (os.exists(config)) Seq("--config", config.toString) else Seq.empty
-    Jvm.runSubprocess(
-      "org.scalafmt.cli.Cli",
-      classpath,
-      mainArgs = toFormat.map(_.toString) ++ configFlags ++ cliFlags
-    )
-  }
+                             config: os.Path)(implicit ctx: Ctx) = {
+    val scalafmt =
+      Scalafmt
+        .create(this.getClass.getClassLoader)
+        .withRespectVersion(false)
+
+    val configPath =
+      if (os.exists(config))
+        config.toNIO
+      else
+        JPaths.get(getClass.getResource("default.scalafmt.conf").toURI)
 
+    toFormat.foreach { pathToFormat =>
+      val code = os.read(pathToFormat)
+      val formatteCode = scalafmt.format(configPath, pathToFormat.toNIO, code)
+      os.write.over(pathToFormat, formatteCode)
+    }
+  }
 }
-- 
cgit v1.2.3