summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE.md1
-rw-r--r--.travis.yml3
-rwxr-xr-xbuild.sc95
-rw-r--r--ci/shared.sc1
-rwxr-xr-xci/test-mill-0.sh3
-rwxr-xr-xci/test-mill-bootstrap.sh2
-rw-r--r--ci/upload.sc8
-rw-r--r--contrib/bloop/src/mill.contrib.bloop/BloopImpl.scala359
-rw-r--r--contrib/bloop/src/mill.contrib.bloop/CirceCompat.scala23
-rw-r--r--contrib/bloop/src/mill/contrib/Bloop.scala10
-rw-r--r--contrib/bloop/test/src/mill/contrib/bloop/BloopTests.scala103
-rw-r--r--contrib/buildinfo/src/BuildInfo.scala36
-rw-r--r--contrib/buildinfo/test/src/BuildInfoTests.scala54
-rw-r--r--contrib/docker/src/DockerModule.scala73
-rw-r--r--contrib/flyway/src/FlywayModule.scala65
-rw-r--r--contrib/flyway/test/resources/db/migration/V1__test_init_migration.sql5
-rw-r--r--contrib/flyway/test/src/BuildTest.scala48
-rw-r--r--contrib/playlib/api/src/RouteCompilerWorkerApi.scala2
-rw-r--r--contrib/playlib/api/src/Versions.scala7
-rw-r--r--contrib/playlib/src/mill/playlib/Dependencies.scala24
-rw-r--r--contrib/playlib/src/mill/playlib/Layout.scala17
-rw-r--r--contrib/playlib/src/mill/playlib/PlayModule.scala25
-rw-r--r--contrib/playlib/src/mill/playlib/RouteCompilerWorkerApi.scala8
-rw-r--r--contrib/playlib/src/mill/playlib/Router.scala6
-rw-r--r--contrib/playlib/src/mill/playlib/RouterModule.scala40
-rw-r--r--contrib/playlib/src/mill/playlib/Server.scala24
-rw-r--r--contrib/playlib/src/mill/playlib/SingleModule.scala6
-rw-r--r--contrib/playlib/src/mill/playlib/Static.scala99
-rw-r--r--contrib/playlib/src/mill/playlib/Twirl.scala29
-rw-r--r--contrib/playlib/src/mill/playlib/Version.scala18
-rw-r--r--contrib/playlib/test/resources/playmulti/core/app/controllers/HomeController.scala24
-rw-r--r--contrib/playlib/test/resources/playmulti/core/app/views/index.scala.html5
-rw-r--r--contrib/playlib/test/resources/playmulti/core/app/views/main.scala.html25
-rw-r--r--contrib/playlib/test/resources/playmulti/core/conf/application.conf2
-rw-r--r--contrib/playlib/test/resources/playmulti/core/conf/routes10
-rw-r--r--contrib/playlib/test/resources/playmulti/core/test/controllers/HomeControllerSpec.scala45
-rw-r--r--contrib/playlib/test/resources/playsingle/app/controllers/HomeController.scala24
-rw-r--r--contrib/playlib/test/resources/playsingle/app/views/index.scala.html5
-rw-r--r--contrib/playlib/test/resources/playsingle/app/views/main.scala.html25
-rw-r--r--contrib/playlib/test/resources/playsingle/conf/application.conf2
-rw-r--r--contrib/playlib/test/resources/playsingle/conf/routes10
-rw-r--r--contrib/playlib/test/resources/playsingle/test/controllers/HomeControllerSpec.scala45
-rw-r--r--contrib/playlib/test/resources/playsingleapi/app/controllers/HomeController.scala24
-rw-r--r--contrib/playlib/test/resources/playsingleapi/conf/application.conf2
-rw-r--r--contrib/playlib/test/resources/playsingleapi/conf/routes10
-rw-r--r--contrib/playlib/test/resources/playsingleapi/test/controllers/HomeControllerSpec.scala45
-rw-r--r--contrib/playlib/test/src/mill/playlib/PlayModuleTests.scala119
-rw-r--r--contrib/playlib/test/src/mill/playlib/PlaySingleApiModuleTests.scala91
-rw-r--r--contrib/playlib/test/src/mill/playlib/PlaySingleModuleTests.scala98
-rw-r--r--contrib/playlib/test/src/mill/playlib/RouterModuleTests.scala (renamed from contrib/playlib/test/src/mill/playlib/HelloWorldTests.scala)2
-rw-r--r--contrib/playlib/worker/2.6/src/RouteCompilerWorker.scala (renamed from contrib/playlib/worker/2.6.0/src/RouteCompilerWorker.scala)2
-rw-r--r--contrib/playlib/worker/2.7/src/RouteCompilerWorker.scala (renamed from contrib/playlib/worker/2.7.0/src/RouteCompilerWorker.scala)2
-rw-r--r--contrib/scalapblib/src/ScalaPBModule.scala4
-rw-r--r--contrib/scoverage/api/src/ScoverageReportWorkerApi.scala7
-rw-r--r--contrib/scoverage/src/ScoverageModule.scala122
-rw-r--r--contrib/scoverage/src/ScoverageReportWorker.scala39
-rw-r--r--contrib/scoverage/test/resources/hello-world/core/src/Greet.scala6
-rw-r--r--contrib/scoverage/test/src/HelloWorldTests.scala107
-rw-r--r--contrib/scoverage/worker/1.3.1/src/ScoverageReportWorkerImpl.scala21
-rw-r--r--contrib/twirllib/src/TwirlModule.scala4
-rw-r--r--docs/pages/1 - Intro to Mill.md7
-rw-r--r--docs/pages/10 - Thirdparty Modules.md250
-rw-r--r--docs/pages/2 - Configuring Mill.md9
-rw-r--r--docs/pages/9 - Contrib Modules.md904
-rw-r--r--main/client/src/MillClientMain.java46
-rw-r--r--main/core/src/eval/Evaluator.scala4
-rw-r--r--main/core/src/util/JsonFormatters.scala5
-rw-r--r--main/src/MillMain.scala156
-rw-r--r--main/src/main/MainRunner.scala8
-rw-r--r--main/src/main/MillServerMain.scala22
-rw-r--r--main/src/main/ReplApplyHandler.scala20
-rw-r--r--main/src/main/RunScript.scala7
-rw-r--r--main/src/main/VisualizeModule.scala4
-rw-r--r--main/src/modules/Jvm.scala49
-rw-r--r--main/src/modules/Util.scala2
-rw-r--r--main/test/src/main/ClientServerTests.scala6
-rw-r--r--main/test/src/util/ScriptTestSuite.scala15
-rw-r--r--main/test/src/util/TestEvaluator.scala4
-rw-r--r--readme.md22
-rw-r--r--scalajslib/src/ScalaJSModule.scala1
-rw-r--r--scalalib/api/src/ZincWorkerApi.scala2
-rw-r--r--scalalib/resources/mill/scalalib/scalafmt/default.scalafmt.conf0
-rw-r--r--scalalib/src/Dep.scala34
-rwxr-xr-xscalalib/src/GenIdeaImpl.scala11
-rw-r--r--scalalib/src/JavaModule.scala52
-rw-r--r--scalalib/src/Lib.scala2
-rw-r--r--scalalib/src/PublishModule.scala2
-rw-r--r--scalalib/src/ScalaModule.scala9
-rw-r--r--scalalib/src/Versions.scala2
-rw-r--r--scalalib/src/ZincWorkerModule.scala7
-rw-r--r--scalalib/src/dependency/metadata/MavenMetadataLoader.scala5
-rw-r--r--scalalib/src/publish/SonatypeHttpApi.scala116
-rw-r--r--scalalib/src/publish/SonatypePublisher.scala5
-rw-r--r--scalalib/src/publish/settings.scala6
-rw-r--r--scalalib/src/scalafmt/ScalafmtModule.scala16
-rw-r--r--scalalib/src/scalafmt/ScalafmtWorker.scala37
-rw-r--r--scalalib/test/src/GenIdeaTests.scala3
-rw-r--r--scalalib/test/src/HelloWorldTests.scala10
-rw-r--r--scalalib/test/src/ResolveDepsTests.scala3
-rw-r--r--scalalib/test/src/dependency/metadata/MetadataLoaderFactoryTests.scala12
-rw-r--r--scalalib/test/src/dependency/updates/UpdatesFinderTests.scala5
-rw-r--r--scalalib/worker/src/ZincWorkerImpl.scala30
-rw-r--r--scalanativelib/src/ScalaNativeModule.scala7
-rw-r--r--scratch/build.sc28
104 files changed, 3305 insertions, 761 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..4cc299d9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1 @@
+Please don't open issues for questions, but ask in our gitter channel at https://gitter.im/lihaoyi/mill
diff --git a/.travis.yml b/.travis.yml
index f374d72e..e7a73872 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,9 @@ language: scala
sudo: required
dist: trusty
+git:
+ depth: false
+
matrix:
include:
- stage: build
diff --git a/build.sc b/build.sc
index 47f5451d..c70aaf71 100755
--- a/build.sc
+++ b/build.sc
@@ -1,7 +1,7 @@
import $file.ci.shared
import $file.ci.upload
import java.nio.file.attribute.PosixFilePermission
-
+import $ivy.`org.scalaj::scalaj-http:2.4.1`
import ammonite.ops._
import coursier.maven.MavenRepository
import mill._
@@ -93,7 +93,7 @@ object main extends MillModule {
def ivyDeps = Agg(
// Keep synchronized with ammonite in Versions.scala
- ivy"com.lihaoyi:::ammonite:1.6.0",
+ ivy"com.lihaoyi:::ammonite:1.6.7",
// Necessary so we can share the JNA classes throughout the build process
ivy"net.java.dev.jna:jna:4.5.0",
ivy"net.java.dev.jna:jna-platform:4.5.0"
@@ -129,8 +129,8 @@ object main extends MillModule {
def moduleDeps = Seq(main, scalalib)
def ivyDeps = Agg(
- ivy"guru.nidi:graphviz-java:0.2.3",
- ivy"org.jgrapht:jgrapht-core:1.2.0"
+ ivy"guru.nidi:graphviz-java:0.8.3",
+ ivy"org.jgrapht:jgrapht-core:1.3.0"
)
def testArgs = Seq(
"-DMILL_GRAPHVIZ=" + runClasspath().map(_.path).mkString(",")
@@ -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{
@@ -187,7 +188,7 @@ object scalalib extends MillModule {
def ivyDeps = Agg(
// Keep synchronized with zinc in Versions.scala
- ivy"org.scala-sbt::zinc:1.3.0-M1"
+ ivy"org.scala-sbt::zinc:1.2.5"
)
def testArgs = T{Seq(
"-DMILL_SCALA_WORKER=" + runClasspath().map(_.path).mkString(",")
@@ -213,6 +214,7 @@ object scalajslib extends MillModule {
object api extends MillApiModule{
def moduleDeps = Seq(main.core)
+ def ivyDeps = Agg(ivy"org.scala-sbt:test-interface:1.0")
}
object worker extends Cross[WorkerModule]("0.6", "1.0")
class WorkerModule(scalajsBinary: String) extends MillApiModule{
@@ -248,15 +250,14 @@ object contrib extends MillModule {
}
object playlib extends MillModule {
- def moduleDeps = Seq(scalalib, playlib.api)
+ def moduleDeps = Seq(scalalib, twirllib, playlib.api)
def testArgs = T {
val mapping = Map(
- "MILL_PLAYLIB_ROUTECOMPILER_WORKER_2_6_0" -> worker("2.6.0").compile().classes.path,
- "MILL_PLAYLIB_ROUTECOMPILER_WORKER_2_7_0" -> worker("2.7.0").compile().classes.path,
- "MILL_CONTRIB_PLAYLIB_ROUTECOMPILER_WORKER_2_6_0" -> worker("2.6.0").compile().classes.path,
- "MILL_CONTRIB_PLAYLIB_ROUTECOMPILER_WORKER_2_7_0" -> worker("2.7.0").compile().classes.path
+ "MILL_CONTRIB_PLAYLIB_ROUTECOMPILER_WORKER_2_6" -> worker("2.6").compile().classes.path,
+ "MILL_CONTRIB_PLAYLIB_ROUTECOMPILER_WORKER_2_7" -> worker("2.7").compile().classes.path
)
+
scalalib.worker.testArgs() ++
scalalib.backgroundwrapper.testArgs() ++
(for ((k, v) <- mapping.toSeq) yield s"-D$k=$v")
@@ -265,18 +266,17 @@ object contrib extends MillModule {
object api extends MillApiModule {
def moduleDeps = Seq(scalalib)
}
-
- object worker extends Cross[WorkerModule]("2.6.0", "2.7.0")
+ object worker extends Cross[WorkerModule]( "2.6", "2.7")
class WorkerModule(scalajsBinary: String) extends MillApiModule {
def moduleDeps = Seq(playlib.api)
def ivyDeps = scalajsBinary match {
- case "2.6.0" =>
+ case "2.6"=>
Agg(
ivy"com.typesafe.play::routes-compiler::2.6.0"
)
- case "2.7.0" =>
+ case "2.7" =>
Agg(
ivy"com.typesafe.play::routes-compiler::2.7.0"
)
@@ -284,11 +284,36 @@ object contrib extends MillModule {
}
}
-
+
object scalapblib 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?
@@ -303,6 +328,24 @@ object contrib extends MillModule {
def moduleDeps = Seq(scalalib)
def testArgs = Seq("-DMILL_VERSION=" + build.publishVersion()._2)
}
+
+ object flyway extends MillModule {
+ def moduleDeps = Seq(scalalib)
+ def ivyDeps = Agg(ivy"org.flywaydb:flyway-core:5.2.4")
+ }
+
+
+ object docker extends MillModule {
+ def moduleDeps = Seq(scalalib)
+ }
+
+ object bloop extends MillModule {
+ def moduleDeps = Seq(scalalib)
+ def ivyDeps = Agg(
+ ivy"ch.epfl.scala::bloop-config:1.2.5",
+ ivy"com.lihaoyi::ujson-circe:0.7.4"
+ )
+ }
}
@@ -325,6 +368,7 @@ object scalanativelib extends MillModule {
}
object api extends MillApiModule{
def moduleDeps = Seq(main.core)
+ def ivyDeps = Agg(ivy"org.scala-sbt:test-interface:1.0")
}
object worker extends Cross[WorkerModule]("0.3")
class WorkerModule(scalaNativeBinary: String) extends MillApiModule {
@@ -389,9 +433,14 @@ def launcherScript(shellJvmArgs: Seq[String],
shellCommands = {
val jvmArgsStr = shellJvmArgs.mkString(" ")
def java(mainClass: String) =
- s"""exec java $jvmArgsStr $$JAVA_OPTS -cp "${shellClassPath.mkString(":")}" $mainClass "$$@""""
-
- s"""case "$$1" in
+ s"""exec $$JAVACMD $jvmArgsStr $$JAVA_OPTS -cp "${shellClassPath.mkString(":")}" $mainClass "$$@""""
+
+ s"""if [ -z "$$JAVA_HOME" ] ; then
+ | JAVACMD="java"
+ |else
+ | JAVACMD="$$JAVA_HOME/bin/java"
+ |fi
+ |case "$$1" in
| -i | --interactive )
| ${java("mill.MillMain")}
| ;;
@@ -403,9 +452,11 @@ def launcherScript(shellJvmArgs: Seq[String],
cmdCommands = {
val jvmArgsStr = cmdJvmArgs.mkString(" ")
def java(mainClass: String) =
- s"""java $jvmArgsStr %JAVA_OPTS% -cp "${cmdClassPath.mkString(";")}" $mainClass %*"""
+ s""""%JAVACMD%" $jvmArgsStr %JAVA_OPTS% -cp "${cmdClassPath.mkString(";")}" $mainClass %*"""
- s"""if "%1" == "-i" set _I_=true
+ s"""set "JAVACMD=java.exe"
+ |if not "%JAVA_HOME%"=="" set "JAVACMD=%JAVA_HOME%\\bin\\java.exe"
+ |if "%1" == "-i" set _I_=true
|if "%1" == "--interactive" set _I_=true
|if defined _I_ (
| ${java("mill.MillMain")}
@@ -417,7 +468,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/shared.sc b/ci/shared.sc
index a496fd1f..89e504fe 100644
--- a/ci/shared.sc
+++ b/ci/shared.sc
@@ -4,6 +4,7 @@
* via import $file
*/
+import $ivy.`org.scalaj::scalaj-http:2.4.1`
import ammonite.ops.{write, Path, mkdir, RelPath, up}
def argNames(n: Int) = {
diff --git a/ci/test-mill-0.sh b/ci/test-mill-0.sh
index b1dd7e49..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}.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/ci/upload.sc b/ci/upload.sc
index d6aa96e2..740f3b58 100644
--- a/ci/upload.sc
+++ b/ci/upload.sc
@@ -18,12 +18,16 @@ def apply(uploadedFile: Path,
tagName: String,
uploadName: String,
authKey: String): String = {
- val body = Http("https://api.github.com/repos/lihaoyi/mill/releases/tags/" + tagName)
+
+ val response = Http(s"https://api.github.com/repos/lihaoyi/mill/releases/tags/${tagName}")
.header("Authorization", "token " + authKey)
- .asString.body
+ .header("Accept", "application/vnd.github.v3+json")
+ .asString
+ val body = response.body
val parsed = ujson.read(body)
+ println("Response code: " + response.code)
println(body)
val snapshotReleaseId = parsed("id").num.toInt
diff --git a/contrib/bloop/src/mill.contrib.bloop/BloopImpl.scala b/contrib/bloop/src/mill.contrib.bloop/BloopImpl.scala
new file mode 100644
index 00000000..03af2465
--- /dev/null
+++ b/contrib/bloop/src/mill.contrib.bloop/BloopImpl.scala
@@ -0,0 +1,359 @@
+package mill.contrib.bloop
+
+import ammonite.ops._
+import bloop.config.ConfigEncoderDecoders._
+import bloop.config.{Config => BloopConfig}
+import mill._
+import mill.api.Loose
+import mill.define.{Module => MillModule, _}
+import mill.eval.Evaluator
+import mill.scalalib._
+import os.pwd
+
+/**
+ * Implementation of the Bloop related tasks. Inherited by the
+ * `mill.contrib.Bloop` object, and usable in tests by passing
+ * a custom evaluator.
+ */
+class BloopImpl(ev: () => Evaluator, wd: Path) extends ExternalModule { outer =>
+
+ /**
+ * Generates bloop configuration files reflecting the build,
+ * under pwd/.bloop.
+ */
+ def install = T {
+ Task.traverse(computeModules)(_.bloop.writeConfig)
+ }
+
+ /**
+ * Trait that can be mixed-in to quickly access the bloop config
+ * of the module.
+ *
+ * {{{
+ * object myModule extends ScalaModule with Bloop.Module {
+ * ...
+ * }
+ * }}}
+ */
+ trait Module extends MillModule with CirceCompat { self: JavaModule =>
+
+ object bloop extends MillModule {
+ def config = T {
+ new BloopOps(self).bloop.config()
+ }
+ }
+ }
+
+ /**
+ * Extension class used to ensure that the config related tasks are
+ * cached alongside their respective modules, without requesting the user
+ * to extend a specific trait.
+ *
+ * This also ensures that we're not duplicating work between the global
+ * "install" task that traverse all modules in the build, and "local" tasks
+ * that traverse only their transitive dependencies.
+ */
+ private implicit class BloopOps(jm: JavaModule)
+ extends MillModule
+ with CirceCompat {
+ override def millOuterCtx = jm.millOuterCtx
+
+ object bloop extends MillModule {
+ def config = T { outer.bloopConfig(jm) }
+
+ def writeConfig: Target[(String, PathRef)] = T {
+ mkdir(bloopDir)
+ val path = bloopConfigPath(jm)
+ _root_.bloop.config.write(config(), path.toNIO)
+ T.ctx().log.info(s"Wrote $path")
+ name(jm) -> PathRef(path)
+ }
+
+ def writeTransitiveConfig = T {
+ Task.traverse(jm.transitiveModuleDeps)(_.bloop.writeConfig)
+ }
+ }
+ }
+
+ private val bloopDir = wd / ".bloop"
+
+ private def computeModules: Seq[JavaModule] = {
+ val eval = ev()
+ if (eval != null) {
+ val rootModule = eval.rootModule
+ rootModule.millInternal.segmentsToModules.values.collect {
+ case m: scalalib.JavaModule => m
+ }.toSeq
+ } else Seq()
+ }
+
+ /**
+ * Computes sources files paths for the whole project. Cached in a way
+ * that does not get invalidated upon sourcefile change. Mainly called
+ * from module#sources in bloopInstall
+ */
+ def moduleSourceMap: Target[Map[String, Seq[Path]]] = T {
+ val sources = Task.traverse(computeModules) { m =>
+ m.allSources.map { paths =>
+ m.millModuleSegments.render -> paths.map(_.path)
+ }
+ }()
+ sources.toMap
+ }
+
+ protected def name(m: JavaModule) = m.millModuleSegments.render
+
+ protected def bloopConfigPath(module: JavaModule): Path =
+ bloopDir / s"${name(module)}.json"
+
+ //////////////////////////////////////////////////////////////////////////////
+ // SemanticDB related configuration
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Version of the semanticDB plugin.
+ def semanticDBVersion: String = "4.1.4"
+
+ // Scala versions supported by semantic db. Needs to be updated when
+ // bumping semanticDBVersion.
+ // See [https://github.com/scalameta/metals/blob/333ab6fc00fb3542bcabd0dac51b91b72798768a/build.sbt#L121]
+ def semanticDBSupported = Set(
+ "2.12.8",
+ "2.12.7",
+ "2.12.6",
+ "2.12.5",
+ "2.12.4",
+ "2.11.12",
+ "2.11.11",
+ "2.11.10",
+ "2.11.9"
+ )
+
+ // Recommended for metals usage.
+ def semanticDBOptions = List(
+ s"-P:semanticdb:sourceroot:$pwd",
+ "-P:semanticdb:synthetics:on",
+ "-P:semanticdb:failures:warning"
+ )
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Computation of the bloop configuration for a specific module
+ //////////////////////////////////////////////////////////////////////////////
+
+ def bloopConfig(module: JavaModule): Task[BloopConfig.File] = {
+ import _root_.bloop.config.Config
+ def out(m: JavaModule) = bloopDir / "out" / m.millModuleSegments.render
+ def classes(m: JavaModule) = out(m) / "classes"
+
+ val javaConfig =
+ module.javacOptions.map(opts => Some(Config.Java(options = opts.toList)))
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Scalac
+ ////////////////////////////////////////////////////////////////////////////
+
+ val scalaConfig = module match {
+ case s: ScalaModule =>
+ val semanticDb = s.resolveDeps(s.scalaVersion.map {
+ case scalaV if semanticDBSupported(scalaV) =>
+ Agg(ivy"org.scalameta:semanticdb-scalac_$scalaV:$semanticDBVersion")
+ case _ => Agg()
+ })
+
+ T.task {
+ val pluginCp = semanticDb() ++ s.scalacPluginClasspath()
+ val pluginOptions = pluginCp.map { pathRef =>
+ s"-Xplugin:${pathRef.path}"
+ }
+
+ val allScalacOptions =
+ (s.scalacOptions() ++ pluginOptions ++ semanticDBOptions).toList
+ Some(
+ BloopConfig.Scala(
+ organization = "org.scala-lang",
+ name = "scala-compiler",
+ version = s.scalaVersion(),
+ options = allScalacOptions,
+ jars = s.scalaCompilerClasspath().map(_.path.toNIO).toList,
+ analysis = None,
+ setup = None
+ )
+ )
+ }
+ case _ => T.task(None)
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Platform (Jvm/Js/Native)
+ ////////////////////////////////////////////////////////////////////////////
+
+ val platform = T.task {
+ BloopConfig.Platform.Jvm(
+ BloopConfig.JvmConfig(
+ home = T.ctx().env.get("JAVA_HOME").map(s => Path(s).toNIO),
+ options = module.forkArgs().toList
+ ),
+ mainClass = module.mainClass()
+ )
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+ ////////////////////////////////////////////////////////////////////////////
+
+ val testConfig = module match {
+ case m: TestModule =>
+ T.task {
+ Some(
+ BloopConfig.Test(
+ frameworks = m
+ .testFrameworks()
+ .map(f => Config.TestFramework(List(f)))
+ .toList,
+ options = Config.TestOptions(
+ excludes = List(),
+ arguments = List()
+ )
+ )
+ )
+ }
+ case _ => T.task(None)
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Ivy dependencies + sources
+ ////////////////////////////////////////////////////////////////////////////
+
+ val scalaLibraryIvyDeps = module match {
+ case x: ScalaModule => x.scalaLibraryIvyDeps
+ case _ => T.task { Loose.Agg.empty[Dep] }
+ }
+
+ /**
+ * Resolves artifacts using coursier and creates the corresponding
+ * bloop config.
+ */
+ def artifacts(repos: Seq[coursier.Repository],
+ deps: Seq[coursier.Dependency]): List[BloopConfig.Module] = {
+ import coursier._
+ import coursier.util._
+
+ def source(r: Resolution) = Resolution(
+ r.dependencies
+ .map(d =>
+ d.copy(attributes = d.attributes.copy(classifier = coursier.Classifier("sources")))
+ )
+ .toSeq
+ )
+
+ import scala.concurrent.ExecutionContext.Implicits.global
+ val unresolved = Resolution(deps)
+ val fetch = ResolutionProcess.fetch(repos, coursier.cache.Cache.default.fetch)
+ val gatherTask = for {
+ resolved <- unresolved.process.run(fetch)
+ resolvedSources <- source(resolved).process.run(fetch)
+ all = resolved.dependencyArtifacts ++ resolvedSources.dependencyArtifacts
+ gathered <- Gather[Task].gather(all.distinct.map {
+ case (dep, art) => coursier.cache.Cache.default.file(art).run.map(dep -> _)
+ })
+ } yield
+ gathered
+ .collect {
+ case (dep, Right(file)) if Path(file).ext == "jar" =>
+ (dep.module.organization,
+ dep.module.name,
+ dep.version,
+ Option(dep.attributes.classifier).filter(_.nonEmpty),
+ file)
+ }
+ .groupBy {
+ case (org, mod, version, _, _) => (org, mod, version)
+ }
+ .mapValues {
+ _.map {
+ case (_, mod, _, classifier, file) =>
+ BloopConfig.Artifact(mod.value, classifier.map(_.value), None, file.toPath)
+ }.toList
+ }
+ .map {
+ case ((org, mod, version), artifacts) =>
+ BloopConfig.Module(
+ organization = org.value,
+ name = mod.value,
+ version = version,
+ configurations = None,
+ artifacts = artifacts
+ )
+ }
+
+ gatherTask.unsafeRun().toList
+ }
+
+ val bloopResolution: Task[BloopConfig.Resolution] = T.task {
+ val repos = module.repositories
+ val allIvyDeps = module
+ .transitiveIvyDeps() ++ scalaLibraryIvyDeps() ++ module.compileIvyDeps()
+ val coursierDeps =
+ allIvyDeps.map(module.resolveCoursierDependency()).toList
+ BloopConfig.Resolution(artifacts(repos, coursierDeps))
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Classpath
+ ////////////////////////////////////////////////////////////////////////////
+
+ val ivyDepsClasspath =
+ module
+ .resolveDeps(T.task {
+ module.compileIvyDeps() ++ module.transitiveIvyDeps()
+ })
+ .map(_.map(_.path).toSeq)
+
+ def transitiveClasspath(m: JavaModule): Task[Seq[Path]] = T.task {
+ m.moduleDeps.map(classes) ++
+ m.unmanagedClasspath().map(_.path) ++
+ Task.traverse(m.moduleDeps)(transitiveClasspath)().flatten
+ }
+
+ val classpath = T.task(transitiveClasspath(module)() ++ ivyDepsClasspath())
+ val resources = T.task(module.resources().map(_.path.toNIO).toList)
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tying up
+ ////////////////////////////////////////////////////////////////////////////
+
+ val project = T.task {
+ val mSources = moduleSourceMap()
+ .get(name(module))
+ .toSeq
+ .flatten
+ .map(_.toNIO)
+ .toList
+
+ BloopConfig.Project(
+ name = name(module),
+ directory = module.millSourcePath.toNIO,
+ sources = mSources,
+ dependencies = module.moduleDeps.map(name).toList,
+ classpath = classpath().map(_.toNIO).toList,
+ out = out(module).toNIO,
+ classesDir = classes(module).toNIO,
+ resources = Some(resources()),
+ `scala` = scalaConfig(),
+ java = javaConfig(),
+ sbt = None,
+ test = testConfig(),
+ platform = Some(platform()),
+ resolution = Some(bloopResolution())
+ )
+ }
+
+ T.task {
+ BloopConfig.File(
+ version = BloopConfig.File.LatestVersion,
+ project = project()
+ )
+ }
+ }
+
+ lazy val millDiscover = Discover[this.type]
+}
diff --git a/contrib/bloop/src/mill.contrib.bloop/CirceCompat.scala b/contrib/bloop/src/mill.contrib.bloop/CirceCompat.scala
new file mode 100644
index 00000000..bfd88e07
--- /dev/null
+++ b/contrib/bloop/src/mill.contrib.bloop/CirceCompat.scala
@@ -0,0 +1,23 @@
+package mill.contrib.bloop
+
+import io.circe.{Decoder, Encoder, Json}
+import upickle.core.Visitor
+import upickle.default
+
+trait CirceCompat {
+
+ // Converts from a Circe encoder to a uPickle one
+ implicit def circeWriter[T: Encoder]: default.Writer[T] =
+ new default.Writer[T] {
+ override def write0[V](out: Visitor[_, V], v: T) =
+ ujson.circe.CirceJson.transform(Encoder[T].apply(v), out)
+ }
+
+ // Converts from a Circe decoder to a uPickle one
+ implicit def circeReader[T: Decoder]: default.Reader[T] =
+ new default.Reader.Delegate[Json, T](
+ ujson.circe.CirceJson.map(Decoder[T].decodeJson).map(_.right.get))
+
+}
+
+object CirceCompat extends CirceCompat
diff --git a/contrib/bloop/src/mill/contrib/Bloop.scala b/contrib/bloop/src/mill/contrib/Bloop.scala
new file mode 100644
index 00000000..9c85a308
--- /dev/null
+++ b/contrib/bloop/src/mill/contrib/Bloop.scala
@@ -0,0 +1,10 @@
+package mill.contrib
+
+import mill.eval.Evaluator
+import os.pwd
+import mill.contrib.bloop.BloopImpl
+
+/**
+ * Usage : `mill mill.contrib.Bloop/install`
+ */
+object Bloop extends BloopImpl(Evaluator.currentEvaluator.get, pwd)
diff --git a/contrib/bloop/test/src/mill/contrib/bloop/BloopTests.scala b/contrib/bloop/test/src/mill/contrib/bloop/BloopTests.scala
new file mode 100644
index 00000000..dfbb346d
--- /dev/null
+++ b/contrib/bloop/test/src/mill/contrib/bloop/BloopTests.scala
@@ -0,0 +1,103 @@
+package mill.contrib.bloop
+
+import bloop.config.Config.{File => BloopFile}
+import bloop.config.ConfigEncoderDecoders._
+import mill._
+import mill.contrib.bloop.CirceCompat._
+import mill.scalalib._
+import mill.util.{TestEvaluator, TestUtil}
+import os.Path
+import upickle.default._
+import utest._
+
+object BloopTests extends TestSuite {
+
+ val workdir = os.pwd / 'target / 'workspace / "bloop"
+ val testEvaluator = TestEvaluator.static(build)
+ val testBloop = new BloopImpl(() => testEvaluator.evaluator, workdir)
+
+ object build extends TestUtil.BaseModule {
+
+ override def millSourcePath = BloopTests.workdir
+
+ object scalaModule extends scalalib.ScalaModule with testBloop.Module {
+ def scalaVersion = "2.12.8"
+ val bloopVersion = "1.2.5"
+ override def mainClass = Some("foo.bar.Main")
+
+ override def ivyDeps = Agg(
+ ivy"ch.epfl.scala::bloop-config:$bloopVersion"
+ )
+ override def scalacOptions = Seq(
+ "-language:higherKinds"
+ )
+
+ object test extends super.Tests {
+ def testFrameworks = Seq("utest.runner.Framework")
+ }
+ }
+
+ }
+
+ def readBloopConf(jsonFile: String) =
+ read[BloopFile](os.read(workdir / ".bloop" / jsonFile))
+
+ def tests: Tests = Tests {
+ 'genBloopTests - {
+
+ testEvaluator(testBloop.install)
+ val scalaModuleConfig = readBloopConf("scalaModule.json")
+ val testModuleConfig = readBloopConf("scalaModule.test.json")
+
+ 'scalaModule - {
+ val p = scalaModuleConfig.project
+ val name = p.name
+ val sources = p.sources.map(Path(_))
+ val options = p.scala.get.options
+ val version = p.scala.get.version
+ val classpath = p.classpath.map(_.toString)
+ val platform = p.platform.get.name
+ val mainCLass = p.platform.get.mainClass.get
+ val resolution = p.resolution.get.modules
+ val sdb = testBloop.semanticDBVersion
+ val sdbOpts = testBloop.semanticDBOptions
+
+ assert(name == "scalaModule")
+ assert(sources == List(workdir / "scalaModule" / "src"))
+ assert(options.contains("-language:higherKinds"))
+ assert(options.exists(_.contains(s"semanticdb-scalac_2.12.8-$sdb.jar")))
+ assert(sdbOpts.forall(options.contains))
+ assert(version == "2.12.8")
+ assert(classpath.exists(_.contains("bloop-config_2.12-1.2.5.jar")))
+ assert(platform == "jvm")
+ assert(mainCLass == "foo.bar.Main")
+
+ val bloopConfigDep = resolution.find(_.name == "bloop-config_2.12").get
+ val artifacts = bloopConfigDep.artifacts
+ assert(bloopConfigDep.version == build.scalaModule.bloopVersion)
+ assert(bloopConfigDep.organization == "ch.epfl.scala")
+ assert(artifacts.map(_.name).distinct == List("bloop-config_2.12"))
+ assert(artifacts.flatMap(_.classifier).contains("sources"))
+ }
+ 'scalaModuleTest - {
+ val p = testModuleConfig.project
+ val name = p.name
+ val sources = p.sources.map(Path(_))
+ val framework = p.test.get.frameworks.head.names.head
+ val dep = p.dependencies.head
+ val mainModuleClasspath = scalaModuleConfig.project.classpath
+ assert(name == "scalaModule.test")
+ assert(sources == List(workdir / "scalaModule" / "test" / "src"))
+ assert(framework == "utest.runner.Framework")
+ assert(dep == "scalaModule")
+ assert(mainModuleClasspath.forall(p.classpath.contains))
+ }
+ 'configAccessTest - {
+ val (accessedConfig, _) =
+ testEvaluator(build.scalaModule.bloop.config).asSuccess.get.value.right.get
+ assert(accessedConfig == scalaModuleConfig)
+ }
+ }
+ }
+
+}
diff --git a/contrib/buildinfo/src/BuildInfo.scala b/contrib/buildinfo/src/BuildInfo.scala
index fe252b97..0804b9a5 100644
--- a/contrib/buildinfo/src/BuildInfo.scala
+++ b/contrib/buildinfo/src/BuildInfo.scala
@@ -1,8 +1,8 @@
package mill.contrib.buildinfo
import mill.T
-import mill.define.Target
-import mill.api.{Ctx, Logger, PathRef}
+import mill.api.Logger
+import mill.api.PathRef
import mill.scalalib.ScalaModule
trait BuildInfo extends ScalaModule {
@@ -15,33 +15,35 @@ trait BuildInfo extends ScalaModule {
Map.empty[String, String]
}
- private def generateBuildInfo(members: Map[String, Any])(implicit dest: Ctx.Dest, log: Ctx.Log): Seq[PathRef] =
- if (!members.isEmpty) {
- val outputFile = dest.dest / "BuildInfo.scala"
+ def generatedBuildInfo: T[(Seq[PathRef], PathRef)] = T {
+ val logger: Logger = T.ctx().log
+ val members: Map[String, String] = buildInfoMembers()
+ if (members.nonEmpty) {
+ val outputFile = T.ctx().dest / "BuildInfo.scala"
val internalMembers =
members
.map {
case (name, value) => s""" def ${name} = "${value}""""
}
.mkString("\n")
- log.log.debug(s"Generating object [${buildInfoPackageName.map(_ + ".").getOrElse("")}${buildInfoObjectName}] with [${members.size}] members to [${outputFile}]")
+ logger.debug(s"Generating object [${buildInfoPackageName.map(_ + ".").getOrElse("")}${buildInfoObjectName}] with [${members.size}] members to [${outputFile}]")
os.write(
outputFile,
- s"""|${buildInfoPackageName.map(p => s"package ${p}").getOrElse("")}
- |object ${buildInfoObjectName} {
- |$internalMembers
- |}""".stripMargin
+ s"""|${buildInfoPackageName.map(packageName => s"package ${packageName}\n").getOrElse("")}
+ |object ${buildInfoObjectName} {
+ |$internalMembers
+ |}""".stripMargin
)
- Seq(PathRef(outputFile))
+ (Seq(PathRef(outputFile)), PathRef(T.ctx().dest))
} else {
- log.log.debug("No build info member defined, skipping code generation")
- Seq.empty[PathRef]
+ logger.debug("No build info member defined, skipping code generation")
+ (Seq.empty[PathRef], PathRef(T.ctx().dest))
}
-
- def buildInfo = T {
- generateBuildInfo(buildInfoMembers())
}
- override def generatedSources: Target[Seq[PathRef]] = T { super.generatedSources() ++ buildInfo() }
+ override def generatedSources = T {
+ val (_, destPathRef) = generatedBuildInfo()
+ super.generatedSources() :+ destPathRef
+ }
}
diff --git a/contrib/buildinfo/test/src/BuildInfoTests.scala b/contrib/buildinfo/test/src/BuildInfoTests.scala
index c6d0256d..9b840ed9 100644
--- a/contrib/buildinfo/test/src/BuildInfoTests.scala
+++ b/contrib/buildinfo/test/src/BuildInfoTests.scala
@@ -1,15 +1,12 @@
package mill.contrib.buildinfo
-import java.util.jar.JarFile
import mill._
+import mill.define.Sources
import mill.define.Target
-import mill.api.Result._
-import mill.eval.{Evaluator, Result}
-import mill.modules.Assembly
-import mill.scalalib.publish.VersionControl
-import mill.scalalib.publish._
-import mill.util.{TestEvaluator, TestUtil}
-import scala.collection.JavaConverters._
+import mill.scalalib.ScalaModule
+import mill.util.TestEvaluator
+import mill.util.TestUtil
+import os.Path
import utest._
import utest.framework.TestPath
@@ -18,8 +15,9 @@ object BuildInfoTests extends TestSuite {
val scalaVersionString = "2.12.4"
trait BuildInfoModule extends TestUtil.BaseModule with scalalib.ScalaModule with BuildInfo {
- def millSourcePath = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.')
- def scalaVersion = scalaVersionString
+ // override build root to test custom builds/modules
+ override def millSourcePath: Path = TestUtil.getSrcPathStatic()
+ override def scalaVersion = scalaVersionString
}
object EmptyBuildInfo extends BuildInfoModule
@@ -42,16 +40,16 @@ object BuildInfoTests extends TestSuite {
}
}
- val resourcePath = os.pwd / 'contrib / 'buildinfo / 'test / 'resources / "buildinfo"
+ val testModuleSourcesPath: Path = os.pwd / 'contrib / 'buildinfo / 'test / 'resources / "buildinfo"
- def workspaceTest[T](m: TestUtil.BaseModule, resourcePath: os.Path = resourcePath)
+ def workspaceTest[T](m: TestUtil.BaseModule)
(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)
+ os.copy(testModuleSourcesPath, m.millSourcePath)
t(eval)
}
@@ -59,36 +57,37 @@ object BuildInfoTests extends TestSuite {
'buildinfo - {
'createSourcefile - workspaceTest(BuildInfo){ eval =>
- val expected =
+ val expected =
s"""|
|object BuildInfo {
| def scalaVersion = "2.12.4"
|}""".stripMargin
- val Right((result, evalCount)) = eval.apply(BuildInfo.buildInfo)
+ val Right(((result, _), evalCount)) = eval.apply(BuildInfo.generatedBuildInfo)
assert(
- result.head.path == eval.outPath / 'buildInfo / 'dest / "BuildInfo.scala" &&
+ result.head.path == eval.outPath / 'generatedBuildInfo / 'dest / "BuildInfo.scala" &&
os.exists(result.head.path) &&
os.read(result.head.path) == expected
)
}
'notCreateEmptySourcefile - workspaceTest(EmptyBuildInfo){ eval =>
- val Right((result, evalCount)) = eval.apply(EmptyBuildInfo.buildInfo)
+ val Right(((result, _), evalCount)) = eval.apply(EmptyBuildInfo.generatedBuildInfo)
assert(
result.isEmpty &&
- !os.exists(eval.outPath / 'buildInfo / 'dest / "BuildInfo.scala")
+ !os.exists(eval.outPath / 'generatedBuildInfo / 'dest / "BuildInfo.scala")
)
}
'supportCustomSettings - workspaceTest(BuildInfoSettings){ eval =>
- val expected =
+ val expected =
s"""|package foo
+ |
|object bar {
| def scalaVersion = "2.12.4"
|}""".stripMargin
- val Right((result, evalCount)) = eval.apply(BuildInfoSettings.buildInfo)
+ val Right(((result, _), evalCount)) = eval.apply(BuildInfoSettings.generatedBuildInfo)
assert(
- result.head.path == eval.outPath / 'buildInfo / 'dest / "BuildInfo.scala" &&
+ result.head.path == eval.outPath / 'generatedBuildInfo / 'dest / "BuildInfo.scala" &&
os.exists(result.head.path) &&
os.read(result.head.path) == expected
)
@@ -104,7 +103,18 @@ object BuildInfoTests extends TestSuite {
val Right((result, evalCount)) = eval.apply(BuildInfo.run(runResult.toString))
assert(
os.exists(runResult),
- os.read(runResult) == scalaVersionString)
+ os.read(runResult) == scalaVersionString
+ )
+ }
+
+ "generatedSources must be a folder" - workspaceTest(BuildInfo) { eval =>
+ val buildInfoGeneratedSourcesFolder = eval.outPath / 'generatedBuildInfo / 'dest
+ val Right((result, evalCount)) = eval.apply(BuildInfo.generatedSources)
+ assert(
+ result.size == 1,
+ os.isDir(result.head.path),
+ result.head.path == buildInfoGeneratedSourcesFolder
+ )
}
}
}
diff --git a/contrib/docker/src/DockerModule.scala b/contrib/docker/src/DockerModule.scala
new file mode 100644
index 00000000..bbf4d926
--- /dev/null
+++ b/contrib/docker/src/DockerModule.scala
@@ -0,0 +1,73 @@
+import mill.T
+import mill.scalalib.JavaModule
+import os.Shellable.IterableShellable
+
+import scala.collection.immutable._
+
+trait DockerModule { outer: JavaModule =>
+
+ trait DockerConfig extends mill.Module {
+ /**
+ * Tags that should be applied to the built image
+ * In the standard registry/repository:tag format
+ */
+ def tags: T[Seq[String]] = T(List(outer.artifactName()))
+ def labels: T[Map[String, String]] = Map.empty[String, String]
+ def baseImage: T[String] = "gcr.io/distroless/java:latest"
+ def pullBaseImage: T[Boolean] = T(baseImage().endsWith(":latest"))
+ private def baseImageCacheBuster: T[(Boolean, Double)] = T.input {
+ val pull = pullBaseImage()
+ if(pull) (pull, Math.random()) else (pull, 0d)
+ }
+
+ def dockerfile: T[String] = T {
+ val jarName = assembly().path.last
+ val labelRhs = labels()
+ .map { case (k, v) =>
+ val lineBrokenValue = v
+ .replace("\r\n", "\\\r\n")
+ .replace("\n", "\\\n")
+ .replace("\r", "\\\r")
+ s""""$k"="$lineBrokenValue""""
+ }
+ .mkString(" ")
+
+ val labelLine = if(labels().isEmpty) "" else s"LABEL $labelRhs"
+
+ s"""
+ |FROM ${baseImage()}
+ |$labelLine
+ |COPY $jarName /$jarName
+ |ENTRYPOINT ["java", "-jar", "/$jarName"]
+ """.stripMargin
+ }
+
+ final def build = T {
+ val dest = T.ctx().dest
+
+ val asmPath = outer.assembly().path
+ os.copy(asmPath, dest / asmPath.last)
+
+ os.write(dest / "Dockerfile", dockerfile())
+
+ val log = T.ctx().log
+
+ val tagArgs = tags().flatMap(t => List("-t", t))
+
+ val (pull, _) = baseImageCacheBuster()
+ val pullLatestBase = IterableShellable(if(pull) Some("--pull") else None)
+
+ val result = os
+ .proc("docker", "build", tagArgs, pullLatestBase, dest)
+ .call(stdout = os.Inherit, stderr = os.Inherit)
+
+ log.info(s"Docker build completed ${if(result.exitCode == 0) "successfully" else "unsuccessfully"} with ${result.exitCode}")
+ tags()
+ }
+
+ final def push() = T.command {
+ val tags = build()
+ tags.foreach(t => os.proc("docker", "push", t).call(stdout = os.Inherit, stderr = os.Inherit))
+ }
+ }
+} \ No newline at end of file
diff --git a/contrib/flyway/src/FlywayModule.scala b/contrib/flyway/src/FlywayModule.scala
new file mode 100644
index 00000000..6ee9ea0b
--- /dev/null
+++ b/contrib/flyway/src/FlywayModule.scala
@@ -0,0 +1,65 @@
+package mill
+package contrib.flyway
+
+import java.net.URLClassLoader
+
+import mill.scalalib.Lib.resolveDependencies
+import mill.scalalib._
+import org.flywaydb.core.Flyway
+import org.flywaydb.core.api.MigrationVersion
+import org.flywaydb.core.api.logging.LogFactory
+import org.flywaydb.core.internal.configuration.{ConfigUtils => flyway}
+import org.flywaydb.core.internal.info.MigrationInfoDumper
+import org.flywaydb.core.internal.logging.console.ConsoleLog.Level
+import org.flywaydb.core.internal.logging.console.ConsoleLogCreator
+
+import scala.collection.JavaConverters._
+
+
+trait FlywayModule extends JavaModule {
+
+ def flywayUrl: T[String]
+ def flywayUser: T[String] = T("")
+ def flywayPassword: T[String] = T("")
+ def flywayFileLocations: T[Seq[PathRef]] = T(resources().map(pr => PathRef(pr.path / "db" / "migration", pr.quick)))
+ def flywayDriverDeps: T[Agg[Dep]]
+ def jdbcClasspath = T ( resolveDependencies(
+ repositories,
+ Lib.depToDependencyJava(_),
+ flywayDriverDeps()
+ ))
+
+ private def strToOptPair[A](key: String, v: String) =
+ Option(v)
+ .filter(_.nonEmpty)
+ .map(key -> _)
+
+ def flywayInstance = T.worker {
+ val jdbcClassloader = new URLClassLoader(jdbcClasspath().map(_.path.toIO.toURI.toURL).toArray)
+
+ val configProps = Map(flyway.URL -> flywayUrl()) ++
+ strToOptPair(flyway.USER, flywayUser()) ++
+ strToOptPair(flyway.PASSWORD, flywayPassword())
+
+ LogFactory.setLogCreator(new ConsoleLogCreator(Level.INFO))
+
+ Flyway
+ .configure(jdbcClassloader)
+ .locations(flywayFileLocations().map("filesystem:" + _.path): _*)
+ .configuration(configProps.asJava)
+ .load
+ }
+
+ def flywayMigrate() = T.command(flywayInstance().migrate())
+ def flywayClean() = T.command(flywayInstance().clean())
+ def flywayBaseline() = T.command(flywayInstance().baseline())
+ def flywayInfo() = T.command {
+ val log = T.ctx().log
+ val info = flywayInstance().info
+ val current = info.current
+ val currentSchemaVersion = if (current == null) MigrationVersion.EMPTY
+ else current.getVersion
+ log.info("Schema version: " + currentSchemaVersion)
+ log.info(MigrationInfoDumper.dumpToAsciiTable(info.all))
+ }
+}
diff --git a/contrib/flyway/test/resources/db/migration/V1__test_init_migration.sql b/contrib/flyway/test/resources/db/migration/V1__test_init_migration.sql
new file mode 100644
index 00000000..964391a2
--- /dev/null
+++ b/contrib/flyway/test/resources/db/migration/V1__test_init_migration.sql
@@ -0,0 +1,5 @@
+
+create table test_table (
+ id bigserial primary key,
+ field varchar
+); \ No newline at end of file
diff --git a/contrib/flyway/test/src/BuildTest.scala b/contrib/flyway/test/src/BuildTest.scala
new file mode 100644
index 00000000..961e0522
--- /dev/null
+++ b/contrib/flyway/test/src/BuildTest.scala
@@ -0,0 +1,48 @@
+package mill.contrib.flyway
+
+import mill._
+import mill.scalalib._
+import mill.util.{TestEvaluator, TestUtil}
+import utest.{TestSuite, Tests, assert, _}
+
+object BuildTest extends TestSuite {
+ object Build extends TestUtil.BaseModule {
+ object build extends FlywayModule {
+
+ def resources = T.sources(os.pwd / 'contrib / 'flyway / 'test / 'resources)
+
+ def postgres = ivy"com.h2database:h2:1.4.199"
+
+ def flywayUrl = "jdbc:h2:mem:test_db;DB_CLOSE_DELAY=-1"
+ def flywayDriverDeps = Agg(postgres)
+ }
+ }
+
+ def tests = Tests {
+ 'clean - {
+ val eval = new TestEvaluator(Build)
+ val Right((_, count)) = eval(Build.build.flywayClean())
+ assert(count > 0)
+ }
+
+ 'migrate - {
+ val eval = new TestEvaluator(Build)
+ val Right((res, count)) = eval(Build.build.flywayMigrate())
+ assert(
+ count > 0,
+ res == 1
+ )
+ val Right((resAgain, countAgain)) = eval(Build.build.flywayMigrate())
+ assert(
+ countAgain > 0,
+ resAgain == 0
+ )
+ }
+
+ 'info - {
+ val eval = new TestEvaluator(Build)
+ val Right((_, count)) = eval(Build.build.flywayInfo())
+ assert(count > 0)
+ }
+ }
+} \ No newline at end of file
diff --git a/contrib/playlib/api/src/RouteCompilerWorkerApi.scala b/contrib/playlib/api/src/RouteCompilerWorkerApi.scala
index 447dfa46..0cc5f372 100644
--- a/contrib/playlib/api/src/RouteCompilerWorkerApi.scala
+++ b/contrib/playlib/api/src/RouteCompilerWorkerApi.scala
@@ -7,7 +7,7 @@ import mill.api.Result
import mill.scalalib.api.CompilationResult
-trait RouteCompilerWorkerApi {
+private[playlib] trait RouteCompilerWorkerApi {
def compile(files: Seq[Path],
additionalImports: Seq[String],
forwardsRouter: Boolean,
diff --git a/contrib/playlib/api/src/Versions.scala b/contrib/playlib/api/src/Versions.scala
new file mode 100644
index 00000000..8c58f6e0
--- /dev/null
+++ b/contrib/playlib/api/src/Versions.scala
@@ -0,0 +1,7 @@
+package mill
+package playlib
+package api
+object Versions {
+ val PLAY_2_6="2.6"
+ val PLAY_2_7="2.7"
+}
diff --git a/contrib/playlib/src/mill/playlib/Dependencies.scala b/contrib/playlib/src/mill/playlib/Dependencies.scala
new file mode 100644
index 00000000..65a5f455
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/Dependencies.scala
@@ -0,0 +1,24 @@
+package mill
+package playlib
+
+import mill.scalalib._
+
+private [playlib] trait Dependencies extends ScalaModule with Version{
+ def core = T { component("play") }
+ def guice = T { component("play-guice") }
+ def server = T { component("play-server") }
+ def logback = T { component("play-logback") }
+ def evolutions = T { component("play-jdbc-evolutions") }
+ def jdbc = T { component("play-jdbc") }
+ def filters = T { component("filters-helpers") }
+ def ws = T { component("play-ahc-ws") }
+ def caffeine = T { component("play-caffeine-cache") }
+
+ override def ivyDeps = T{
+ super.ivyDeps() ++ Agg(
+ core(),
+ guice(),
+ server(),
+ logback()
+ )}
+}
diff --git a/contrib/playlib/src/mill/playlib/Layout.scala b/contrib/playlib/src/mill/playlib/Layout.scala
new file mode 100644
index 00000000..6d58152a
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/Layout.scala
@@ -0,0 +1,17 @@
+package mill
+package playlib
+
+import mill.scalalib._
+
+private[playlib] trait Layout extends JavaModule{
+
+ def conf = T.sources{ millSourcePath / 'conf }
+ def app = T.sources{ millSourcePath / 'app }
+
+ override def sources = T.sources{ app() }
+ override def resources = T.sources{ conf() }
+}
+
+
+
+
diff --git a/contrib/playlib/src/mill/playlib/PlayModule.scala b/contrib/playlib/src/mill/playlib/PlayModule.scala
new file mode 100644
index 00000000..10b6d40d
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/PlayModule.scala
@@ -0,0 +1,25 @@
+package mill
+package playlib
+
+import mill.scalalib._
+
+import api.Versions
+
+trait PlayApiModule extends Dependencies with Router with Server{
+ trait PlayTests extends super.Tests{
+ def testFrameworks = Seq("org.scalatest.tools.Framework")
+ override def ivyDeps = T{
+ playMinorVersion() match {
+ case Versions.PLAY_2_6=>
+ Agg(ivy"org.scalatestplus.play::scalatestplus-play::3.1.2")
+ case Versions.PLAY_2_7=>
+ Agg(ivy"org.scalatestplus.play::scalatestplus-play::4.0.1")
+ }
+ }
+ override def sources = T.sources{ millSourcePath }
+ }
+
+ def start(args: String*) = T.command{ run(args:_*) }
+
+}
+trait PlayModule extends PlayApiModule with Static with Twirl
diff --git a/contrib/playlib/src/mill/playlib/RouteCompilerWorkerApi.scala b/contrib/playlib/src/mill/playlib/RouteCompilerWorkerApi.scala
index 6be0f3f6..b8612b79 100644
--- a/contrib/playlib/src/mill/playlib/RouteCompilerWorkerApi.scala
+++ b/contrib/playlib/src/mill/playlib/RouteCompilerWorkerApi.scala
@@ -1,13 +1,13 @@
-package mill.playlib
+package mill
+package playlib
import ammonite.ops.Path
-import mill._
import mill.api.{Ctx, Result}
import mill.define.{Discover, ExternalModule, Worker}
import mill.playlib.api.RouteCompilerType
import mill.scalalib.api.CompilationResult
-class RouteCompilerWorker {
+private[playlib] class RouteCompilerWorker {
private var routeCompilerInstanceCache = Option.empty[(Long, mill.playlib.api.RouteCompilerWorkerApi)]
private def bridge(toolsClasspath: Agg[os.Path])
@@ -61,7 +61,7 @@ class RouteCompilerWorker {
}
-object RouteCompilerWorkerModule extends ExternalModule {
+private[playlib] object RouteCompilerWorkerModule extends ExternalModule {
def routeCompilerWorker: Worker[RouteCompilerWorker] = T.worker {
new RouteCompilerWorker()
}
diff --git a/contrib/playlib/src/mill/playlib/Router.scala b/contrib/playlib/src/mill/playlib/Router.scala
new file mode 100644
index 00000000..da3ef1dc
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/Router.scala
@@ -0,0 +1,6 @@
+package mill
+package playlib
+
+private[playlib] trait Router extends RouterModule with Layout {
+ override def routes = T{ conf() }
+}
diff --git a/contrib/playlib/src/mill/playlib/RouterModule.scala b/contrib/playlib/src/mill/playlib/RouterModule.scala
index ea2593aa..c02bd7e1 100644
--- a/contrib/playlib/src/mill/playlib/RouterModule.scala
+++ b/contrib/playlib/src/mill/playlib/RouterModule.scala
@@ -1,22 +1,16 @@
package mill
package playlib
-import coursier.{Cache, MavenRepository}
-import mill.api.Loose
+import coursier.MavenRepository
+import mill.eval.PathRef
import mill.playlib.api.RouteCompilerType
import mill.scalalib.Lib.resolveDependencies
import mill.scalalib._
import mill.scalalib.api._
-trait RouterModule extends mill.Module with ScalaModule {
+trait RouterModule extends ScalaModule with Version {
- def playVersion: T[String]
-
- override def generatedSources = T {
- super.generatedSources() ++ Seq(compileRouter().classes)
- }
-
- def routes = T.sources { millSourcePath / 'routes }
+ def routes: T[Seq[PathRef]] = T.sources { millSourcePath / 'routes }
private def routeFiles = T {
val paths = routes().flatMap(file => os.walk(file.path))
@@ -52,10 +46,10 @@ trait RouterModule extends mill.Module with ScalaModule {
*/
def generatorType: RouteCompilerType = RouteCompilerType.InjectedGenerator
- def routerClasspath: T[Loose.Agg[PathRef]] = T {
+ def routerClasspath: T[Agg[PathRef]] = T {
resolveDependencies(
Seq(
- Cache.ivy2Local,
+ coursier.LocalRepositories.ivy2Local,
MavenRepository("https://repo1.maven.org/maven2")
),
Lib.depToDependency(_, scalaVersion()),
@@ -65,7 +59,7 @@ trait RouterModule extends mill.Module with ScalaModule {
)
}
- final def compileRouter: T[CompilationResult] = T {
+ final def compileRouter: T[CompilationResult] = T.persistent {
T.ctx().log.debug(s"compiling play routes with ${playVersion()} worker")
RouteCompilerWorkerModule.routeCompilerWorker().compile(
toolsClasspath().map(_.path),
@@ -78,21 +72,15 @@ trait RouterModule extends mill.Module with ScalaModule {
T.ctx().dest)
}
- private def playMinorVersion: T[String] = T {
- playVersion().split("\\.").take(2).mkString("", ".", ".0")
- }
-
private def playRouteCompilerWorkerClasspath = T {
- val workerKey = "MILL_CONTRIB_PLAYLIB_ROUTECOMPILER_WORKER_" + playMinorVersion().replace(".",
- "_")
- T.ctx.log.debug(s"classpath worker key: $workerKey")
+ val workerKey = "MILL_CONTRIB_PLAYLIB_ROUTECOMPILER_WORKER_" + playMinorVersion().replace(".", "_")
//While the following seems to work (tests pass), I am not completely
//confident that the strings I used for artifact and resolveFilter are
//actually correct
mill.modules.Util.millProjectModule(
workerKey,
- s"mill-contrib-playlib-worker-${playVersion()}",
+ s"mill-contrib-playlib-worker-${playMinorVersion()}",
repositories,
resolveFilter = _.toString.contains("mill-contrib-playlib-worker")
)
@@ -101,4 +89,12 @@ trait RouterModule extends mill.Module with ScalaModule {
private def toolsClasspath = T {
playRouteCompilerWorkerClasspath() ++ routerClasspath()
}
-} \ No newline at end of file
+
+ def routerClasses = T{
+ Seq(compileRouter().classes)
+ }
+
+ override def generatedSources = T {
+ super.generatedSources() ++ routerClasses()
+ }
+}
diff --git a/contrib/playlib/src/mill/playlib/Server.scala b/contrib/playlib/src/mill/playlib/Server.scala
new file mode 100644
index 00000000..0b7b086a
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/Server.scala
@@ -0,0 +1,24 @@
+package mill
+package playlib
+
+import mill.scalalib._
+
+private[playlib] trait Server extends ScalaModule with Version {
+
+ def nettyServer = T { component("play-netty-server") }
+
+ def akkaHttpServer = T { component("play-akka-http-server") }
+
+ def playServerProvider = T { akkaHttpServer() }
+
+
+ override def runIvyDeps = T {
+ super.runIvyDeps() ++ Agg(playServerProvider())
+ }
+
+ override def mainClass = T { Some("play.core.server.ProdServerStart") }
+}
+
+
+
+
diff --git a/contrib/playlib/src/mill/playlib/SingleModule.scala b/contrib/playlib/src/mill/playlib/SingleModule.scala
new file mode 100644
index 00000000..d98d67f7
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/SingleModule.scala
@@ -0,0 +1,6 @@
+package mill
+package playlib
+
+trait SingleModule extends Module {
+ override def millSourcePath: os.Path = super.millSourcePath / os.up
+}
diff --git a/contrib/playlib/src/mill/playlib/Static.scala b/contrib/playlib/src/mill/playlib/Static.scala
new file mode 100644
index 00000000..e6cd7779
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/Static.scala
@@ -0,0 +1,99 @@
+package mill.playlib
+
+import java.net.URI
+import java.nio.file._
+import java.nio.file.attribute.BasicFileAttributes
+import java.util
+
+import mill.{PathRef, T}
+import mill.scalalib.{Lib, ScalaModule}
+
+trait Static extends ScalaModule {
+ /**
+ * project resources including configuration, webjars and static assets
+ */
+ override def resources = T.sources {
+ super.resources() :+ webJarResources() :+ staticAssets()
+ }
+
+ /**
+ * Resource base path of packaged assets (path they will appear in in the jar)
+ */
+ def assetsPath = T{ "public" }
+
+ /**
+ * Directories to include assets from
+ */
+ def assetSources = T.sources{ millSourcePath / assetsPath() }
+
+ /*
+ Collected static assets for the project
+ */
+ def staticAssets = T {
+ val toPath = os.Path(assetsPath(), T.ctx().dest)
+ assetSources().foreach{ pathRef =>
+ val fromPath = pathRef.path
+ if (os.isDir(fromPath)) {
+ os.walk(fromPath).filter(os.isFile(_)).foreach{ p =>
+ os.copy(p, toPath / p.relativeTo(fromPath), createFolders = true)
+ }
+ }
+ }
+ PathRef(T.ctx().dest)
+ }
+
+ /**
+ * webjar dependencies - created from transitive ivy deps
+ */
+ def webJarDeps = T{
+ transitiveIvyDeps().filter(_.dep.module.organization == "org.webjars")
+ }
+
+ /**
+ * jar files of web jars
+ */
+ def webJars = T{
+ Lib.resolveDependencies(repositories, Lib.depToDependency(_, scalaVersion()), webJarDeps())
+ }
+
+ /**
+ * webjar resources extracted from their source jars with version from path removed
+ */
+ def webJarResources = T {
+ extractWebJars(webJars().toSeq, os.Path(assetsPath(), T.ctx().dest) / 'lib)
+ PathRef(T.ctx().dest)
+ }
+
+ private def extractWebJars(jars: Seq[PathRef], webJarBase: os.Path): Unit = {
+ import scala.collection.JavaConverters._
+ val prefix = "/META-INF/resources/webjars/"
+
+ jars.foreach{ jarRef =>
+ val uri = s"jar:file:${jarRef.path}"
+ val env = Map.empty[String,String].asJava
+
+ val zipFs = FileSystems.newFileSystem(URI.create(uri), env)
+ try {
+ for(root <- zipFs.getRootDirectories.asScala) {
+ Files.walkFileTree(root, util.EnumSet.noneOf(classOf[FileVisitOption]), Int.MaxValue,
+ new SimpleFileVisitor[Path] {
+ override def visitFile(file: Path, attrs: BasicFileAttributes) = {
+ if (file.startsWith(prefix)) {
+ val rel = os.RelPath(file.toString.substring(prefix.length))
+ val toFile = webJarBase / os.RelPath(rel.segments(0) +: rel.segments.drop(2), 0)
+ //println(s"$file -> $toFile")
+ os.makeDir.all(toFile / os.up)
+ Files.copy(file, toFile.toNIO)
+ }
+ FileVisitResult.CONTINUE
+ }
+ }
+ )
+ }
+ }
+ finally {
+ zipFs.close()
+ }
+ }
+ }
+}
diff --git a/contrib/playlib/src/mill/playlib/Twirl.scala b/contrib/playlib/src/mill/playlib/Twirl.scala
new file mode 100644
index 00000000..64da53b3
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/Twirl.scala
@@ -0,0 +1,29 @@
+package mill
+package playlib
+
+import mill.twirllib._
+
+trait Twirl extends TwirlModule with Layout {
+
+ override def twirlSources=T.sources{ app() }
+
+ override def twirlAdditionalImports = Seq(
+ "_root_.play.twirl.api.TwirlFeatureImports._",
+ "_root_.play.twirl.api.TwirlHelperImports._",
+ "_root_.play.twirl.api.Html",
+ "_root_.play.twirl.api.JavaScript",
+ "_root_.play.twirl.api.Txt",
+ "_root_.play.twirl.api.Xml",
+ "models._",
+ "controllers._",
+ "play.api.i18n._",
+ "views.html._",
+ "play.api.templates.PlayMagic._",
+ "play.api.mvc._",
+ "play.api.data._"
+ )
+
+ def twirlOutput = T{Seq(compileTwirl().classes)}
+
+ override def generatedSources = T{ super.generatedSources() ++ twirlOutput() }
+}
diff --git a/contrib/playlib/src/mill/playlib/Version.scala b/contrib/playlib/src/mill/playlib/Version.scala
new file mode 100644
index 00000000..3ec77cde
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/Version.scala
@@ -0,0 +1,18 @@
+package mill
+package playlib
+
+import mill.define.{Target, Task}
+import mill.scalalib._
+
+private[playlib] trait Version extends Module{
+
+ def playVersion: T[String]
+
+ private[playlib] def playMinorVersion: T[String] = T {
+ playVersion().split("\\.").take(2).mkString(".")
+ }
+
+ private[playlib] def component(id: String) = T.task {
+ ivy"com.typesafe.play::$id::${playVersion()}"
+ }
+}
diff --git a/contrib/playlib/test/resources/playmulti/core/app/controllers/HomeController.scala b/contrib/playlib/test/resources/playmulti/core/app/controllers/HomeController.scala
new file mode 100644
index 00000000..818ec635
--- /dev/null
+++ b/contrib/playlib/test/resources/playmulti/core/app/controllers/HomeController.scala
@@ -0,0 +1,24 @@
+package controllers
+
+import javax.inject._
+import play.api._
+import play.api.mvc._
+
+/**
+ * This controller creates an `Action` to handle HTTP requests to the
+ * application's home page.
+ */
+@Singleton
+class HomeController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
+
+ /**
+ * Create an Action to render an HTML page.
+ *
+ * The configuration in the `routes` file means that this method
+ * will be called when the application receives a `GET` request with
+ * a path of `/`.
+ */
+ def index() = Action { implicit request: Request[AnyContent] =>
+ Ok(views.html.index())
+ }
+}
diff --git a/contrib/playlib/test/resources/playmulti/core/app/views/index.scala.html b/contrib/playlib/test/resources/playmulti/core/app/views/index.scala.html
new file mode 100644
index 00000000..68d37fb1
--- /dev/null
+++ b/contrib/playlib/test/resources/playmulti/core/app/views/index.scala.html
@@ -0,0 +1,5 @@
+@()
+
+@main("Welcome to Play") {
+ <h1>Welcome to Play!</h1>
+}
diff --git a/contrib/playlib/test/resources/playmulti/core/app/views/main.scala.html b/contrib/playlib/test/resources/playmulti/core/app/views/main.scala.html
new file mode 100644
index 00000000..808a8b89
--- /dev/null
+++ b/contrib/playlib/test/resources/playmulti/core/app/views/main.scala.html
@@ -0,0 +1,25 @@
+@*
+ * This template is called from the `index` template. This template
+ * handles the rendering of the page header and body tags. It takes
+ * two arguments, a `String` for the title of the page and an `Html`
+ * object to insert into the body of the page.
+ *@
+@(title: String)(content: Html)
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ @* Here's where we render the page title `String`. *@
+ <title>@title</title>
+ <link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
+ <link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
+
+ </head>
+ <body>
+ @* And here's where we render the `Html` object containing
+ * the page content. *@
+ @content
+
+ <script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
+ </body>
+</html>
diff --git a/contrib/playlib/test/resources/playmulti/core/conf/application.conf b/contrib/playlib/test/resources/playmulti/core/conf/application.conf
new file mode 100644
index 00000000..233bcc90
--- /dev/null
+++ b/contrib/playlib/test/resources/playmulti/core/conf/application.conf
@@ -0,0 +1,2 @@
+# https://www.playframework.com/documentation/latest/Configuration
+play.http.secret.key="foobarbaz"
diff --git a/contrib/playlib/test/resources/playmulti/core/conf/routes b/contrib/playlib/test/resources/playmulti/core/conf/routes
new file mode 100644
index 00000000..2ac6b336
--- /dev/null
+++ b/contrib/playlib/test/resources/playmulti/core/conf/routes
@@ -0,0 +1,10 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# https://www.playframework.com/documentation/latest/ScalaRouting
+# ~~~~
+
+# An example controller showing a sample home page
+GET / controllers.HomeController.index
+
+# Map static resources from the /public folder to the /assets URL path
+GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
diff --git a/contrib/playlib/test/resources/playmulti/core/test/controllers/HomeControllerSpec.scala b/contrib/playlib/test/resources/playmulti/core/test/controllers/HomeControllerSpec.scala
new file mode 100644
index 00000000..97947556
--- /dev/null
+++ b/contrib/playlib/test/resources/playmulti/core/test/controllers/HomeControllerSpec.scala
@@ -0,0 +1,45 @@
+package controllers
+
+import org.scalatestplus.play._
+import org.scalatestplus.play.guice._
+import play.api.test._
+import play.api.test.Helpers._
+
+/**
+ * Add your spec here.
+ * You can mock out a whole application including requests, plugins etc.
+ *
+ * For more information, see https://www.playframework.com/documentation/latest/ScalaTestingWithScalaTest
+ */
+class HomeControllerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
+
+ "HomeController GET" should {
+
+ "render the index page from a new instance of controller" in {
+ val controller = new HomeController(stubControllerComponents())
+ val home = controller.index().apply(FakeRequest(GET, "/"))
+
+ status(home) mustBe OK
+ contentType(home) mustBe Some("text/html")
+ contentAsString(home) must include ("Welcome to Play")
+ }
+
+ "render the index page from the application" in {
+ val controller = inject[HomeController]
+ val home = controller.index().apply(FakeRequest(GET, "/"))
+
+ status(home) mustBe OK
+ contentType(home) mustBe Some("text/html")
+ contentAsString(home) must include ("Welcome to Play")
+ }
+
+ "render the index page from the router" in {
+ val request = FakeRequest(GET, "/")
+ val home = route(app, request).get
+
+ status(home) mustBe OK
+ contentType(home) mustBe Some("text/html")
+ contentAsString(home) must include ("Welcome to Play")
+ }
+ }
+}
diff --git a/contrib/playlib/test/resources/playsingle/app/controllers/HomeController.scala b/contrib/playlib/test/resources/playsingle/app/controllers/HomeController.scala
new file mode 100644
index 00000000..818ec635
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingle/app/controllers/HomeController.scala
@@ -0,0 +1,24 @@
+package controllers
+
+import javax.inject._
+import play.api._
+import play.api.mvc._
+
+/**
+ * This controller creates an `Action` to handle HTTP requests to the
+ * application's home page.
+ */
+@Singleton
+class HomeController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
+
+ /**
+ * Create an Action to render an HTML page.
+ *
+ * The configuration in the `routes` file means that this method
+ * will be called when the application receives a `GET` request with
+ * a path of `/`.
+ */
+ def index() = Action { implicit request: Request[AnyContent] =>
+ Ok(views.html.index())
+ }
+}
diff --git a/contrib/playlib/test/resources/playsingle/app/views/index.scala.html b/contrib/playlib/test/resources/playsingle/app/views/index.scala.html
new file mode 100644
index 00000000..68d37fb1
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingle/app/views/index.scala.html
@@ -0,0 +1,5 @@
+@()
+
+@main("Welcome to Play") {
+ <h1>Welcome to Play!</h1>
+}
diff --git a/contrib/playlib/test/resources/playsingle/app/views/main.scala.html b/contrib/playlib/test/resources/playsingle/app/views/main.scala.html
new file mode 100644
index 00000000..808a8b89
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingle/app/views/main.scala.html
@@ -0,0 +1,25 @@
+@*
+ * This template is called from the `index` template. This template
+ * handles the rendering of the page header and body tags. It takes
+ * two arguments, a `String` for the title of the page and an `Html`
+ * object to insert into the body of the page.
+ *@
+@(title: String)(content: Html)
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ @* Here's where we render the page title `String`. *@
+ <title>@title</title>
+ <link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
+ <link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
+
+ </head>
+ <body>
+ @* And here's where we render the `Html` object containing
+ * the page content. *@
+ @content
+
+ <script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
+ </body>
+</html>
diff --git a/contrib/playlib/test/resources/playsingle/conf/application.conf b/contrib/playlib/test/resources/playsingle/conf/application.conf
new file mode 100644
index 00000000..233bcc90
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingle/conf/application.conf
@@ -0,0 +1,2 @@
+# https://www.playframework.com/documentation/latest/Configuration
+play.http.secret.key="foobarbaz"
diff --git a/contrib/playlib/test/resources/playsingle/conf/routes b/contrib/playlib/test/resources/playsingle/conf/routes
new file mode 100644
index 00000000..2ac6b336
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingle/conf/routes
@@ -0,0 +1,10 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# https://www.playframework.com/documentation/latest/ScalaRouting
+# ~~~~
+
+# An example controller showing a sample home page
+GET / controllers.HomeController.index
+
+# Map static resources from the /public folder to the /assets URL path
+GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
diff --git a/contrib/playlib/test/resources/playsingle/test/controllers/HomeControllerSpec.scala b/contrib/playlib/test/resources/playsingle/test/controllers/HomeControllerSpec.scala
new file mode 100644
index 00000000..97947556
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingle/test/controllers/HomeControllerSpec.scala
@@ -0,0 +1,45 @@
+package controllers
+
+import org.scalatestplus.play._
+import org.scalatestplus.play.guice._
+import play.api.test._
+import play.api.test.Helpers._
+
+/**
+ * Add your spec here.
+ * You can mock out a whole application including requests, plugins etc.
+ *
+ * For more information, see https://www.playframework.com/documentation/latest/ScalaTestingWithScalaTest
+ */
+class HomeControllerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
+
+ "HomeController GET" should {
+
+ "render the index page from a new instance of controller" in {
+ val controller = new HomeController(stubControllerComponents())
+ val home = controller.index().apply(FakeRequest(GET, "/"))
+
+ status(home) mustBe OK
+ contentType(home) mustBe Some("text/html")
+ contentAsString(home) must include ("Welcome to Play")
+ }
+
+ "render the index page from the application" in {
+ val controller = inject[HomeController]
+ val home = controller.index().apply(FakeRequest(GET, "/"))
+
+ status(home) mustBe OK
+ contentType(home) mustBe Some("text/html")
+ contentAsString(home) must include ("Welcome to Play")
+ }
+
+ "render the index page from the router" in {
+ val request = FakeRequest(GET, "/")
+ val home = route(app, request).get
+
+ status(home) mustBe OK
+ contentType(home) mustBe Some("text/html")
+ contentAsString(home) must include ("Welcome to Play")
+ }
+ }
+}
diff --git a/contrib/playlib/test/resources/playsingleapi/app/controllers/HomeController.scala b/contrib/playlib/test/resources/playsingleapi/app/controllers/HomeController.scala
new file mode 100644
index 00000000..d9fc4bf2
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingleapi/app/controllers/HomeController.scala
@@ -0,0 +1,24 @@
+package controllers
+
+import javax.inject._
+import play.api._
+import play.api.mvc._
+
+/**
+ * This controller creates an `Action` to handle HTTP requests to the
+ * application's home page.
+ */
+@Singleton
+class HomeController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
+
+ /**
+ * Create an Action to render an HTML page.
+ *
+ * The configuration in the `routes` file means that this method
+ * will be called when the application receives a `GET` request with
+ * a path of `/`.
+ */
+ def index() = Action { implicit request: Request[AnyContent] =>
+ Ok("Welcome to Play")
+ }
+}
diff --git a/contrib/playlib/test/resources/playsingleapi/conf/application.conf b/contrib/playlib/test/resources/playsingleapi/conf/application.conf
new file mode 100644
index 00000000..233bcc90
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingleapi/conf/application.conf
@@ -0,0 +1,2 @@
+# https://www.playframework.com/documentation/latest/Configuration
+play.http.secret.key="foobarbaz"
diff --git a/contrib/playlib/test/resources/playsingleapi/conf/routes b/contrib/playlib/test/resources/playsingleapi/conf/routes
new file mode 100644
index 00000000..2ac6b336
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingleapi/conf/routes
@@ -0,0 +1,10 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# https://www.playframework.com/documentation/latest/ScalaRouting
+# ~~~~
+
+# An example controller showing a sample home page
+GET / controllers.HomeController.index
+
+# Map static resources from the /public folder to the /assets URL path
+GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
diff --git a/contrib/playlib/test/resources/playsingleapi/test/controllers/HomeControllerSpec.scala b/contrib/playlib/test/resources/playsingleapi/test/controllers/HomeControllerSpec.scala
new file mode 100644
index 00000000..97947556
--- /dev/null
+++ b/contrib/playlib/test/resources/playsingleapi/test/controllers/HomeControllerSpec.scala
@@ -0,0 +1,45 @@
+package controllers
+
+import org.scalatestplus.play._
+import org.scalatestplus.play.guice._
+import play.api.test._
+import play.api.test.Helpers._
+
+/**
+ * Add your spec here.
+ * You can mock out a whole application including requests, plugins etc.
+ *
+ * For more information, see https://www.playframework.com/documentation/latest/ScalaTestingWithScalaTest
+ */
+class HomeControllerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
+
+ "HomeController GET" should {
+
+ "render the index page from a new instance of controller" in {
+ val controller = new HomeController(stubControllerComponents())
+ val home = controller.index().apply(FakeRequest(GET, "/"))
+
+ status(home) mustBe OK
+ contentType(home) mustBe Some("text/html")
+ contentAsString(home) must include ("Welcome to Play")
+ }
+
+ "render the index page from the application" in {
+ val controller = inject[HomeController]
+ val home = controller.index().apply(FakeRequest(GET, "/"))
+
+ status(home) mustBe OK
+ contentType(home) mustBe Some("text/html")
+ contentAsString(home) must include ("Welcome to Play")
+ }
+
+ "render the index page from the router" in {
+ val request = FakeRequest(GET, "/")
+ val home = route(app, request).get
+
+ status(home) mustBe OK
+ contentType(home) mustBe Some("text/html")
+ contentAsString(home) must include ("Welcome to Play")
+ }
+ }
+}
diff --git a/contrib/playlib/test/src/mill/playlib/PlayModuleTests.scala b/contrib/playlib/test/src/mill/playlib/PlayModuleTests.scala
new file mode 100644
index 00000000..e862249d
--- /dev/null
+++ b/contrib/playlib/test/src/mill/playlib/PlayModuleTests.scala
@@ -0,0 +1,119 @@
+package mill
+package playlib
+
+import ammonite.ops.{Path, cp, ls, mkdir, pwd, rm, _}
+import mill.util.{TestEvaluator, TestUtil}
+import utest.framework.TestPath
+import utest.{TestSuite, Tests, assert, _}
+
+object PlayModuleTests extends TestSuite {
+
+ object playmulti extends TestUtil.BaseModule{
+ object core extends PlayModule {
+ override def playVersion = T{"2.7.0"}
+ override def twirlVersion = T{"1.4.0"}
+ override def scalaVersion = T{"2.12.8"}
+ object test extends PlayTests
+ override def ivyDeps = T { super.ivyDeps() ++ Agg(ws())}
+ }
+ }
+
+ val resourcePath: Path = pwd / 'contrib / 'playlib / 'test / 'resources / 'playmulti
+
+ def workspaceTest[T, M <: TestUtil.BaseModule](m: M, resourcePath: Path = resourcePath)
+ (t: TestEvaluator => T)
+ (implicit tp: TestPath): T = {
+ val eval = new TestEvaluator(m)
+ rm(m.millSourcePath)
+ rm(eval.outPath)
+ mkdir(m.millSourcePath / up)
+ cp(resourcePath, m.millSourcePath)
+ t(eval)
+ }
+
+ def tests: Tests = Tests {
+ 'playVersion - {
+ 'fromBuild - workspaceTest(playmulti) { eval =>
+ val Right((result, evalCount)) = eval.apply(playmulti.core.playVersion)
+ assert(
+ result == "2.7.0",
+ evalCount > 0
+ )
+ }
+ }
+ 'layout - {
+ 'fromBuild - workspaceTest(playmulti) { eval =>
+ val Right((conf, _)) = eval.apply(playmulti.core.conf)
+ val Right((app, _)) = eval.apply(playmulti.core.app)
+ val Right((sources, _)) = eval.apply(playmulti.core.sources)
+ val Right((resources, _)) = eval.apply(playmulti.core.resources)
+ val Right((testSources, _)) = eval.apply(playmulti.core.test.sources)
+ val Right((testResources, _)) = eval.apply(playmulti.core.test.resources)
+ assert(
+ conf.map(_.path.relativeTo(playmulti.millSourcePath).toString()) == Seq("core/conf"),
+ app.map(_.path.relativeTo(playmulti.millSourcePath).toString()) == Seq("core/app"),
+ sources== app,
+ resources.map(_.path.relativeTo(playmulti.millSourcePath).toString()).contains("core/conf"),
+ testSources.map(_.path.relativeTo(playmulti.millSourcePath).toString()) == Seq("core/test"),
+ testResources.map(_.path.relativeTo(playmulti.millSourcePath).toString()) == Seq("core/test/resources")
+ )
+ }
+ }
+ 'dependencies - {
+ 'fromBuild - workspaceTest(playmulti) { eval =>
+ val Right((deps, evalCount)) = eval.apply(playmulti.core.ivyDeps)
+ val expectedModules = Seq[String](
+ "play",
+ "play-guice",
+ "play-server",
+ "play-logback",
+ "play-ahc-ws"
+ )
+ val outputModules = deps.map(_.dep.module.name.value)
+ assert(
+ outputModules.forall(expectedModules.contains),
+ evalCount > 0
+ )
+ }
+ }
+ 'compile - workspaceTest(playmulti) { eval =>
+ val eitherResult = eval.apply(playmulti.core.compile)
+ val Right((result, evalCount)) = eitherResult
+ val outputFiles = ls.rec(result.classes.path).filter(_.isFile)
+ val expectedClassfiles = Seq[RelPath](
+ RelPath("controllers/HomeController.class"),
+ RelPath("controllers/ReverseAssets.class"),
+ RelPath("controllers/ReverseHomeController.class"),
+ RelPath("controllers/routes.class"),
+ RelPath("controllers/routes$javascript.class"),
+ RelPath("controllers/javascript/ReverseHomeController.class"),
+ RelPath("controllers/javascript/ReverseAssets.class"),
+ RelPath("router/Routes$$anonfun$routes$1.class"),
+ RelPath("router/Routes.class"),
+ RelPath("router/RoutesPrefix$.class"),
+ RelPath("router/RoutesPrefix.class"),
+ RelPath("views/html/index$.class"),
+ RelPath("views/html/index.class"),
+ RelPath("views/html/main$.class"),
+ RelPath("views/html/main.class")
+ ).map(
+ eval.outPath / 'core / 'compile / 'dest / 'classes / _
+ )
+ assert(
+ result.classes.path == eval.outPath / 'core / 'compile / 'dest / 'classes,
+ outputFiles.nonEmpty,
+ outputFiles.forall(expectedClassfiles.contains),
+ outputFiles.size == 15,
+ evalCount > 0
+ )
+
+
+ // don't recompile if nothing changed
+ val Right((_, unchangedEvalCount)) = eval.apply(playmulti.core.compile)
+
+ // FIXME the following test should be uncommented once
+ // https://github.com/lihaoyi/mill/issues/554 is resolved
+ // assert(unchangedEvalCount == 0)
+ }
+ }
+}
diff --git a/contrib/playlib/test/src/mill/playlib/PlaySingleApiModuleTests.scala b/contrib/playlib/test/src/mill/playlib/PlaySingleApiModuleTests.scala
new file mode 100644
index 00000000..df69ca83
--- /dev/null
+++ b/contrib/playlib/test/src/mill/playlib/PlaySingleApiModuleTests.scala
@@ -0,0 +1,91 @@
+package mill.playlib
+
+import ammonite.ops.{Path, cp, ls, mkdir, pwd, rm, _}
+import mill.T
+import mill.util.{TestEvaluator, TestUtil}
+import utest.framework.TestPath
+import utest.{TestSuite, Tests, assert, _}
+
+object PlaySingleApiModuleTests extends TestSuite {
+
+ object playsingleapi extends TestUtil.BaseModule with PlayApiModule with SingleModule{
+ override def playVersion = T{"2.7.0"}
+ def twirlVersion = T{"1.4.0"}
+ override def scalaVersion = T{"2.12.8"}
+ object test extends PlayTests
+ }
+
+ val resourcePath: Path = pwd / 'contrib / 'playlib / 'test / 'resources / "playsingleapi"
+
+ def workspaceTest[T, M <: TestUtil.BaseModule](m: M, resourcePath: Path = resourcePath)
+ (t: TestEvaluator => T)
+ (implicit tp: TestPath): T = {
+ val eval = new TestEvaluator(m)
+ rm(m.millSourcePath)
+ rm(eval.outPath)
+ mkdir(m.millSourcePath / up)
+ cp(resourcePath, m.millSourcePath)
+ t(eval)
+ }
+
+ def tests: Tests = Tests {
+ 'playVersion - {
+ 'fromBuild - workspaceTest(playsingleapi) { eval =>
+ val Right((result, evalCount)) = eval.apply(playsingleapi.playVersion)
+ assert(
+ result == "2.7.0",
+ evalCount > 0
+ )
+ }
+ }
+ 'layout - {
+ 'fromBuild - workspaceTest(playsingleapi) { eval =>
+ val Right((conf, _)) = eval.apply(playsingleapi.conf)
+ val Right((app, _)) = eval.apply(playsingleapi.app)
+ val Right((sources, _)) = eval.apply(playsingleapi.sources)
+ val Right((resources, _)) = eval.apply(playsingleapi.resources)
+ val Right((testSources, _)) = eval.apply(playsingleapi.test.sources)
+ val Right((testResources, _)) = eval.apply(playsingleapi.test.resources)
+ assert(
+ conf.map(_.path.relativeTo(playsingleapi.millSourcePath).toString()) == Seq("conf"),
+ app.map(_.path.relativeTo(playsingleapi.millSourcePath).toString()) == Seq("app"),
+ sources== app,
+ resources== conf,
+ testSources.map(_.path.relativeTo(playsingleapi.millSourcePath).toString()) == Seq("test"),
+ testResources.map(_.path.relativeTo(playsingleapi.millSourcePath).toString()) == Seq("test/resources")
+ )
+ }
+ }
+ 'compile - workspaceTest(playsingleapi) { eval =>
+ val eitherResult = eval.apply(playsingleapi.compile)
+ val Right((result, evalCount)) = eitherResult
+ val outputFiles = ls.rec(result.classes.path).filter(_.isFile)
+ val expectedClassfiles = Seq[RelPath](
+ RelPath("controllers/HomeController.class"),
+ RelPath("controllers/ReverseAssets.class"),
+ RelPath("controllers/ReverseHomeController.class"),
+ RelPath("controllers/routes.class"),
+ RelPath("controllers/routes$javascript.class"),
+ RelPath("controllers/javascript/ReverseHomeController.class"),
+ RelPath("controllers/javascript/ReverseAssets.class"),
+ RelPath("router/Routes$$anonfun$routes$1.class"),
+ RelPath("router/Routes.class"),
+ RelPath("router/RoutesPrefix$.class"),
+ RelPath("router/RoutesPrefix.class")
+ ).map(
+ eval.outPath / 'compile / 'dest / 'classes / _
+ )
+ assert(
+ result.classes.path == eval.outPath / 'compile / 'dest / 'classes,
+ outputFiles.nonEmpty,
+ outputFiles.forall(expectedClassfiles.contains),
+ outputFiles.size == 11,
+ evalCount > 0
+ )
+
+ val Right((_, unchangedEvalCount)) = eval.apply(playsingleapi.compile)
+
+ assert(unchangedEvalCount == 0)
+ }
+ }
+}
diff --git a/contrib/playlib/test/src/mill/playlib/PlaySingleModuleTests.scala b/contrib/playlib/test/src/mill/playlib/PlaySingleModuleTests.scala
new file mode 100644
index 00000000..e139ae16
--- /dev/null
+++ b/contrib/playlib/test/src/mill/playlib/PlaySingleModuleTests.scala
@@ -0,0 +1,98 @@
+package mill.playlib
+
+import ammonite.ops.{Path, cp, ls, mkdir, pwd, rm, _}
+import mill.T
+import mill.util.{TestEvaluator, TestUtil}
+import utest.framework.TestPath
+import utest.{TestSuite, Tests, assert, _}
+
+object PlaySingleModuleTests extends TestSuite {
+
+ object playsingle extends TestUtil.BaseModule with PlayModule with SingleModule{
+ override def playVersion = T{"2.7.0"}
+ def twirlVersion = T{"1.4.0"}
+ override def scalaVersion = T{"2.12.8"}
+ object test extends PlayTests
+ }
+
+ val resourcePath: Path = pwd / 'contrib / 'playlib / 'test / 'resources / "playsingle"
+
+ def workspaceTest[T, M <: TestUtil.BaseModule](m: M, resourcePath: Path = resourcePath)
+ (t: TestEvaluator => T)
+ (implicit tp: TestPath): T = {
+ val eval = new TestEvaluator(m)
+ rm(m.millSourcePath)
+ rm(eval.outPath)
+ mkdir(m.millSourcePath / up)
+ cp(resourcePath, m.millSourcePath)
+ t(eval)
+ }
+
+ def tests: Tests = Tests {
+ 'playVersion - {
+ 'fromBuild - workspaceTest(playsingle) { eval =>
+ val Right((result, evalCount)) = eval.apply(playsingle.playVersion)
+ assert(
+ result == "2.7.0",
+ evalCount > 0
+ )
+ }
+ }
+ 'layout - {
+ 'fromBuild - workspaceTest(playsingle) { eval =>
+ val Right((conf, _)) = eval.apply(playsingle.conf)
+ val Right((app, _)) = eval.apply(playsingle.app)
+ val Right((sources, _)) = eval.apply(playsingle.sources)
+ val Right((resources, _)) = eval.apply(playsingle.resources)
+ val Right((testSources, _)) = eval.apply(playsingle.test.sources)
+ val Right((testResources, _)) = eval.apply(playsingle.test.resources)
+ assert(
+ conf.map(_.path.relativeTo(playsingle.millSourcePath).toString()) == Seq("conf"),
+ app.map(_.path.relativeTo(playsingle.millSourcePath).toString()) == Seq("app"),
+ sources == app,
+ resources.map(_.path.relativeTo(playsingle.millSourcePath).toString()).contains("conf"),
+ testSources.map(_.path.relativeTo(playsingle.millSourcePath).toString()) == Seq("test"),
+ testResources.map(_.path.relativeTo(playsingle.millSourcePath).toString()) == Seq("test/resources")
+ )
+ }
+ }
+ 'compile - workspaceTest(playsingle) { eval =>
+ val eitherResult = eval.apply(playsingle.compile)
+ val Right((result, evalCount)) = eitherResult
+ val outputFiles = ls.rec(result.classes.path).filter(_.isFile)
+ val expectedClassfiles = Seq[RelPath](
+ RelPath("controllers/HomeController.class"),
+ RelPath("controllers/ReverseAssets.class"),
+ RelPath("controllers/ReverseHomeController.class"),
+ RelPath("controllers/routes.class"),
+ RelPath("controllers/routes$javascript.class"),
+ RelPath("controllers/javascript/ReverseHomeController.class"),
+ RelPath("controllers/javascript/ReverseAssets.class"),
+ RelPath("router/Routes$$anonfun$routes$1.class"),
+ RelPath("router/Routes.class"),
+ RelPath("router/RoutesPrefix$.class"),
+ RelPath("router/RoutesPrefix.class"),
+ RelPath("views/html/index$.class"),
+ RelPath("views/html/index.class"),
+ RelPath("views/html/main$.class"),
+ RelPath("views/html/main.class")
+ ).map(
+ eval.outPath / 'compile / 'dest / 'classes / _
+ )
+ assert(
+ result.classes.path == eval.outPath / 'compile / 'dest / 'classes,
+ outputFiles.nonEmpty,
+ outputFiles.forall(expectedClassfiles.contains),
+ outputFiles.size == 15,
+ evalCount > 0
+ )
+
+ // don't recompile if nothing changed
+ val Right((_, unchangedEvalCount)) = eval.apply(playsingle.compile)
+
+ // FIXME the following test should be uncommented once
+ // https://github.com/lihaoyi/mill/issues/554 is resolved
+ // assert(unchangedEvalCount == 0)
+ }
+ }
+}
diff --git a/contrib/playlib/test/src/mill/playlib/HelloWorldTests.scala b/contrib/playlib/test/src/mill/playlib/RouterModuleTests.scala
index 54e8a057..1aeed019 100644
--- a/contrib/playlib/test/src/mill/playlib/HelloWorldTests.scala
+++ b/contrib/playlib/test/src/mill/playlib/RouterModuleTests.scala
@@ -7,7 +7,7 @@ import mill.util.{TestEvaluator, TestUtil}
import utest.framework.TestPath
import utest.{TestSuite, Tests, assert, _}
-object HelloWorldTests extends TestSuite {
+object RouterModuleTests extends TestSuite {
trait HelloBase extends TestUtil.BaseModule {
override def millSourcePath: Path = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.')
diff --git a/contrib/playlib/worker/2.6.0/src/RouteCompilerWorker.scala b/contrib/playlib/worker/2.6/src/RouteCompilerWorker.scala
index be911af8..83de5e42 100644
--- a/contrib/playlib/worker/2.6.0/src/RouteCompilerWorker.scala
+++ b/contrib/playlib/worker/2.6/src/RouteCompilerWorker.scala
@@ -14,7 +14,7 @@ import play.routes.compiler.RoutesCompiler.RoutesCompilerTask
import play.routes.compiler._
-class RouteCompilerWorker extends RouteCompilerWorkerApi {
+private[playlib] class RouteCompilerWorker extends RouteCompilerWorkerApi {
override def compile(files: Seq[Path],
additionalImports: Seq[String],
diff --git a/contrib/playlib/worker/2.7.0/src/RouteCompilerWorker.scala b/contrib/playlib/worker/2.7/src/RouteCompilerWorker.scala
index 616ab943..3e5a1b68 100644
--- a/contrib/playlib/worker/2.7.0/src/RouteCompilerWorker.scala
+++ b/contrib/playlib/worker/2.7/src/RouteCompilerWorker.scala
@@ -14,7 +14,7 @@ import play.routes.compiler.RoutesCompiler.RoutesCompilerTask
import play.routes.compiler.{InjectedRoutesGenerator, RoutesCompilationError, RoutesCompiler, RoutesGenerator}
-class RouteCompilerWorker extends RouteCompilerWorkerApi {
+private[playlib] class RouteCompilerWorker extends RouteCompilerWorkerApi {
override def compile(files: Seq[Path],
additionalImports: Seq[String],
diff --git a/contrib/scalapblib/src/ScalaPBModule.scala b/contrib/scalapblib/src/ScalaPBModule.scala
index 57bfdd40..00b977ce 100644
--- a/contrib/scalapblib/src/ScalaPBModule.scala
+++ b/contrib/scalapblib/src/ScalaPBModule.scala
@@ -1,7 +1,7 @@
package mill
package contrib.scalapblib
-import coursier.{Cache, MavenRepository}
+import coursier.MavenRepository
import coursier.core.Version
import mill.define.Sources
import mill.api.PathRef
@@ -51,7 +51,7 @@ trait ScalaPBModule extends ScalaModule {
def scalaPBClasspath: T[Loose.Agg[PathRef]] = T {
resolveDependencies(
Seq(
- Cache.ivy2Local,
+ coursier.LocalRepositories.ivy2Local,
MavenRepository("https://repo1.maven.org/maven2")
),
Lib.depToDependency(_, "2.12.4"),
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..8c1735a0
--- /dev/null
+++ b/contrib/scoverage/src/ScoverageModule.scala
@@ -0,0 +1,122 @@
+package mill
+package contrib
+package scoverage
+
+import coursier.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(coursier.LocalRepositories.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/contrib/twirllib/src/TwirlModule.scala b/contrib/twirllib/src/TwirlModule.scala
index 22e4a43a..72887019 100644
--- a/contrib/twirllib/src/TwirlModule.scala
+++ b/contrib/twirllib/src/TwirlModule.scala
@@ -1,7 +1,7 @@
package mill
package twirllib
-import coursier.{Cache, MavenRepository}
+import coursier.MavenRepository
import mill.define.Sources
import mill.api.PathRef
import mill.scalalib.Lib.resolveDependencies
@@ -22,7 +22,7 @@ trait TwirlModule extends mill.Module {
def twirlClasspath: T[Loose.Agg[PathRef]] = T {
resolveDependencies(
Seq(
- Cache.ivy2Local,
+ coursier.LocalRepositories.ivy2Local,
MavenRepository("https://repo1.maven.org/maven2")
),
Lib.depToDependency(_, "2.12.4"),
diff --git a/docs/pages/1 - Intro to Mill.md b/docs/pages/1 - Intro to Mill.md
index ea0bac7a..7bd3ac6d 100644
--- a/docs/pages/1 - Intro to Mill.md
+++ b/docs/pages/1 - Intro to Mill.md
@@ -44,7 +44,7 @@ pkg install mill
### Windows
To get started, download Mill from:
-https://github.com/lihaoyi/mill/releases/download/0.3.6/0.3.6, and save it as
+https://github.com/lihaoyi/mill/releases/download/0.3.9/0.3.9, and save it as
`mill.bat`.
If you're using [Scoop](https://scoop.sh) you can install Mill via
@@ -81,7 +81,7 @@ To get started, download Mill and install it into your system via the following
`curl`/`chmod` command:
```bash
-sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/lihaoyi/mill/releases/download/0.3.6/0.3.6) > /usr/local/bin/mill && chmod +x /usr/local/bin/mill'
+sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/lihaoyi/mill/releases/download/0.3.9/0.3.9) > /usr/local/bin/mill && chmod +x /usr/local/bin/mill'
```
### Development Releases
@@ -739,6 +739,9 @@ object foo extends ScalaModule with PublishModule {
}
```
+You can change the name of the published artifact (artifactId in the Maven POM)
+by overriding `artifactName` in the module you want to publish.
+
You can download an example project with this layout here:
- [Example 2](example-2.zip)
diff --git a/docs/pages/10 - Thirdparty Modules.md b/docs/pages/10 - Thirdparty Modules.md
new file mode 100644
index 00000000..b98b9f67
--- /dev/null
+++ b/docs/pages/10 - Thirdparty Modules.md
@@ -0,0 +1,250 @@
+
+The modules (aka plugins) in this section are developed/maintained outside the mill git tree.
+
+Besides the documentation provided here, we urge you to consult the respective linked plugin documentation pages.
+The usage examples given here are most probably incomplete and sometimes outdated.
+
+If you develop or maintain a mill plugin, please create a [pull request](https://github.com/lihaoyi/mill/pulls) to get your plugin listed here.
+
+[comment]: # (Please keep list of plugins in alphabetical order)
+
+## DGraph
+
+Show transitive dependencies of your build in your browser.
+
+Project home: https://github.com/ajrnz/mill-dgraph
+
+### Quickstart
+
+```scala
+import $ivy.`com.github.ajrnz::mill-dgraph:0.2.0`
+```
+
+```sh
+sh> mill plugin.dgraph.browseDeps(proj)()
+```
+
+## Ensime
+
+Create an [.ensime](http://ensime.github.io/ "ensime") file for your build.
+
+Project home: https://github.com/yyadavalli/mill-ensime
+
+### Quickstart
+
+```scala
+import $ivy.`fun.valycorp::mill-ensime:0.0.1`
+```
+
+```sh
+sh> mill fun.valycorp.mill.GenEnsime/ensimeConfig
+```
+
+## Integration Testing Mill Plugins
+
+Integration testing for mill plugins.
+
+### Quickstart
+
+We assume, you have a mill plugin named `mill-demo`
+
+```scala
+// build.sc
+import mill._, mill.scalalib._
+object demo extends ScalaModule with PublishModule {
+ // ...
+}
+```
+
+Add an new test sub-project, e.g. `it`.
+
+```scala
+import $ivy.`de.tototec::de.tobiasroeser.mill.integrationtest:0.1.0`
+import de.tobiasroeser.mill.integrationtest._
+
+object it extends MillIntegrationTest {
+
+ def millTestVersion = "{exampleMillVersion}"
+
+ def pluginsUnderTest = Seq(demo)
+
+}
+```
+
+Your project should now look similar to this:
+
+```text
+.
++-- demo/
+| +-- src/
+|
++-- it/
+ +-- src/
+ +-- 01-first-test/
+ | +-- build.sc
+ | +-- src/
+ |
+ +-- 02-second-test/
+ +-- build.sc
+```
+
+As the buildfiles `build.sc` in your test cases typically want to access the locally built plugin(s),
+the plugins publishes all plugins referenced under `pluginsUnderTest` to a temporary ivy repository, just before the test is executed.
+The mill version used in the integration test then used that temporary ivy repository.
+
+Instead of referring to your plugin with `import $ivy.'your::plugin:version'`,
+you can use the following line instead, which ensures you will use the correct locally build plugins.
+
+```scala
+// build.sc
+import $exec.plugins
+```
+
+Effectively, at execution time, this line gets replaced by the content of `plugins.sc`, a file which was generated just before the test started to execute.
+
+### Configuration and Targets
+
+The mill-integrationtest plugin provides the following targets.
+
+#### Mandatory configuration
+
+* `def millTestVersion: T[String]`
+ The mill version used for executing the test cases.
+ Used by `downloadMillTestVersion` to automatically download.
+
+* `def pluginsUnderTest: Seq[PublishModule]` -
+ The plugins used in the integration test.
+ You should at least add your plugin under test here.
+ You can also add additional libraries, e.g. those that assist you in the test result validation (e.g. a local test support project).
+ The defined modules will be published into a temporary ivy repository before the tests are executed.
+ In your test `build.sc` file, instead of the typical `import $ivy.` line,
+ you should use `import $exec.plugins` to include all plugins that are defined here.
+
+#### Optional configuration
+
+* `def sources: Sources` -
+ Locations where integration tests are located.
+ Each integration test is a sub-directory, containing a complete test mill project.
+
+* `def testCases: T[Seq[PathRef]]` -
+ The directories each representing a mill test case.
+ Derived from `sources`.
+
+* `def testTargets: T[Seq[String]]` -
+ The targets which are called to test the project.
+ Defaults to `verify`, which should implement test result validation.
+
+* `def downloadMillTestVersion: T[PathRef]` -
+ Download the mill version as defined by `millTestVersion`.
+ Override this, if you need to use a custom built mill version.
+ Returns the `PathRef` to the mill executable (must have the executable flag).
+
+#### Commands
+
+* `def test(): Command[Unit]` -
+ Run the integration tests.
+
+
+## JBake
+
+Create static sites/blogs with JBake.
+
+Plugin home: https://github.com/lefou/mill-jbake
+
+JBake home: https://jbake.org
+
+### Quickstart
+
+```scala
+// build.sc
+import mill._
+import $ivy.`de.tototec::de.tobiasroeser.mill.jbake:0.1.0`
+import de.tobiasroeser.mill.jbake._
+
+object site extends JBakeModule {
+
+ def jbakeVersion = "2.6.4"
+
+}
+```
+
+Generate the site:
+
+```sh
+bash> mill site.jbake
+```
+
+Start a local Web-Server on Port 8820 with the generated site:
+
+```sh
+bash> mill site.jbakeServe
+```
+
+
+## OSGi
+
+Produce OSGi Bundles with mill.
+
+Project home: https://github.com/lefou/mill-osgi
+
+### Quickstart
+
+```scala
+import mill._, mill.scalalib._
+import $ivy.`de.tototec::de.tobiasroeser.mill.osgi:0.0.5`
+import de.tobiasroeser.mill.osgi._
+
+object project extends ScalaModule with OsgiBundleModule {
+
+ def bundleSymbolicName = "com.example.project"
+
+ def osgiHeaders = T{ super.osgiHeaders().copy(
+ `Export-Package` = Seq("com.example.api"),
+ `Bundle-Activator` = Some("com.example.internal.Activator")
+ )}
+
+ // other settings ...
+
+}
+```
+
+
+## PublishM2
+
+Mill plugin to publish artifacts into a local Maven repository.
+
+Project home: https://github.com/lefou/mill-publishM2
+
+### Quickstart
+
+Just mix-in the `PublishM2Module` into your project.
+`PublishM2Module` already extends mill's built-in `PublishModule`.
+
+File: `build.sc`
+```scala
+import mill._, scalalib._, publish._
+
+import $ivy.`de.tototec::de.tobiasroeser.mill.publishM2:0.0.1`
+import de.tobiasroeser.mill.publishM2._
+
+object project extends PublishModule with PublishM2Module {
+ // ...
+}
+```
+
+Publishing to default local Maven repository
+
+```bash
+> mill project.publishM2Local
+[40/40] project.publishM2Local
+Publishing to /home/user/.m2/repository
+```
+
+Publishing to custom local Maven repository
+
+```bash
+> mill project.publishM2Local /tmp/m2repo
+[40/40] project.publishM2Local
+Publishing to /tmp/m2repo
+```
+
diff --git a/docs/pages/2 - Configuring Mill.md b/docs/pages/2 - Configuring Mill.md
index f5a4a24e..f6ca86a0 100644
--- a/docs/pages/2 - Configuring Mill.md
+++ b/docs/pages/2 - Configuring Mill.md
@@ -60,6 +60,9 @@ dependencies; for Java dependencies you would use a single `:` e.g.
against the full Scala version (eg. `2.12.4` instead of just `2.12`),
you can use `:::` as in `ivy"org.scalamacros:::paradise:2.1.1"`.
+To select the test-jars from a dependency use the following syntax:
+`ivy"org.apache.spark::spark-sql:2.4.0;classifier=tests`.
+
By default these are resolved from maven central, but you can add your own
resolvers by overriding the `repositories` definition in the module:
@@ -234,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/docs/pages/9 - Contrib Modules.md b/docs/pages/9 - Contrib Modules.md
index 3cf9e7a8..36eb40ef 100644
--- a/docs/pages/9 - Contrib Modules.md
+++ b/docs/pages/9 - Contrib Modules.md
@@ -1,6 +1,69 @@
-## Contrib Modules
-### BuildInfo
+The plugins in this section are developed/maintained in the mill git tree.
+
+When using one of these, you should make sure to use the versions that matches your mill version.
+
+[comment]: # (Please keep list of plugins in alphabetical order)
+
+## Bloop
+
+This plugin generates [bloop](https://scalacenter.github.io/bloop/) configuration
+from your build file, which lets you use the bloop CLI for compiling, and makes
+your scala code editable in [Metals](https://scalameta.org/metals/)
+
+
+### Quickstart
+```scala
+// build.sc (or any other .sc file it depends on, including predef)
+// Don't forget to replace VERSION
+import $ivy.`com.lihaoyi::mill-contrib-bloop:VERSION`
+```
+
+Then in your terminal :
+
+```
+> mill mill.contrib.Bloop/install
+```
+
+### Mix-in
+
+You can mix-in the `Bloop.Module` trait with any JavaModule to quickly access
+the deserialised configuration for that particular module:
+
+```scala
+// build.sc
+import mill._
+import mill.scalalib._
+import mill.contrib.Bloop
+
+object MyModule extends ScalaModule with Bloop.Module {
+ def myTask = T { bloop.config() }
+}
+```
+
+### Note regarding metals
+
+Generating the bloop config should be enough for metals to pick it up and for
+features to start working in vscode (or the bunch of other editors metals supports).
+However, note that this applies only to your project sources. Your mill/ammonite related
+`.sc` files are not yet supported by metals.
+
+The generated bloop config references the semanticDB compiler plugin required by
+metals to function. If need be, the version of semanticDB can be overriden by
+extending `mill.contrib.bloop.BloopImpl` in your own space.
+
+### Note regarding current mill support in bloop
+
+The mill-bloop integration currently present in the [bloop codebase](https://github.com/scalacenter/bloop/blob/master/integrations/mill-bloop/src/main/scala/bloop/integrations/mill/MillBloop.scala#L10)
+will be deprecated in favour of this implementation.
+
+### Caveats
+
+At this time, only Java/ScalaModule are processed correctly. ScalaJS/ScalaNative integration will
+be added in a near future.
+
+
+## BuildInfo
Generate scala code from your buildfile.
This plugin generates a single object containing information from your build.
@@ -25,7 +88,7 @@ object project extends BuildInfo {
}
```
-#### Configuration options
+### Configuration options
* `def buildInfoMembers: T[Map[String, String]]`
The map containing all member names and values for the generated info object.
@@ -35,8 +98,437 @@ 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
+
+Enables you to configure and run [Flyway](https://flywaydb.org/) commands from your mill build file.
+The flyway module currently supports the most common flyway use cases with file based migrations.
+
+Configure flyway by overriding settings in your module. For example
+
+```scala
+// build.sc
+
+// You have to replace VERSION
+import mill._, scalalib._
+
+import ivy`com.lihaoyi::mill-contrib-flyway:VERSION`
+import contrib.flyway.FlywayModule
+
+object foo extends ScalaModule with FlywayModule {
+ def scalaVersion = "2.12.8"
+
+ //region flyway
+ def flywayUrl = "jdbc:postgresql:myDb" // required
+ def flywayDriverDeps = Agg(ivy"org.postgresql:postgresql:42.2.5") // required
+ def flywayUser = "postgres" // optional
+ // def flywayPassword = "" // optional
+ //endregion
+}
+```
+
+Flyway will look for migration files in `db/migration` in all resources folders by default.
+This should work regardless of if you are using a mill or sbt project layout.
+
+You can then run common flyway commands like
+
+```
+mill foo.flywayClean
+mill foo.flywayInfo
+mill foo.flywayMigrate
+```
+
+> REMINDER:
+> You should never hard-code credentials or check them into a version control system.
+> You should write some code to populate the settings for flyway instead.
+> For example `def flywayPassword = T.input(T.ctx().env("FLYWAY_PASSWORD"))`
+
+## Play Framework
+
+This module adds basic Play Framework support to mill:
+
+* configures mill for Play default directory layout,
+* integrates the Play routes compiler,
+* provides helpers for commonly used framework libraries,
+* optionally: integrates the Twirl template engine,
+* optionally: configures mill for single module play applications.
+
+There is no specific Play Java support, building a Play Java application will require a bit
+of customization (mostly adding the proper dependencies).
+
+### Using the plugin
+
+There are 2 base modules and 2 helper traits in this plugin, all of which can be found
+ in `mill.playlib`.
+
+The base modules:
+
+* `PlayModule` applies the default Play configuration (layout, dependencies, routes compilation,
+Twirl compilation and Akka HTTP server)
+* `PlayApiModule` applies the default Play configuration without `Twirl` templating. This is useful
+if your Play app is a pure API server or if you want to use a different templating engine.
+
+The two helper traits:
+
+* `SingleModule` can be useful to configure mill for a single module Play application such as the
+[play-scala-seed project](https://github.com/playframework/play-scala-seed.g8). Mill is
+multi-module by default and requires a bit more configuration to have source, resource, and test
+directories at the top level alongside the `build.sc` file. This trait takes care of that (See
+[Using SingleModule](#using-singlemodule) below).
+* `RouterModule` allows you to use the Play router without the rest of the configuration (see
+[Using the router module directly](#using-the-router-module-directly).)
+
+### Using `PlayModule`
+
+In order to use the `PlayModule` for your application, you need to provide the scala, Play and
+Twirl versions. You also need to define your own test object which extends the provided
+`PlayTests` trait.
+
+```scala
+// build.sc
+import mill._
+// You have to replace VERSION
+import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
+
+
+object core extends PlayModule {
+ //config
+ override def scalaVersion= T{"2.12.8"}
+ override def playVersion= T{"2.7.0"}
+ override def twirlVersion= T{"1.4.0"}
+
+ object test extends PlayTests
+}
+```
+
+Using the above definition, your build will be configured to use the default Play layout:
+
+```text
+.
+├── build.sc
+└── core
+ ├── app
+ │   ├── controllers
+ │   └── views
+ ├── conf
+ │   └── application.conf
+ │   └── routes
+ │   └── ...
+ ├── logs
+ ├── public
+ │   ├── images
+ │   ├── javascripts
+ │   └── stylesheets
+ └── test
+ └── controllers
+```
+
+The following compile dependencies will automatically be added to your build:
+
+```
+ivy"com.typesafe.play::play:${playVersion()}",
+ivy"com.typesafe.play::play-guice:${playVersion()}",
+ivy"com.typesafe.play::play-server:${playVersion()}",
+ivy"com.typesafe.play::play-logback:${playVersion()}"
+```
+
+Scala test will be setup as the default test framework and the following test dependencies will be
+added (the actual version depends on the version of Play you are pulling `2.6.x` or `2.7.x`):
+
+```
+ivy"org.scalatestplus.play::scalatestplus-play::4.0.1"
+```
+
+In order to have a working `start` command the following runtime dependency is also added:
+
+```
+ivy"com.typesafe.play::play-akka-http-server:${playVersion()}"
+```
+
+### Using `PlayApiModule`
+
+The `PlayApiModule` trait behaves the same as the `PlayModule` trait but it won't process .scala
+.html files and you don't need to define the `twirlVersion:
+
+```scala
+// build.sc
+import mill._
+// You have to replace VERSION
+import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
+
+
+object core extends PlayApiModule {
+ //config
+ override def scalaVersion= T{"2.12.8"}
+ override def playVersion= T{"2.7.0"}
+
+ object test extends PlayTests
+}
+```
+
+### Play configuration options
+
+The Play modules themselves don't have specific configuration options at this point but the [router
+module configuration options](#router-configuration-options) and the [Twirl module configuration options](#twirl-configuration-options) are applicable.
+
+### Additional play libraries
+
+The following helpers are available to provide additional Play Framework dependencies:
+
+* `core()` - added by default ,
+* `guice()` - added by default,
+* `server()` - added by default,
+* `logback()` - added by default,
+* `evolutions()` - optional,
+* `jdbc()` - optional,
+* `filters()` - optional,
+* `ws()` - optional,
+* `caffeine()` - optional.
+
+If you want to add an optional library using the helper you can do so by overriding `ivyDeps`
+like in the following example build:
+
+```scala
+// build.sc
+import mill._
+// You have to replace VERSION
+import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
+
+
+object core extends PlayApiModule {
+ //config
+ override def scalaVersion= T{"2.12.8"}
+ override def playVersion= T{"2.7.0"}
+
+ object test extends PlayTests
+
+ override def ivyDeps = T{ super.ivyDeps() ++ Agg(ws(), filters()) }
+}
+```
+
+### Commands equivalence
+
+Mill commands are targets on a named build. For example if your build is called `core`:
+
+* compile: `core.compile`
+* run: *NOT Implemented yet*. It can be approximated with `mill -w core.runBackground` but this
+starts a server in *PROD* mode which:
+ * doesn't do any kind of classloading magic (meaning potentially slower restarts)
+ * returns less detailed error messages (no source code extract and line numbers)
+ * can sometimes fail because of a leftover RUNNING_PID file
+* start: `core.start` or `core.run` both start the server in *PROD* mode.
+* test: `core.test`
+* dist: *NOT Implemented yet*. However you can use the equivalent `core.assembly`
+command to get a runnable fat jar of the project. The packaging is slightly different but should
+be find for a production deployment.
+
+### Using `SingleModule`
+
+The `SingleModule` trait allows you to have the build descriptor at the same level as the source
+ code on the filesystem. You can move from there to a multi-module build either by refactoring
+ your directory layout into multiple subdirectories or by using mill's nested modules feature.
+
+Looking back at the sample build definition in [Using PlayModule](#using-playmodule):
+
+```scala
+// build.sc
+import mill._
+// You have to replace VERSION
+import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
+
+
+object core extends PlayModule {
+ //config
+ override def scalaVersion= T{"2.12.8"}
+ override def playVersion= T{"2.7.0"}
+ override def twirlVersion= T{"1.4.0"}
+
+ object test extends PlayTests
+}
+```
+
+The directory layout was:
+
+```text
+.
+├── build.sc
+└── core
+ ├── app
+ │   ├── controllers
+ │   └── views
+ ├── conf
+ │   └── application.conf
+ │   └── routes
+ │   └── ...
+ ├── logs
+ ├── public
+ │   ├── images
+ │   ├── javascripts
+ │   └── stylesheets
+ └── test
+ └── controllers
+```
+
+by mixing in the `SingleModule` trait in your build:
+
+```scala
+// build.sc
+import mill._
+// You have to replace VERSION
+import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
+
+
+object core extends PlayModule with SingleModule {
+ //config
+ override def scalaVersion= T{"2.12.8"}
+ override def playVersion= T{"2.7.0"}
+ override def twirlVersion= T{"1.4.0"}
+
+ object test extends PlayTests
+}
+```
+
+the layout becomes:
+
+```text
+.
+└── core
+ ├── build.sc
+ ├── app
+ │   ├── controllers
+ │   └── views
+ ├── conf
+ │   └── application.conf
+ │   └── routes
+ │   └── ...
+ ├── logs
+ ├── public
+ │   ├── images
+ │   ├── javascripts
+ │   └── stylesheets
+ └── test
+ └── controllers
+```
+
+#### Using the router module directly
+
+If you want to use the router module in a project which doesn't use the default Play layout, you
+can mix-in the `mill.playlib.routesModule` trait directly when defining your module. Your app must
+define `playVersion` and `scalaVersion`.
+
+```scala
+// build.sc
+import mill._
+// You have to replace VERSION
+import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
+
+
+object app extends ScalaModule with RouterModule {
+ def playVersion= T{"2.7.0"}
+ def scalaVersion= T{"2.12.8"}
+}
+```
+
+##### Router Configuration options
+
+* `def playVersion: T[String]` (mandatory) - The version of Play to use to compile the routes file.
+* `def scalaVersion: T[String]` - The scalaVersion in use in your project.
+* `def routes: Sources` - The directory which contains your route files. (Defaults to : `routes/`)
+* `def routesAdditionalImport: Seq[String]` - Additional imports to use in the generated routers.
+ (Defaults to `Seq("controllers.Assets.Asset", "play.libs.F")`
+* `def generateForwardsRouter: Boolean = true` - Enables the forward router generation.
+* `def generateReverseRouter: Boolean = true` - Enables the reverse router generation.
+* `def namespaceReverseRouter: Boolean = false` - Enables the namespacing of reverse routers.
+* `def generatorType: RouteCompilerType = RouteCompilerType.InjectedGenerator` - The routes
+ compiler type, one of RouteCompilerType.InjectedGenerator or RouteCompilerType.StaticGenerator
+
+##### Details
+
+The following filesystem layout is expected by default:
+
+```text
+.
+├── app
+│   └── routes
+│   └── routes
+└── build.sc
+```
+
+`RouterModule` adds the `compileRouter` task to the module:
+
+```
+mill app.compileRouter
+```
+
+(it will be automatically run whenever you compile your module)
+
+This task will compile `routes` templates into the `out/app/compileRouter/dest`
+directory. This directory must be added to the generated sources of the module to be compiled and
+made accessible from the rest of the code. This is done by default in the trait, but if you need
+to have a custom override for `generatedSources` you can get the list of files from `routerClasses`
+
+To add additional imports to all of the routes:
+
+```scala
+// build.sc
+import mill.scalalib._
+
+// You have to replace VERSION
+import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
+
+object app extends ScalaModule with RouterModule {
+ def playVersion = "2.7.0"
+ override def routesAdditionalImport = Seq("my.additional.stuff._", "my.other.stuff._")
+}
+```
+
-### ScalaPB
+## ScalaPB
This module allows [ScalaPB](https://scalapb.github.io) to be used in Mill builds. ScalaPB is a [Protocol Buffers](https://developers.google.com/protocol-buffers/) compiler plugin that generates Scala case classes, encoders and decoders for protobuf messages.
@@ -67,7 +559,7 @@ example/
resources/
```
-#### Configuration options
+### Configuration options
* scalaPBVersion (mandatory) - The ScalaPB version `String` e.g. `"0.7.4"`
@@ -95,7 +587,50 @@ object example extends ScalaPBModule {
}
```
-### TestNG
+
+## 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).
@@ -112,7 +647,7 @@ object project extends ScalaModule {
}
```
-### Tut
+## Tut
This module allows [Tut](https://tpolecat.github.io/tut) to be used in Mill builds. Tut is a documentation tool which compiles and evaluates Scala code in documentation files and provides various options for configuring how the results will be displayed in the compiled documentation.
@@ -151,7 +686,7 @@ In order to compile documentation we can execute the `tut` task in the module:
sh> mill example.tut
```
-#### Configuration options
+### Configuration options
* tutSourceDirectory - This task determines where documentation files must be placed in order to be compiled with Tut. By default this is the `tut` folder at the root of the module.
@@ -171,7 +706,7 @@ sh> mill example.tut
* tutPluginJars - A task which performs the dependency resolution for the scalac plugins to be used with Tut.
-### Twirl
+## Twirl
Twirl templates support.
@@ -190,7 +725,7 @@ object app extends ScalaModule with TwirlModule {
}
```
-#### Configuration options
+### Twirl configuration options
* `def twirlVersion: T[String]` (mandatory) - the version of the twirl compiler to use, like "1.3.15"
* `def twirlAdditionalImports: Seq[String] = Nil` - the additional imports that will be added by twirl compiler to the top of all templates
@@ -198,7 +733,7 @@ object app extends ScalaModule with TwirlModule {
* `def twirlCodec = Codec(Properties.sourceEncoding)` - the codec used to generate the files (the default is the same sbt plugin uses)
* `def twirlInclusiveDot: Boolean = false`
-#### Details
+### Details
The following filesystem layout is expected:
@@ -211,6 +746,7 @@ app/
```
`TwirlModule` adds the `compileTwirl` task to the module:
+
```
mill app.compileTwirl
```
@@ -219,6 +755,7 @@ mill app.compileTwirl
This task will compile `*.scala.html` templates (and others, like `*.scala.txt`) into the `out/app/compileTwirl/dest`
directory. This directory must be added to the generated sources of the module to be compiled and made accessible from the rest of the code:
+
```scala
// build.sc
import mill.scalalib._
@@ -233,6 +770,7 @@ object app extends ScalaModule with TwirlModule {
```
To add additional imports to all of the twirl templates:
+
```scala
// build.sc
import mill.scalalib._
@@ -248,12 +786,14 @@ object app extends ScalaModule with TwirlModule {
```
as the result all templates will get this line at the top:
+
```scala
@import "my.additional.stuff._"
@import "my.other.stuff._"
```
Besides that, twirl compiler has default imports, at the moment these:
+
```scala
Seq(
"_root_.play.twirl.api.TwirlFeatureImports._",
@@ -267,345 +807,5 @@ Seq(
These imports will always be added to every template. You don't need to list them if you override `twirlAdditionalImports`.
-#### Example
+### Example
There's an [example project](https://github.com/lihaoyi/cask/tree/master/example/twirl)
-
-### Play Framework
-
-Play framework routes generation support.
-
-
-To declare a module that needs to generate Play Framework routes, you must mix-in the
-`mill.playlib.routesModule` trait when defining your module.
-
-
-```scala
-// build.sc
-
-// You have to replace VERSION
-import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
-
-object app extends RouterModule {
-// ...
-}
-```
-
-#### Configuration options
-
- * `def playVersion: T[String]` (mandatory) - The version of play to use to compile the routes file.
- * `def scalaVersion: T[String]` - The scalaVersion in use in your project.
- * `def conf: Sources` - The directory which contains your route files. (Defaults to : `routes/` )
- * `def routesAdditionalImport: Seq[String]` - Additional imports to use in the generated routers. (Defaults to `Seq("controllers.Assets.Asset", "play.libs.F")`
- * `def generateForwardsRouter: Boolean = true` - Enables the forward router generation.
- * `def generateReverseRouter: Boolean = true` - Enables the reverse router generation.
- * `def namespaceReverseRouter: Boolean = false` - Enables the namespacing of reverse routers.
- * `def generatorType: RouteCompilerType = RouteCompilerType.InjectedGenerator` - The routes compiler type, one of RouteCompilerType.InjectedGenerator or RouteCompilerType.StaticGenerator
-
-#### Details
-
-The following filesystem layout is expected by default:
-
-```text
-.
-├── app
-│   └── routes
-│   └── routes
-└── build.sc
-```
-
-`RouterModule` adds the `compileRouter` task to the module:
-```
-mill app.compileRouter
-```
-
-(it will be automatically run whenever you compile your module)
-
-This task will compile `routes` templates into the `out/app/compileRouter/dest`
-directory. This directory must be added to the generated sources of the module to be compiled and made accessible from the rest of the code:
-```scala
-object app extends ScalaModule with RouterModule {
- def playVersion= T{"2.7.0"}
- def scalaVersion= T{"2.12.8"}
-}
-```
-
-To add additional imports to all of the routes:
-```scala
-// build.sc
-import mill.scalalib._
-
-// You have to replace VERSION
-import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
-
-object app extends ScalaModule with RouterModule {
- def playVersion = "2.7.0"
- override def routesAdditionalImport = Seq("my.additional.stuff._", "my.other.stuff._")
-}
-```
-
-If you want to use playframework's default of storing the routes in `conf/` you can do the
-follwing:
-```scala
-// build.sc
-import mill.scalalib._
-
-// You have to replace VERSION
-import $ivy.`com.lihaoyi::mill-contrib-playlib:VERSION`, mill.playlib._
-
-object app extends ScalaModule with RouterModule {
- def playVersion = "2.7.0"
- override def routesAdditionalImport = Seq("my.additional.stuff._", "my.other.stuff._")
- override def routes = T.sources{ millSourcePath / 'conf }
-}
-```
-
-which will work with the following directory structure:
-```text
-.
-├── app
-│   └── conf
-│   └── routes
-└── build.sc
-```
-
-## Thirdparty Mill Plugins
-
-### DGraph
-
-Show transitive dependencies of your build in your browser.
-
-Project home: https://github.com/ajrnz/mill-dgraph
-
-#### Quickstart
-
-```scala
-import $ivy.`com.github.ajrnz::mill-dgraph:0.2.0`
-```
-
-```sh
-sh> mill plugin.dgraph.browseDeps(proj)()
-```
-
-### Ensime
-
-Create an [.ensime](http://ensime.github.io/ "ensime") file for your build.
-
-Project home: https://github.com/yyadavalli/mill-ensime
-
-#### Quickstart
-
-```scala
-import $ivy.`fun.valycorp::mill-ensime:0.0.1`
-```
-
-```sh
-sh> mill fun.valycorp.mill.GenEnsime/ensimeConfig
-```
-
-### Integration Testing Mill Plugins
-
-Integration testing for mill plugins.
-
-#### Quickstart
-
-We assume, you have a mill plugin named `mill-demo`
-
-```scala
-// build.sc
-import mill._, mill.scalalib._
-object demo extends ScalaModule with PublishModule {
- // ...
-}
-```
-
-Add an new test sub-project, e.g. `it`.
-
-```scala
-import $ivy.`de.tototec::de.tobiasroeser.mill.integrationtest:0.1.0`
-import de.tobiasroeser.mill.integrationtest._
-
-object it extends MillIntegrationTest {
-
- def millTestVersion = "{exampleMillVersion}"
-
- def pluginsUnderTest = Seq(demo)
-
-}
-```
-
-Your project should now look similar to this:
-
-```text
-.
-+-- demo/
-| +-- src/
-|
-+-- it/
- +-- src/
- +-- 01-first-test/
- | +-- build.sc
- | +-- src/
- |
- +-- 02-second-test/
- +-- build.sc
-```
-
-As the buildfiles `build.sc` in your test cases typically want to access the locally built plugin(s),
-the plugins publishes all plugins referenced under `pluginsUnderTest` to a temporary ivy repository, just before the test is executed.
-The mill version used in the integration test then used that temporary ivy repository.
-
-Instead of referring to your plugin with `import $ivy.'your::plugin:version'`,
-you can use the following line instead, which ensures you will use the correct locally build plugins.
-
-```scala
-// build.sc
-import $exec.plugins
-```
-
-Effectively, at execution time, this line gets replaced by the content of `plugins.sc`, a file which was generated just before the test started to execute.
-
-#### Configuration and Targets
-
-The mill-integrationtest plugin provides the following targets.
-
-##### Mandatory configuration
-
-* `def millTestVersion: T[String]`
- The mill version used for executing the test cases.
- Used by `downloadMillTestVersion` to automatically download.
-
-* `def pluginsUnderTest: Seq[PublishModule]` -
- The plugins used in the integration test.
- You should at least add your plugin under test here.
- You can also add additional libraries, e.g. those that assist you in the test result validation (e.g. a local test support project).
- The defined modules will be published into a temporary ivy repository before the tests are executed.
- In your test `build.sc` file, instead of the typical `import $ivy.` line,
- you should use `import $exec.plugins` to include all plugins that are defined here.
-
-##### Optional configuration
-
-* `def sources: Sources` -
- Locations where integration tests are located.
- Each integration test is a sub-directory, containing a complete test mill project.
-
-* `def testCases: T[Seq[PathRef]]` -
- The directories each representing a mill test case.
- Derived from `sources`.
-
-* `def testTargets: T[Seq[String]]` -
- The targets which are called to test the project.
- Defaults to `verify`, which should implement test result validation.
-
-* `def downloadMillTestVersion: T[PathRef]` -
- Download the mill version as defined by `millTestVersion`.
- Override this, if you need to use a custom built mill version.
- Returns the `PathRef` to the mill executable (must have the executable flag).
-
-##### Commands
-
-* `def test(): Command[Unit]` -
- Run the integration tests.
-
-
-### JBake
-
-Create static sites/blogs with JBake.
-
-Plugin home: https://github.com/lefou/mill-jbake
-
-JBake home: https://jbake.org
-
-#### Quickstart
-
-```scala
-// build.sc
-import mill._
-import $ivy.`de.tototec::de.tobiasroeser.mill.jbake:0.1.0`
-import de.tobiasroeser.mill.jbake._
-
-object site extends JBakeModule {
-
- def jbakeVersion = "2.6.4"
-
-}
-```
-
-Generate the site:
-
-```sh
-bash> mill site.jbake
-```
-
-Start a local Web-Server on Port 8820 with the generated site:
-
-```sh
-bash> mill site.jbakeServe
-```
-
-
-### OSGi
-
-Produce OSGi Bundles with mill.
-
-Project home: https://github.com/lefou/mill-osgi
-
-#### Quickstart
-
-```scala
-import mill._, mill.scalalib._
-import $ivy.`de.tototec::de.tobiasroeser.mill.osgi:0.0.5`
-import de.tobiasroeser.mill.osgi._
-
-object project extends ScalaModule with OsgiBundleModule {
-
- def bundleSymbolicName = "com.example.project"
-
- def osgiHeaders = T{ super.osgiHeaders().copy(
- `Export-Package` = Seq("com.example.api"),
- `Bundle-Activator` = Some("com.example.internal.Activator")
- )}
-
- // other settings ...
-
-}
-```
-
-
-### PublishM2
-
-Mill plugin to publish artifacts into a local Maven repository.
-
-Project home: https://github.com/lefou/mill-publishM2
-
-#### Quickstart
-
-Just mix-in the `PublishM2Module` into your project.
-`PublishM2Module` already extends mill's built-in `PublishModule`.
-
-File: `build.sc`
-```scala
-import mill._, scalalib._, publish._
-
-import $ivy.`de.tototec::de.tobiasroeser.mill.publishM2:0.0.1`
-import de.tobiasroeser.mill.publishM2._
-
-object project extends PublishModule with PublishM2Module {
- // ...
-}
-```
-
-Publishing to default local Maven repository
-
-```bash
-> mill project.publishM2Local
-[40/40] project.publishM2Local
-Publishing to /home/user/.m2/repository
-```
-
-Publishing to custom local Maven repository
-
-```bash
-> mill project.publishM2Local /tmp/m2repo
-[40/40] project.publishM2Local
-Publishing to /tmp/m2repo
-```
diff --git a/main/client/src/MillClientMain.java b/main/client/src/MillClientMain.java
index 3857caff..0097102a 100644
--- a/main/client/src/MillClientMain.java
+++ b/main/client/src/MillClientMain.java
@@ -7,15 +7,24 @@ import java.net.Socket;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.*;
+import java.lang.Math;
public class MillClientMain {
+
+ // use methods instead of constants to avoid inlining by compiler
+ public static final int ExitClientCodeCannotReadFromExitCodeFile() { return 1; }
+ public static final int ExitServerCodeWhenIdle() { return 0; }
+ public static final int ExitServerCodeWhenVersionMismatch() { return 101; }
+
static void initServer(String lockBase, boolean setJnaNoSys) throws IOException,URISyntaxException{
String[] selfJars = System.getProperty("MILL_CLASSPATH").split(",");
List<String> l = new ArrayList<>();
List<String> vmOptions = new ArrayList<>();
- l.add("java");
+ l.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");
final Properties props = System.getProperties();
for(final String k: props.stringPropertyNames()){
if (k.startsWith("MILL_") && !"MILL_CLASSPATH".equals(k)) {
@@ -46,6 +55,16 @@ public class MillClientMain {
.redirectError(new java.io.File(lockBase + "/logs"))
.start();
}
+
+ private static String sha1HashPath(String path) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+ MessageDigest md = MessageDigest.getInstance("SHA1");
+ md.reset();
+ byte[] pathBytes = path.getBytes("UTF-8");
+ md.update(pathBytes);
+ byte[] digest = md.digest();
+ return Base64.getEncoder().encodeToString(digest);
+ }
+
public static void main(String[] args) throws Exception{
System.exit(main0(args));
}
@@ -56,9 +75,22 @@ public class MillClientMain {
System.setProperty("jna.nosys", "true");
}
int index = 0;
- while (index < 5) {
+ String jvmHomeEncoding = sha1HashPath(System.getProperty("java.home"));
+ File outFolder = new File("out");
+ String[] totalProcesses = outFolder.list((dir,name) -> name.startsWith("mill-worker-"));
+ String[] thisJdkProcesses = outFolder.list((dir,name) -> name.startsWith("mill-worker-" + jvmHomeEncoding));
+
+ int processLimit = 5;
+ if(totalProcesses != null) {
+ if(thisJdkProcesses != null) {
+ processLimit -= Math.min(totalProcesses.length - thisJdkProcesses.length, 5);
+ } else {
+ processLimit -= Math.min(totalProcesses.length, 5);
+ }
+ }
+ while (index < processLimit) {
index += 1;
- String lockBase = "out/mill-worker-" + index;
+ String lockBase = "out/mill-worker-" + jvmHomeEncoding + "-" + index;
new java.io.File(lockBase).mkdirs();
try(RandomAccessFile lockFile = new RandomAccessFile(lockBase + "/clientLock", "rw");
@@ -91,7 +123,7 @@ public class MillClientMain {
}
}
- throw new Exception("Reached max process limit: " + 5);
+ throw new Exception("Reached max process limit: " + processLimit);
}
public static int run(String lockBase,
@@ -151,10 +183,10 @@ public class MillClientMain {
locks.serverLock.await();
- try(FileInputStream fos = new FileInputStream(lockBase + "/exitCode")){
- return Integer.parseInt(new BufferedReader(new InputStreamReader(fos)).readLine());
+ try(FileInputStream fis = new FileInputStream(lockBase + "/exitCode")){
+ return Integer.parseInt(new BufferedReader(new InputStreamReader(fis)).readLine());
} catch(Throwable e){
- return 1;
+ return ExitClientCodeCannotReadFromExitCodeFile();
} finally{
ioSocket.close();
}
diff --git a/main/core/src/eval/Evaluator.scala b/main/core/src/eval/Evaluator.scala
index ebb00ef1..f4ec8ff9 100644
--- a/main/core/src/eval/Evaluator.scala
+++ b/main/core/src/eval/Evaluator.scala
@@ -32,7 +32,7 @@ case class Evaluator(home: os.Path,
externalOutPath: os.Path,
rootModule: mill.define.BaseModule,
log: Logger,
- classLoaderSig: Seq[(Either[String, os.Path], Long)] = Evaluator.classLoaderSig,
+ classLoaderSig: Seq[(Either[String, java.net.URL], Long)] = Evaluator.classLoaderSig,
workerCache: mutable.Map[Segments, (Int, Any)] = mutable.Map.empty,
env : Map[String, String] = Evaluator.defaultEnv,
failFast: Boolean = true
@@ -374,7 +374,7 @@ object Evaluator{
implicit val rw: upickle.default.ReadWriter[Cached] = upickle.default.macroRW
}
case class State(rootModule: mill.define.BaseModule,
- classLoaderSig: Seq[(Either[String, os.Path], Long)],
+ classLoaderSig: Seq[(Either[String, java.net.URL], Long)],
workerCache: mutable.Map[Segments, (Int, Any)],
watched: Seq[(os.Path, Long)])
// This needs to be a ThreadLocal because we need to pass it into the body of
diff --git a/main/core/src/util/JsonFormatters.scala b/main/core/src/util/JsonFormatters.scala
index 830782c6..c1dd18f0 100644
--- a/main/core/src/util/JsonFormatters.scala
+++ b/main/core/src/util/JsonFormatters.scala
@@ -6,5 +6,10 @@ trait JsonFormatters extends mill.api.JsonFormatters{
implicit lazy val modFormat: RW[coursier.Module] = upickle.default.macroRW
implicit lazy val depFormat: RW[coursier.Dependency]= upickle.default.macroRW
implicit lazy val attrFormat: RW[coursier.Attributes] = upickle.default.macroRW
+ implicit lazy val orgFormat: RW[coursier.Organization] = upickle.default.macroRW
+ implicit lazy val modNameFormat: RW[coursier.ModuleName] = upickle.default.macroRW
+ implicit lazy val configurationFormat: RW[coursier.core.Configuration] = upickle.default.macroRW
+ implicit lazy val typeFormat: RW[coursier.core.Type] = upickle.default.macroRW
+ implicit lazy val classifierFormat: RW[coursier.core.Classifier] = upickle.default.macroRW
}
object JsonFormatters extends JsonFormatters
diff --git a/main/src/MillMain.scala b/main/src/MillMain.scala
index f1a7a9e7..ca4af87d 100644
--- a/main/src/MillMain.scala
+++ b/main/src/MillMain.scala
@@ -23,54 +23,57 @@ object MillMain {
System.out,
System.err,
System.getenv().asScala.toMap,
- b => ()
+ b => (),
+ initialSystemProperties = Map()
)
- System.exit(if(result) 0 else 1)
+ System.exit(if (result) 0 else 1)
}
- def main0(args: Array[String],
- stateCache: Option[Evaluator.State],
- mainInteractive: Boolean,
- stdin: InputStream,
- stdout: PrintStream,
- stderr: PrintStream,
- env: Map[String, String],
- setIdle: Boolean => Unit): (Boolean, Option[Evaluator.State]) = {
+ def main0(
+ args: Array[String],
+ stateCache: Option[Evaluator.State],
+ mainInteractive: Boolean,
+ stdin: InputStream,
+ stdout: PrintStream,
+ stderr: PrintStream,
+ env: Map[String, String],
+ setIdle: Boolean => Unit,
+ initialSystemProperties: Map[String, String]
+ ): (Boolean, Option[Evaluator.State]) = {
import ammonite.main.Cli
-
+
val millHome = mill.api.Ctx.defaultHome
val removed = Set("predef-code", "no-home-predef")
+
var interactive = false
val interactiveSignature = Arg[Config, Unit](
"interactive", Some('i'),
"Run Mill in interactive mode, suitable for opening REPLs and taking user input. In this mode, no mill server will be used.",
- (c, v) =>{
+ (c, v) => {
interactive = true
c
}
)
-
-
var disableTicker = false
val disableTickerSignature = Arg[Config, Unit](
- "disable-ticker", None,
- "Disable ticker log (e.g. short-lived prints of stages and progress bars)",
- (c, v) =>{
- disableTicker = true
- c
- }
+ name = "disable-ticker", shortName = None,
+ doc = "Disable ticker log (e.g. short-lived prints of stages and progress bars)",
+ action = (c, v) => {
+ disableTicker = true
+ c
+ }
)
var debugLog = false
val debugLogSignature = Arg[Config, Unit](
name = "debug", shortName = Some('d'),
doc = "Show debug output on STDOUT",
- (c, v) => {
- debugLog = true
- c
- }
+ action = (c, v) => {
+ debugLog = true
+ c
+ }
)
var keepGoing = false
@@ -82,27 +85,46 @@ object MillMain {
}
)
+ var extraSystemProperties = Map[String, String]()
+ val extraSystemPropertiesSignature = Arg[Config, String](
+ name = "define", shortName = Some('D'),
+ doc = "Define (or overwrite) a system property",
+ action = { (c, v) =>
+ extraSystemProperties += (v.split("[=]", 2) match {
+ case Array(k, v) => k -> v
+ case Array(k) => k -> ""
+ })
+ c
+ }
+ )
+
val millArgSignature =
Cli.genericSignature.filter(a => !removed(a.name)) ++
- Seq(interactiveSignature, disableTickerSignature, debugLogSignature, keepGoingSignature)
+ Seq(
+ interactiveSignature,
+ disableTickerSignature,
+ debugLogSignature,
+ keepGoingSignature,
+ extraSystemPropertiesSignature
+ )
Cli.groupArgs(
args.toList,
millArgSignature,
Cli.Config(home = millHome, remoteLogging = false)
- ) match{
- case _ if interactive =>
- stderr.println("-i/--interactive must be passed in as the first argument")
- (false, None)
- case Left(msg) =>
- stderr.println(msg)
- (false, None)
- case Right((cliConfig, _)) if cliConfig.help =>
- val leftMargin = millArgSignature.map(ammonite.main.Cli.showArg(_).length).max + 2
- stdout.println(
- s"""Mill Build Tool
- |usage: mill [mill-options] [target [target-options]]
- |
+ ) match {
+ case _ if interactive =>
+ stderr.println("-i/--interactive must be passed in as the first argument")
+ (false, None)
+ case Left(msg) =>
+ stderr.println(msg)
+ (false, None)
+ case Right((cliConfig, _)) if cliConfig.help =>
+ val leftMargin = millArgSignature.map(ammonite.main.Cli.showArg(_).length).max + 2
+ stdout.println(
+ s"""Mill Build Tool
+ |usage: mill [mill-options] [target [target-options]]
+ |
|${formatBlock(millArgSignature, leftMargin).mkString(ammonite.util.Util.newLine)}""".stripMargin
)
(true, None)
@@ -113,6 +135,8 @@ object MillMain {
stderr.println("Build repl needs to be run with the -i/--interactive flag")
(false, stateCache)
}else{
+ val systemProps = initialSystemProperties ++ extraSystemProperties
+
val config =
if(!repl) cliConfig
else cliConfig.copy(
@@ -125,43 +149,45 @@ object MillMain {
| repl.pprinter(),
| build.millSelf.get,
| build.millDiscover,
- | $debugLog,
- | keepGoing = $keepGoing
+ | debugLog = $debugLog,
+ | keepGoing = $keepGoing,
+ | systemProperties = ${systemProps}
|)
|repl.pprinter() = replApplyHandler.pprinter
|import replApplyHandler.generatedEval._
|
""".stripMargin,
- welcomeBanner = None
+ welcomeBanner = None
+ )
+
+ val runner = new mill.main.MainRunner(
+ config.copy(colored = config.colored orElse Option(mainInteractive)),
+ disableTicker,
+ stdout, stderr, stdin,
+ stateCache,
+ env,
+ setIdle,
+ debugLog = debugLog,
+ keepGoing = keepGoing,
+ systemProperties = systemProps
)
- val runner = new mill.main.MainRunner(
- config.copy(colored = config.colored orElse Option(mainInteractive)),
- disableTicker,
- stdout, stderr, stdin,
- stateCache,
- env,
- setIdle,
- debugLog,
- keepGoing = keepGoing
- )
-
- if (mill.main.client.Util.isJava9OrAbove) {
- val rt = cliConfig.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)
+ if (mill.main.client.Util.isJava9OrAbove) {
+ val rt = cliConfig.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)
+ }
}
- }
- if (repl){
- runner.printInfo("Loading...")
- (runner.watchLoop(isRepl = true, printing = false, _.run()), runner.stateCache)
- } else {
- (runner.runScript(os.pwd / "build.sc", leftoverArgs), runner.stateCache)
+ if (repl) {
+ runner.printInfo("Loading...")
+ (runner.watchLoop(isRepl = true, printing = false, _.run()), runner.stateCache)
+ } else {
+ (runner.runScript(os.pwd / "build.sc", leftoverArgs), runner.stateCache)
+ }
}
- }
- }
+ }
}
}
diff --git a/main/src/main/MainRunner.scala b/main/src/main/MainRunner.scala
index e08905a6..6705a4b3 100644
--- a/main/src/main/MainRunner.scala
+++ b/main/src/main/MainRunner.scala
@@ -25,7 +25,8 @@ class MainRunner(val config: ammonite.main.Cli.Config,
env : Map[String, String],
setIdle: Boolean => Unit,
debugLog: Boolean,
- keepGoing: Boolean)
+ keepGoing: Boolean,
+ systemProperties: Map[String, String])
extends ammonite.MainRunner(
config, outprintStream, errPrintStream,
stdIn, outprintStream, errPrintStream
@@ -85,7 +86,8 @@ class MainRunner(val config: ammonite.main.Cli.Config,
debugEnabled = debugLog
),
env,
- keepGoing = keepGoing
+ keepGoing = keepGoing,
+ systemProperties
)
result match{
@@ -126,7 +128,7 @@ class MainRunner(val config: ammonite.main.Cli.Config,
)
}
- object CustomCodeWrapper extends Preprocessor.CodeWrapper {
+ object CustomCodeWrapper extends ammonite.interp.CodeWrapper {
def apply(code: String,
source: CodeSource,
imports: ammonite.util.Imports,
diff --git a/main/src/main/MillServerMain.scala b/main/src/main/MillServerMain.scala
index 55cef632..500c3e8f 100644
--- a/main/src/main/MillServerMain.scala
+++ b/main/src/main/MillServerMain.scala
@@ -21,7 +21,8 @@ trait MillServerMain[T]{
stdout: PrintStream,
stderr: PrintStream,
env : Map[String, String],
- setIdle: Boolean => Unit): (Boolean, Option[T])
+ setIdle: Boolean => Unit,
+ systemProperties: Map[String, String]): (Boolean, Option[T])
}
object MillServerMain extends mill.main.MillServerMain[Evaluator.State]{
@@ -37,13 +38,14 @@ object MillServerMain extends mill.main.MillServerMain[Evaluator.State]{
def handle(sig: Signal) = {} // do nothing
})
new Server(
- args0(0),
+ lockBase = args0(0),
this,
- () => System.exit(0),
+ () => System.exit(MillClientMain.ExitServerCodeWhenIdle()),
300000,
mill.main.client.Locks.files(args0(0))
).run()
}
+
def main0(args: Array[String],
stateCache: Option[Evaluator.State],
mainInteractive: Boolean,
@@ -51,7 +53,8 @@ object MillServerMain extends mill.main.MillServerMain[Evaluator.State]{
stdout: PrintStream,
stderr: PrintStream,
env : Map[String, String],
- setIdle: Boolean => Unit) = {
+ setIdle: Boolean => Unit,
+ systemProperties: Map[String, String]) = {
MillMain.main0(
args,
stateCache,
@@ -60,7 +63,8 @@ object MillServerMain extends mill.main.MillServerMain[Evaluator.State]{
stdout,
stderr,
env,
- setIdle = setIdle
+ setIdle = setIdle,
+ systemProperties
)
}
}
@@ -124,10 +128,15 @@ class Server[T](lockBase: String,
if (clientMillVersion != serverMillVersion) {
// FIXME: exiting with 0 isn't correct, see https://github.com/lihaoyi/mill/issues/557
stdout.println(s"Mill version changed ($serverMillVersion -> $clientMillVersion), re-starting server")
- System.exit(0)
+ java.nio.file.Files.write(
+ java.nio.file.Paths.get(lockBase + "/exitCode"),
+ s"${MillClientMain.ExitServerCodeWhenVersionMismatch()}".getBytes()
+ )
+ System.exit(MillClientMain.ExitServerCodeWhenVersionMismatch())
}
val args = Util.parseArgs(argStream)
val env = Util.parseMap(argStream)
+ val systemProperties = Util.parseMap(argStream)
argStream.close()
@volatile var done = false
@@ -143,6 +152,7 @@ class Server[T](lockBase: String,
stderr,
env.asScala.toMap,
idle = _,
+ systemProperties.asScala.toMap
)
sm.stateCache = newStateCache
diff --git a/main/src/main/ReplApplyHandler.scala b/main/src/main/ReplApplyHandler.scala
index 6f1e060d..7f959929 100644
--- a/main/src/main/ReplApplyHandler.scala
+++ b/main/src/main/ReplApplyHandler.scala
@@ -1,14 +1,13 @@
package mill.main
+import scala.collection.mutable
+import mill.api.Strict.Agg
import mill.define.Applicative.ApplyHandler
import mill.define.Segment.Label
import mill.define._
import mill.eval.{Evaluator, Result}
-import mill.api.Strict.Agg
-
-import scala.collection.mutable
object ReplApplyHandler{
def apply[T](home: os.Path,
disableTicker: Boolean,
@@ -17,7 +16,8 @@ object ReplApplyHandler{
rootModule: mill.define.BaseModule,
discover: Discover[_],
debugLog: Boolean,
- keepGoing: Boolean) = {
+ keepGoing: Boolean,
+ systemProperties: Map[String, String]): ReplApplyHandler = {
new ReplApplyHandler(
pprinter0,
new Evaluator(
@@ -36,7 +36,8 @@ object ReplApplyHandler{
debugEnabled = debugLog
),
failFast = !keepGoing
- )
+ ),
+ systemProperties
)
}
def pprintCross(c: mill.define.Cross[_], evaluator: Evaluator) = {
@@ -113,8 +114,15 @@ object ReplApplyHandler{
}
}
+
class ReplApplyHandler(pprinter0: pprint.PPrinter,
- val evaluator: Evaluator) extends ApplyHandler[Task] {
+ val evaluator: Evaluator,
+ systemProperties: Map[String, String]) extends ApplyHandler[Task] {
+
+ systemProperties.foreach {case (k,v) =>
+ System.setProperty(k,v)
+ }
+
// Evaluate classLoaderSig only once in the REPL to avoid busting caches
// as the user enters more REPL commands and changes the classpath
val classLoaderSig = Evaluator.classLoaderSig
diff --git a/main/src/main/RunScript.scala b/main/src/main/RunScript.scala
index ea8e554f..ab53aa1a 100644
--- a/main/src/main/RunScript.scala
+++ b/main/src/main/RunScript.scala
@@ -30,9 +30,14 @@ object RunScript{
stateCache: Option[Evaluator.State],
log: Logger,
env : Map[String, String],
- keepGoing: Boolean)
+ keepGoing: Boolean,
+ systemProperties: Map[String, String])
: (Res[(Evaluator, Seq[PathRef], Either[String, Seq[ujson.Value]])], Seq[(os.Path, Long)]) = {
+ systemProperties.foreach {case (k,v) =>
+ System.setProperty(k, v)
+ }
+
val (evalState, interpWatched) = stateCache match{
case Some(s) if watchedSigUnchanged(s.watched) => Res.Success(s) -> s.watched
case _ =>
diff --git a/main/src/main/VisualizeModule.scala b/main/src/main/VisualizeModule.scala
index e950973f..b3d59509 100644
--- a/main/src/main/VisualizeModule.scala
+++ b/main/src/main/VisualizeModule.scala
@@ -2,7 +2,7 @@ package mill.main
import java.util.concurrent.LinkedBlockingQueue
-import coursier.Cache
+import coursier.LocalRepositories
import coursier.core.Repository
import coursier.maven.MavenRepository
import mill.T
@@ -11,7 +11,7 @@ import mill.eval.{PathRef, Result}
object VisualizeModule extends ExternalModule with VisualizeModule {
def repositories = Seq(
- Cache.ivy2Local,
+ LocalRepositories.ivy2Local,
MavenRepository("https://repo1.maven.org/maven2"),
MavenRepository("https://oss.sonatype.org/content/repositories/releases")
)
diff --git a/main/src/modules/Jvm.scala b/main/src/modules/Jvm.scala
index 555fabae..c1f07a9b 100644
--- a/main/src/modules/Jvm.scala
+++ b/main/src/modules/Jvm.scala
@@ -8,7 +8,7 @@ import java.nio.file.attribute.PosixFilePermission
import java.util.Collections
import java.util.jar.{JarEntry, JarFile, JarOutputStream}
-import coursier.{Cache, Dependency, Fetch, Repository, Resolution, CachePolicy}
+import coursier.{Dependency, Fetch, Repository, Resolution}
import coursier.util.{Gather, Task}
import geny.Generator
import mill.main.client.InputPumper
@@ -37,7 +37,7 @@ object Jvm {
val commandArgs =
Vector("java") ++
jvmArgs ++
- Vector("-cp", classPath.mkString(File.pathSeparator), mainClass) ++
+ Vector("-cp", classPath.mkString(java.io.File.pathSeparator), mainClass) ++
mainArgs
val workingDir1 = Option(workingDir).getOrElse(ctx.dest)
@@ -60,7 +60,7 @@ object Jvm {
val args =
Vector("java") ++
jvmArgs ++
- Vector("-cp", classPath.mkString(File.pathSeparator), mainClass) ++
+ Vector("-cp", classPath.mkString(java.io.File.pathSeparator), mainClass) ++
mainArgs
if (background) spawnSubprocess(args, envArgs, workingDir)
@@ -84,7 +84,7 @@ object Jvm {
process.waitFor()
if (process.exitCode() == 0) ()
- else throw new Exception("Interactive Subprocess Failed")
+ else throw new Exception("Interactive Subprocess Failed (exit code " + process.exitCode() + ")")
}
/**
@@ -289,9 +289,7 @@ object Jvm {
writeEntry(path, concatenated, append = true)
case (mapping, WriteOnceEntry(entry)) =>
val path = zipFs.getPath(mapping).toAbsolutePath
- if (Files.notExists(path)) {
- writeEntry(path, entry.inputStream, append = false)
- }
+ writeEntry(path, entry.inputStream, append = false)
}
zipFs.close()
@@ -409,6 +407,7 @@ object Jvm {
repositories, deps, force, mapDependencies, ctx
)
val errs = resolution.metadataErrors
+
if(errs.nonEmpty) {
val header =
s"""|
@@ -424,13 +423,11 @@ object Jvm {
} else {
def load(artifacts: Seq[coursier.Artifact]) = {
- val logger = None
import scala.concurrent.ExecutionContext.Implicits.global
val loadedArtifacts = Gather[Task].gather(
for (a <- artifacts)
- yield coursier.Cache.file[Task](a, logger = logger).run
- .map(a.isOptional -> _)
+ yield coursier.cache.Cache.default.file(a).run.map(a.optional -> _)
).unsafeRun
val errors = loadedArtifacts.collect {
@@ -442,8 +439,13 @@ object Jvm {
}
val sourceOrJar =
- if (sources) resolution.classifiersArtifacts(Seq("sources"))
- else resolution.artifacts(true)
+ if (sources) {
+ resolution.artifacts(
+ types = Set(coursier.Type.source, coursier.Type.javaSource),
+ classifiers = Some(Seq(coursier.Classifier("sources")))
+ )
+ }
+ else resolution.artifacts()
val (errors, successes) = load(sourceOrJar)
if(errors.isEmpty){
mill.Agg.from(
@@ -463,7 +465,7 @@ object Jvm {
mapDependencies: Option[Dependency => Dependency] = None,
ctx: Option[mill.util.Ctx.Log] = None) = {
- val cachePolicies = CachePolicy.default
+ val cachePolicies = coursier.cache.CacheDefaults.cachePolicies
val forceVersions = force
.map(mapDependencies.getOrElse(identity[Dependency](_)))
@@ -471,20 +473,23 @@ object Jvm {
.toMap
val start = Resolution(
- deps.map(mapDependencies.getOrElse(identity[Dependency](_))).toSet,
+ deps.map(mapDependencies.getOrElse(identity[Dependency](_))).toSeq,
forceVersions = forceVersions,
mapDependencies = mapDependencies
)
val resolutionLogger = ctx.map(c => new TickerResolutionLogger(c))
- val fetches = cachePolicies.map { p =>
- Cache.fetch[Task](
- logger = resolutionLogger,
- cachePolicy = p
- )
+ val cache = resolutionLogger match {
+ case None => coursier.cache.FileCache[Task].withCachePolicies(cachePolicies)
+ case Some(l) =>
+ coursier.cache.FileCache[Task]
+ .withCachePolicies(cachePolicies)
+ .withLogger(l)
}
- val fetch = Fetch.from(repositories, fetches.head, fetches.tail: _*)
+ val fetches = cache.fetchs
+
+ val fetch = coursier.core.ResolutionProcess.fetch(repositories, fetches.head, fetches.tail: _*)
import scala.concurrent.ExecutionContext.Implicits.global
val resolution = start.process.run(fetch).unsafeRun()
@@ -498,7 +503,7 @@ object Jvm {
* In practice, this ticker output gets prefixed with the current target for which
* dependencies are being resolved, using a [[mill.util.ProxyLogger]] subclass.
*/
- class TickerResolutionLogger(ctx: mill.util.Ctx.Log) extends Cache.Logger {
+ class TickerResolutionLogger(ctx: mill.util.Ctx.Log) extends coursier.cache.CacheLogger {
case class DownloadState(var current: Long, var total: Long)
var downloads = new mutable.TreeMap[String,DownloadState]()
var totalDownloadCount = 0
@@ -518,7 +523,7 @@ object Jvm {
ctx.log.ticker(s"Downloading [${downloads.size + finishedCount}/$totalDownloadCount] artifacts (~${sums.current}/${sums.total} bytes)")
}
- override def downloadingArtifact(url: String, file: File): Unit = synchronized {
+ override def downloadingArtifact(url: String): Unit = synchronized {
totalDownloadCount += 1
downloads += url -> DownloadState(0,0)
updateTicker()
diff --git a/main/src/modules/Util.scala b/main/src/modules/Util.scala
index 20f06d8f..8cb72e61 100644
--- a/main/src/modules/Util.scala
+++ b/main/src/modules/Util.scala
@@ -65,7 +65,7 @@ object Util {
repositories,
Seq(
coursier.Dependency(
- coursier.Module("com.lihaoyi", artifact + artifactSuffix),
+ coursier.Module(coursier.Organization("com.lihaoyi"), coursier.ModuleName(artifact + artifactSuffix)),
sys.props("MILL_VERSION")
)
),
diff --git a/main/test/src/main/ClientServerTests.scala b/main/test/src/main/ClientServerTests.scala
index 05238a5f..6d918b30 100644
--- a/main/test/src/main/ClientServerTests.scala
+++ b/main/test/src/main/ClientServerTests.scala
@@ -13,7 +13,8 @@ class EchoServer extends MillServerMain[Int]{
stdout: PrintStream,
stderr: PrintStream,
env: Map[String, String],
- setIdle: Boolean => Unit) = {
+ setIdle: Boolean => Unit,
+ systemProperties: Map[String, String]) = {
val reader = new BufferedReader(new InputStreamReader(stdin))
val str = reader.readLine()
@@ -23,6 +24,9 @@ class EchoServer extends MillServerMain[Int]{
env.toSeq.sortBy(_._1).foreach{
case (key, value) => stdout.println(s"$key=$value")
}
+ systemProperties.toSeq.sortBy(_._1).foreach{
+ case (key, value) => stdout.println(s"$key=$value")
+ }
stdout.flush()
if (args.nonEmpty){
stderr.println(str.toUpperCase + args(0))
diff --git a/main/test/src/util/ScriptTestSuite.scala b/main/test/src/util/ScriptTestSuite.scala
index 92f57c4f..b15541f3 100644
--- a/main/test/src/util/ScriptTestSuite.scala
+++ b/main/test/src/util/ScriptTestSuite.scala
@@ -16,10 +16,19 @@ abstract class ScriptTestSuite(fork: Boolean) extends TestSuite{
val disableTicker = false
val debugLog = false
val keepGoing = false
+ val systemProperties = Map[String, String]()
lazy val runner = new mill.main.MainRunner(
- ammonite.main.Cli.Config(wd = wd), disableTicker,
- stdOutErr, stdOutErr, stdIn, None, Map.empty,
- b => (), debugLog, keepGoing = keepGoing
+ config = ammonite.main.Cli.Config(wd = wd),
+ disableTicker = disableTicker,
+ outprintStream = stdOutErr,
+ errPrintStream = stdOutErr,
+ stdIn = stdIn,
+ stateCache0 = None,
+ env = Map.empty,
+ setIdle = b => (),
+ debugLog = debugLog,
+ keepGoing = keepGoing,
+ systemProperties = systemProperties
)
def eval(s: String*) = {
if (!fork) runner.runScript(workspacePath / buildPath , s.toList)
diff --git a/main/test/src/util/TestEvaluator.scala b/main/test/src/util/TestEvaluator.scala
index 97be20be..45bc41d9 100644
--- a/main/test/src/util/TestEvaluator.scala
+++ b/main/test/src/util/TestEvaluator.scala
@@ -12,12 +12,12 @@ object TestEvaluator{
val externalOutPath = os.pwd / 'target / 'external
- def static(module: TestUtil.BaseModule)(implicit fullName: sourcecode.FullName) = {
+ def static(module: => TestUtil.BaseModule)(implicit fullName: sourcecode.FullName) = {
new TestEvaluator(module)(fullName, TestPath(Nil))
}
}
-class TestEvaluator(module: TestUtil.BaseModule, failFast: Boolean = false)
+class TestEvaluator(module: => TestUtil.BaseModule, failFast: Boolean = false)
(implicit fullName: sourcecode.FullName,
tp: TestPath){
val outPath = TestUtil.getOutPath()
diff --git a/readme.md b/readme.md
index ec074c1b..d5961a9b 100644
--- a/readme.md
+++ b/readme.md
@@ -155,9 +155,27 @@ optimizer without classpath conflicts.
## Changelog
-### {master}
+### 0.3.9
-- Publish compileIvyDeps as provided scope
+- Publish `compileIvyDeps` as provided scope
+ ([535](https://github.com/lihaoyi/mill/issues/535))
+
+- Added contrib modules to integrate
+ [Bloop](http://www.lihaoyi.com/mill/page/contrib-modules.html#bloop),
+ [Flyway](http://www.lihaoyi.com/mill/page/contrib-modules.html#flyway),
+ [Play Framework](http://www.lihaoyi.com/mill/page/contrib-modules.html#play-framework),
+ [Scoverage](http://www.lihaoyi.com/mill/page/contrib-modules.html#scoverage)
+
+- Allow configuration of GPG key names when publishing
+ ([530](https://github.com/lihaoyi/mill/pull/530))
+
+- Bump Ammonite version to 1.6.7, making
+ [Requests-Scala](https://github.com/lihaoyi/requests-scala) available to use
+ in your `build.sc`
+
+- Support for Scala 2.13.0-RC2
+
+- ScalaFmt support now uses the version specified in `.scalafmt.conf`
### 0.3.6
diff --git a/scalajslib/src/ScalaJSModule.scala b/scalajslib/src/ScalaJSModule.scala
index 230a3e5c..51b04e6a 100644
--- a/scalajslib/src/ScalaJSModule.scala
+++ b/scalajslib/src/ScalaJSModule.scala
@@ -1,7 +1,6 @@
package mill
package scalajslib
-import coursier.Cache
import coursier.maven.MavenRepository
import mill.eval.{PathRef, Result}
import mill.api.Result.Success
diff --git a/scalalib/api/src/ZincWorkerApi.scala b/scalalib/api/src/ZincWorkerApi.scala
index 70128e8d..790ea274 100644
--- a/scalalib/api/src/ZincWorkerApi.scala
+++ b/scalalib/api/src/ZincWorkerApi.scala
@@ -60,7 +60,7 @@ object Util{
classPath
.find(p => p.toString.endsWith(mavenStylePath) || p.toString.endsWith(ivyStylePath))
- .getOrElse(throw new Exception(s"Cannot find $mavenStylePath or $ivyStylePath"))
+ .getOrElse(throw new Exception(s"Cannot find $mavenStylePath or $ivyStylePath in ${classPath.mkString("[", ", ", "]")}"))
}
private val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r
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
--- /dev/null
+++ b/scalalib/resources/mill/scalalib/scalafmt/default.scalafmt.conf
diff --git a/scalalib/src/Dep.scala b/scalalib/src/Dep.scala
index 714fa21e..59c3be5e 100644
--- a/scalalib/src/Dep.scala
+++ b/scalalib/src/Dep.scala
@@ -9,16 +9,28 @@ case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
def artifactName(binaryVersion: String, fullVersion: String, platformSuffix: String) = {
val suffix = cross.suffixString(binaryVersion, fullVersion, platformSuffix)
- dep.module.name + suffix
+ dep.module.name.value + suffix
}
def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes))
def forceVersion(): Dep = copy(force = true)
- def exclude(exclusions: (String, String)*) = copy(dep = dep.copy(exclusions = dep.exclusions ++ exclusions))
+ def exclude(exclusions: (String, String)*) = copy(
+ dep = dep.copy(
+ exclusions =
+ dep.exclusions ++
+ exclusions.map{case (k, v) => (coursier.Organization(k), coursier.ModuleName(v))}
+ )
+ )
def excludeOrg(organizations: String*): Dep = exclude(organizations.map(_ -> "*"): _*)
def excludeName(names: String*): Dep = exclude(names.map("*" -> _): _*)
def toDependency(binaryVersion: String, fullVersion: String, platformSuffix: String) =
- dep.copy(module = dep.module.copy(name = artifactName(binaryVersion, fullVersion, platformSuffix)))
- def withConfiguration(configuration: String): Dep = copy(dep = dep.copy(configuration = configuration))
+ dep.copy(
+ module = dep.module.copy(
+ name = coursier.ModuleName(artifactName(binaryVersion, fullVersion, platformSuffix))
+ )
+ )
+ def withConfiguration(configuration: String): Dep = copy(
+ dep = dep.copy(configuration = coursier.core.Configuration(configuration))
+ )
/**
* If scalaVersion is a Dotty version, replace the cross-version suffix
@@ -49,14 +61,14 @@ case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
object Dep {
- val DefaultConfiguration = "default(compile)"
+ val DefaultConfiguration = coursier.core.Configuration("default(compile)")
implicit def parse(signature: String): Dep = {
val parts = signature.split(';')
val module = parts.head
val attributes = parts.tail.foldLeft(coursier.Attributes()) { (as, s) =>
s.split('=') match {
- case Array("classifier", v) => as.copy(classifier = v)
+ case Array("classifier", v) => as.copy(classifier = coursier.Classifier(v))
case Array(k, v) => throw new Exception(s"Unrecognized attribute: [$s]")
case _ => throw new Exception(s"Unable to parse attribute specifier: [$s]")
}
@@ -72,7 +84,15 @@ object Dep {
}).configure(attributes = attributes)
}
def apply(org: String, name: String, version: String, cross: CrossVersion, force: Boolean = false): Dep = {
- apply(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force)
+ apply(
+ coursier.Dependency(
+ coursier.Module(coursier.Organization(org), coursier.ModuleName(name)),
+ version,
+ DefaultConfiguration
+ ),
+ cross,
+ force
+ )
}
implicit def rw: RW[Dep] = macroRW
}
diff --git a/scalalib/src/GenIdeaImpl.scala b/scalalib/src/GenIdeaImpl.scala
index 3dd839da..404a0235 100755
--- a/scalalib/src/GenIdeaImpl.scala
+++ b/scalalib/src/GenIdeaImpl.scala
@@ -1,7 +1,7 @@
package mill.scalalib
import ammonite.runtime.SpecialClassLoader
-import coursier.{Cache, CoursierPaths, Repository}
+import coursier.Repository
import mill.define._
import mill.eval.{Evaluator, PathRef, Result}
import mill.api.Ctx.{Home, Log}
@@ -66,9 +66,12 @@ object GenIdeaImpl {
fetchMillModules: Boolean = true): Seq[(os.RelPath, scala.xml.Node)] = {
val modules = rootModule.millInternal.segmentsToModules.values
- .collect{ case x: scalalib.JavaModule => (x.millModuleSegments, x)}
+ .collect{ case x: scalalib.JavaModule => x }
+ .flatMap(_.transitiveModuleDeps)
+ .map(x => (x.millModuleSegments, x))
.toSeq
-
+ .distinct
+
val buildLibraryPaths =
if (!fetchMillModules) Nil
else sys.props.get("MILL_BUILD_LIBRARIES") match {
@@ -201,7 +204,7 @@ object GenIdeaImpl {
// Tries to group jars with their poms and sources.
def toResolvedJar(path : os.Path) : Option[ResolvedLibrary] = {
- val inCoursierCache = path.startsWith(os.Path(CoursierPaths.cacheDirectory()))
+ val inCoursierCache = path.startsWith(os.Path(coursier.paths.CoursierPaths.cacheDirectory()))
val isSource = path.last.endsWith("sources.jar")
val isPom = path.ext == "pom"
if (inCoursierCache && (isSource || isPom)) {
diff --git a/scalalib/src/JavaModule.scala b/scalalib/src/JavaModule.scala
index 1a5bc47b..7b373650 100644
--- a/scalalib/src/JavaModule.scala
+++ b/scalalib/src/JavaModule.scala
@@ -163,7 +163,6 @@ trait JavaModule extends mill.Module with TaskModule { outer =>
finalMainClassOpt().toOption match{
case None => ""
case Some(cls) =>
- val isWin = scala.util.Properties.isWin
mill.modules.Jvm.launcherUniversalScript(
cls,
Agg("$0"), Agg("%~dpnx0"),
@@ -298,36 +297,43 @@ trait JavaModule extends mill.Module with TaskModule { outer =>
}
/**
- * The documentation jar, containing all the Javadoc/Scaladoc HTML files, for
- * publishing to Maven Central
- */
+ * Additional options to be used by the javadoc tool.
+ * You should not set the `-d` setting for specifying the target directory,
+ * as that is done in the [[docJar]] target.
+ */
+ def javadocOptions: T[Seq[String]] = T { Seq[String]() }
+
+ /**
+ * The documentation jar, containing all the Javadoc/Scaladoc HTML files, for
+ * publishing to Maven Central
+ */
def docJar = T[PathRef] {
val outDir = T.ctx().dest
val javadocDir = outDir / 'javadoc
os.makeDir.all(javadocDir)
- val files = for{
+ val files = for {
ref <- allSources()
if os.exists(ref.path)
p <- (if (os.isDir(ref.path)) os.walk(ref.path) else Seq(ref.path))
if os.isFile(p) && (p.ext == "java")
} yield p.toNIO.toString
- val options = Seq("-d", javadocDir.toNIO.toString)
+ val options = javadocOptions() ++ Seq("-d", javadocDir.toNIO.toString)
- if (files.nonEmpty) Jvm.baseInteractiveSubprocess(
+ if (files.nonEmpty) Jvm.runSubprocess(
commandArgs = Seq(
"javadoc"
) ++ options ++
- Seq(
- "-classpath",
- compileClasspath()
+ Seq(
+ "-classpath",
+ compileClasspath()
.map(_.path)
.filter(_.ext != "pom")
.mkString(java.io.File.pathSeparator)
- ) ++
- files.map(_.toString),
+ ) ++
+ files.map(_.toString),
envArgs = Map(),
workingDir = T.ctx().dest
)
@@ -377,8 +383,14 @@ trait JavaModule extends mill.Module with TaskModule { outer =>
Some(mapDependencies())
)
- println(coursier.util.Print.dependencyTree(flattened, resolution,
- printExclusions = false, reverse = inverse))
+ println(
+ coursier.util.Print.dependencyTree(
+ roots = flattened,
+ resolution = resolution,
+ printExclusions = false,
+ reverse = inverse
+ )
+ )
Result.Success()
}
@@ -517,10 +529,18 @@ trait JavaModule extends mill.Module with TaskModule { outer =>
}
}
- // publish artifact with name "mill_2.12.4" instead of "mill_2.12"
-
+ /**
+ * Override this to change the published artifact id.
+ * For example, by default a scala module foo.baz might be published as foo-baz_2.12 and a java module would be foo-baz.
+ * Setting this to baz would result in a scala artifact baz_2.12 or a java artifact baz.
+ */
def artifactName: T[String] = millModuleSegments.parts.mkString("-")
+ /**
+ * The exact id of the artifact to be published. You probably don't want to override this.
+ * If you want to customize the name of the artifact, override artifactName instead.
+ * If you want to customize the scala version in the artifact id, see ScalaModule.artifactScalaVersion
+ */
def artifactId: T[String] = artifactName()
def intellijModulePath: os.Path = millSourcePath
diff --git a/scalalib/src/Lib.scala b/scalalib/src/Lib.scala
index 2706850e..da133d94 100644
--- a/scalalib/src/Lib.scala
+++ b/scalalib/src/Lib.scala
@@ -8,7 +8,7 @@ import java.util.zip.ZipInputStream
import javax.tools.ToolProvider
import ammonite.util.Util
-import coursier.{Cache, Dependency, Fetch, Repository, Resolution}
+import coursier.{Dependency, Fetch, Repository, Resolution}
import mill.scalalib.api.Util.isDotty
import mill.Agg
import mill.eval.{PathRef, Result}
diff --git a/scalalib/src/PublishModule.scala b/scalalib/src/PublishModule.scala
index 0fd862b3..9a4374f2 100644
--- a/scalalib/src/PublishModule.scala
+++ b/scalalib/src/PublishModule.scala
@@ -106,9 +106,9 @@ object PublishModule extends ExternalModule {
def publishAll(sonatypeCreds: String,
gpgPassphrase: String = null,
- gpgKeyName: String = null,
publishArtifacts: mill.main.Tasks[PublishModule.PublishData],
release: Boolean = false,
+ gpgKeyName: String = null,
sonatypeUri: String = "https://oss.sonatype.org/service/local",
sonatypeSnapshotUri: String = "https://oss.sonatype.org/content/repositories/snapshots",
signed: Boolean = true) = T.command {
diff --git a/scalalib/src/ScalaModule.scala b/scalalib/src/ScalaModule.scala
index 5fad1664..f45a7e98 100644
--- a/scalalib/src/ScalaModule.scala
+++ b/scalalib/src/ScalaModule.scala
@@ -48,8 +48,13 @@ trait ScalaModule extends JavaModule { outer =>
Set("dotty-library", "dotty-compiler")
else
Set("scala-library", "scala-compiler", "scala-reflect")
- if (!artifacts(d.module.name)) d
- else d.copy(module = d.module.copy(organization = scalaOrganization()), version = scalaVersion())
+ if (!artifacts(d.module.name.value)) d
+ else d.copy(
+ module = d.module.copy(
+ organization = coursier.Organization(scalaOrganization())
+ ),
+ version = scalaVersion()
+ )
}
override def resolveCoursierDependency: Task[Dep => coursier.Dependency] = T.task{
diff --git a/scalalib/src/Versions.scala b/scalalib/src/Versions.scala
index 231eb86b..973b05ed 100644
--- a/scalalib/src/Versions.scala
+++ b/scalalib/src/Versions.scala
@@ -2,7 +2,7 @@ package mill.scalalib
object Versions {
// Keep synchronized with ammonite dependency in core in build.sc
- val ammonite = "1.6.0"
+ val ammonite = "1.6.7"
// Keep synchronized with zinc dependency in scalalib.worker in build.sc
val zinc = "1.2.5"
}
diff --git a/scalalib/src/ZincWorkerModule.scala b/scalalib/src/ZincWorkerModule.scala
index 50d37611..4c94102c 100644
--- a/scalalib/src/ZincWorkerModule.scala
+++ b/scalalib/src/ZincWorkerModule.scala
@@ -1,6 +1,6 @@
package mill.scalalib
-import coursier.Cache
+
import coursier.maven.MavenRepository
import mill.Agg
import mill.T
@@ -17,7 +17,7 @@ object ZincWorkerModule extends mill.define.ExternalModule with ZincWorkerModule
}
trait ZincWorkerModule extends mill.Module{
def repositories = Seq(
- Cache.ivy2Local,
+ coursier.LocalRepositories.ivy2Local,
MavenRepository("https://repo1.maven.org/maven2"),
MavenRepository("https://oss.sonatype.org/content/repositories/releases")
)
@@ -69,11 +69,10 @@ trait ZincWorkerModule extends mill.Module{
instance.asInstanceOf[mill.scalalib.api.ZincWorkerApi]
}
- private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r
def scalaCompilerBridgeSourceJar(scalaVersion: String,
scalaOrganization: String) = {
val (scalaVersion0, scalaBinaryVersion0) = scalaVersion match {
- case Milestone213(_, _) => ("2.13.0-M2", "2.13.0-M2")
+ case s if s.startsWith("2.13.") => ("2.13.0-M2", "2.13.0-M2")
case _ => (scalaVersion, mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion))
}
diff --git a/scalalib/src/dependency/metadata/MavenMetadataLoader.scala b/scalalib/src/dependency/metadata/MavenMetadataLoader.scala
index 491911bf..e40337fc 100644
--- a/scalalib/src/dependency/metadata/MavenMetadataLoader.scala
+++ b/scalalib/src/dependency/metadata/MavenMetadataLoader.scala
@@ -1,6 +1,5 @@
package mill.scalalib.dependency.metadata
-import coursier.Cache
import coursier.maven.MavenRepository
import coursier.util.Task
import mill.scalalib.dependency.versions.Version
@@ -8,14 +7,14 @@ import mill.scalalib.dependency.versions.Version
private[dependency] final case class MavenMetadataLoader(mavenRepo: MavenRepository)
extends MetadataLoader {
- private val fetch = Cache.fetch[Task]()
+ private val fetch = coursier.cache.FileCache[Task].fetch
override def getVersions(module: coursier.Module): List[Version] = {
import scala.concurrent.ExecutionContext.Implicits.global
// TODO fallback to 'versionsFromListing' if 'versions' doesn't work? (needs to be made public in coursier first)
val allVersions = mavenRepo.versions(module, fetch).run.unsafeRun
allVersions
- .map(_.available.map(Version(_)))
+ .map(_._1.available.map(Version(_)))
.getOrElse(List.empty)
}
}
diff --git a/scalalib/src/publish/SonatypeHttpApi.scala b/scalalib/src/publish/SonatypeHttpApi.scala
index 12defa93..217d556e 100644
--- a/scalalib/src/publish/SonatypeHttpApi.scala
+++ b/scalalib/src/publish/SonatypeHttpApi.scala
@@ -5,18 +5,10 @@ import java.util.Base64
import scala.concurrent.duration._
-import scalaj.http.{BaseHttp, HttpOptions, HttpRequest, HttpResponse}
-
-object PatientHttp
- extends BaseHttp(
- options = Seq(
- HttpOptions.connTimeout(5.seconds.toMillis.toInt),
- HttpOptions.readTimeout(1.minute.toMillis.toInt),
- HttpOptions.followRedirects(false)
- )
- )
+
class SonatypeHttpApi(uri: String, credentials: String) {
+ val http = requests.Session(connectTimeout = 5000, readTimeout = 1000, maxRedirects = 0)
private val base64Creds = base64(credentials)
@@ -29,12 +21,19 @@ class SonatypeHttpApi(uri: String, credentials: String) {
// https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles.html
def getStagingProfileUri(groupId: String): String = {
val response = withRetry(
- PatientHttp(s"$uri/staging/profiles").headers(commonHeaders))
- .throwError
+ http.get(
+ s"$uri/staging/profiles",
+ headers = commonHeaders
+ )
+ )
+
+ if (!response.is2xx) {
+ throw new Exception(s"$uri/staging/profiles returned ${response.statusCode}")
+ }
val resourceUri =
ujson
- .read(response.body)("data")
+ .read(response.data.text)("data")
.arr
.find(profile =>
groupId.split('.').startsWith(profile("name").str.split('.')))
@@ -47,79 +46,84 @@ class SonatypeHttpApi(uri: String, credentials: String) {
}
def getStagingRepoState(stagingRepoId: String): String = {
- val response = PatientHttp(s"${uri}/staging/repository/${stagingRepoId}")
- .option(HttpOptions.readTimeout(60000))
- .headers(commonHeaders)
- .asString
- .throwError
-
- ujson.read(response.body)("type").str.toString
+ val response = http.get(
+ s"${uri}/staging/repository/${stagingRepoId}",
+ readTimeout = 60000,
+ headers = commonHeaders
+ )
+ ujson.read(response.data.text)("type").str.toString
}
// https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_start.html
def createStagingRepo(profileUri: String, groupId: String): String = {
- val response = withRetry(PatientHttp(s"${profileUri}/start")
- .headers(commonHeaders)
- .postData(
- s"""{"data": {"description": "fresh staging profile for ${groupId}"}}"""))
- .throwError
+ val response = http.post(
+ s"${profileUri}/start",
+ headers = commonHeaders,
+ data = s"""{"data": {"description": "fresh staging profile for ${groupId}"}}"""
+ )
- ujson.read(response.body)("data")("stagedRepositoryId").str.toString
+ if (!response.is2xx) {
+ throw new Exception(s"$uri/staging/profiles returned ${response.statusCode}")
+ }
+
+ ujson.read(response.data.text)("data")("stagedRepositoryId").str.toString
}
// https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_finish.html
def closeStagingRepo(profileUri: String, repositoryId: String): Boolean = {
val response = withRetry(
- PatientHttp(s"${profileUri}/finish")
- .headers(commonHeaders)
- .postData(
- s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "closing staging repository"}}"""
- ))
+ http.post(
+ s"${profileUri}/finish",
+ headers = commonHeaders,
+ data = s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "closing staging repository"}}"""
+ )
+ )
- response.code == 201
+ response.statusCode == 201
}
// https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_promote.html
def promoteStagingRepo(profileUri: String, repositoryId: String): Boolean = {
val response = withRetry(
- PatientHttp(s"${profileUri}/promote")
- .headers(commonHeaders)
- .postData(
- s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "promote staging repository"}}"""
- ))
+ http.post(
+ s"${profileUri}/promote",
+ headers = commonHeaders,
+ data = s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "promote staging repository"}}"""
+ )
+ )
- response.code == 201
+ response.statusCode == 201
}
// https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_drop.html
def dropStagingRepo(profileUri: String, repositoryId: String): Boolean = {
val response = withRetry(
- PatientHttp(s"${profileUri}/drop")
- .headers(commonHeaders)
- .postData(
- s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "drop staging repository"}}"""
- ))
-
- response.code == 201
+ http.post(
+ s"${profileUri}/drop",
+ headers = commonHeaders,
+ data = s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "drop staging repository"}}"""
+ )
+ )
+ response.statusCode == 201
}
private val uploadTimeout = 5.minutes.toMillis.toInt
- def upload(uri: String, data: Array[Byte]): HttpResponse[String] = {
- PatientHttp(uri)
- .option(HttpOptions.readTimeout(uploadTimeout))
- .method("PUT")
- .headers(
+ def upload(uri: String, data: Array[Byte]): requests.Response = {
+ http.put(
+ uri,
+ readTimeout = uploadTimeout,
+ headers = Seq(
"Content-Type" -> "application/binary",
"Authorization" -> s"Basic ${base64Creds}"
- )
- .put(data)
- .asString
+ ),
+ data = data
+ )
}
- private def withRetry(request: HttpRequest,
- retries: Int = 10): HttpResponse[String] = {
- val resp = request.asString
+ private def withRetry(request: => requests.Response,
+ retries: Int = 10): requests.Response = {
+ val resp = request
if (resp.is5xx && retries > 0) {
Thread.sleep(500)
withRetry(request, retries - 1)
diff --git a/scalalib/src/publish/SonatypePublisher.scala b/scalalib/src/publish/SonatypePublisher.scala
index 6dcadd5b..5ca8f5c1 100644
--- a/scalalib/src/publish/SonatypePublisher.scala
+++ b/scalalib/src/publish/SonatypePublisher.scala
@@ -5,7 +5,6 @@ import java.security.MessageDigest
import mill.api.Logger
import os.Shellable
-import scalaj.http.HttpResponse
class SonatypePublisher(uri: String,
snapshotUri: String,
@@ -105,13 +104,13 @@ class SonatypePublisher(uri: String,
}
}
- private def reportPublishResults(publishResults: Seq[HttpResponse[String]],
+ private def reportPublishResults(publishResults: Seq[requests.Response],
artifacts: Seq[Artifact]) = {
if (publishResults.forall(_.is2xx)) {
log.info(s"Published ${artifacts.map(_.id).mkString(", ")} to Sonatype")
} else {
val errors = publishResults.filterNot(_.is2xx).map { response =>
- s"Code: ${response.code}, message: ${response.body}"
+ s"Code: ${response.statusCode}, message: ${response.data.text}"
}
throw new RuntimeException(
s"Failed to publish ${artifacts.map(_.id).mkString(", ")} to Sonatype. Errors: \n${errors.mkString("\n")}"
diff --git a/scalalib/src/publish/settings.scala b/scalalib/src/publish/settings.scala
index bca81cf0..d2801752 100644
--- a/scalalib/src/publish/settings.scala
+++ b/scalalib/src/publish/settings.scala
@@ -23,13 +23,13 @@ object Artifact {
)
Dependency(
Artifact(
- dep.dep.module.organization,
+ dep.dep.module.organization.value,
name,
dep.dep.version
),
Scope.Compile,
- if (dep.dep.configuration == "") None else Some(dep.dep.configuration),
- dep.dep.exclusions.toList
+ if (dep.dep.configuration == "") None else Some(dep.dep.configuration.value),
+ dep.dep.exclusions.toList.map{case (a, b) => (a.value, b.value)}
)
}
}
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)
+ }
+ }
}
diff --git a/scalalib/test/src/GenIdeaTests.scala b/scalalib/test/src/GenIdeaTests.scala
index f8d9a0ed..60c9f9a8 100644
--- a/scalalib/test/src/GenIdeaTests.scala
+++ b/scalalib/test/src/GenIdeaTests.scala
@@ -1,6 +1,5 @@
package mill.scalalib
-import coursier.Cache
import mill._
import mill.util.{TestEvaluator, TestUtil}
import utest._
@@ -57,6 +56,6 @@ object GenIdeaTests extends TestSuite {
private def normaliseLibraryPaths(in: String): String = {
- in.replaceAll(Cache.default.toPath.toAbsolutePath.toString, "COURSIER_HOME")
+ in.replaceAll(coursier.paths.CoursierPaths.cacheDirectory().toString, "COURSIER_HOME")
}
}
diff --git a/scalalib/test/src/HelloWorldTests.scala b/scalalib/test/src/HelloWorldTests.scala
index da08f056..57750991 100644
--- a/scalalib/test/src/HelloWorldTests.scala
+++ b/scalalib/test/src/HelloWorldTests.scala
@@ -766,7 +766,7 @@ object HelloWorldTests extends TestSuite {
resourcePath = helloWorldMultiResourcePath
)
- 'writeFirstWhenNoRule - {
+ 'writeDownstreamWhenNoRule - {
'withDeps - workspaceTest(HelloWorldAkkaHttpNoRules) { eval =>
val Right((result, _)) = eval.apply(HelloWorldAkkaHttpNoRules.core.assembly)
@@ -802,11 +802,11 @@ object HelloWorldTests extends TestSuite {
val referenceContent = readFileFromJar(jarFile, "reference.conf")
assert(
- referenceContent.contains("Model Reference Config File"),
- referenceContent.contains("foo.bar=2"),
+ !referenceContent.contains("Model Reference Config File"),
+ !referenceContent.contains("foo.bar=2"),
- !referenceContent.contains("Core Reference Config File"),
- !referenceContent.contains("bar.baz=hello")
+ referenceContent.contains("Core Reference Config File"),
+ referenceContent.contains("bar.baz=hello")
)
}
}
diff --git a/scalalib/test/src/ResolveDepsTests.scala b/scalalib/test/src/ResolveDepsTests.scala
index ce905907..94b8adb9 100644
--- a/scalalib/test/src/ResolveDepsTests.scala
+++ b/scalalib/test/src/ResolveDepsTests.scala
@@ -1,6 +1,5 @@
package mill.scalalib
-import coursier.Cache
import coursier.maven.MavenRepository
import mill.api.Result.{Failure, Success}
import mill.eval.{PathRef, Result}
@@ -8,7 +7,7 @@ import mill.api.Loose.Agg
import utest._
object ResolveDepsTests extends TestSuite {
- val repos = Seq(Cache.ivy2Local, MavenRepository("https://repo1.maven.org/maven2"))
+ val repos = Seq(coursier.LocalRepositories.ivy2Local, MavenRepository("https://repo1.maven.org/maven2"))
def evalDeps(deps: Agg[Dep]): Result[Agg[PathRef]] = Lib.resolveDependencies(
repos,
diff --git a/scalalib/test/src/dependency/metadata/MetadataLoaderFactoryTests.scala b/scalalib/test/src/dependency/metadata/MetadataLoaderFactoryTests.scala
index 4c2206b8..af2ea617 100644
--- a/scalalib/test/src/dependency/metadata/MetadataLoaderFactoryTests.scala
+++ b/scalalib/test/src/dependency/metadata/MetadataLoaderFactoryTests.scala
@@ -28,8 +28,8 @@
*/
package mill.scalalib.dependency.metadata
-import coursier.Fetch.Content
-import coursier.core.{Artifact, Module, Project, Repository}
+import coursier.Fetch
+import coursier.core.{Artifact, Classifier, Dependency, Module, Project, Repository}
import coursier.ivy.IvyRepository
import coursier.maven.MavenRepository
import coursier.util.{EitherT, Monad}
@@ -57,8 +57,12 @@ object MetadataLoaderFactoryTests extends TestSuite {
}
case class CustomRepository() extends Repository {
- override def find[F[_]](module: Module, version: String, fetch: Content[F])(
- implicit F: Monad[F]): EitherT[F, String, (Artifact.Source, Project)] =
+ override def find[F[_]](module: Module, version: String, fetch: coursier.Repository.Fetch[F])
+ (implicit F: Monad[F]): EitherT[F, String, (Artifact.Source, Project)] =
???
+
+ override def artifacts(dependency: Dependency,
+ project: Project,
+ overrideClassifiers: Option[Seq[Classifier]]) = ???
}
}
diff --git a/scalalib/test/src/dependency/updates/UpdatesFinderTests.scala b/scalalib/test/src/dependency/updates/UpdatesFinderTests.scala
index 7b6e6e36..3b613bcb 100644
--- a/scalalib/test/src/dependency/updates/UpdatesFinderTests.scala
+++ b/scalalib/test/src/dependency/updates/UpdatesFinderTests.scala
@@ -37,7 +37,10 @@ object UpdatesFinderTests extends TestSuite {
available: Seq[String],
allowPreRelease: Boolean) = {
val dependency = coursier.Dependency(
- coursier.Module("com.example.organization", "example-artifact"),
+ coursier.Module(
+ coursier.Organization("com.example.organization"),
+ coursier.ModuleName("example-artifact")
+ ),
current)
val currentVersion = Version(current)
val allVersions = available.map(Version(_)).toSet
diff --git a/scalalib/worker/src/ZincWorkerImpl.scala b/scalalib/worker/src/ZincWorkerImpl.scala
index a6360d8f..30f6b834 100644
--- a/scalalib/worker/src/ZincWorkerImpl.scala
+++ b/scalalib/worker/src/ZincWorkerImpl.scala
@@ -85,17 +85,30 @@ class ZincWorkerImpl(compilerBridge: Either[
val sourceFolder = mill.api.IO.unpackZip(srcJars(scalaVersion, scalaOrganization))(workingDir)
val classloader = mill.api.ClassLoader.create(compilerJars.map(_.toURI.toURL), null)(ctx0)
- val compilerMain = classloader.loadClass(
- if (isDotty(scalaVersion)) "dotty.tools.dotc.Main"
- else "scala.tools.nsc.Main"
- )
+
+ val sources = os.walk(sourceFolder.path).filter(a => a.ext == "scala" || a.ext == "java")
+
val argsArray = Array[String](
"-d", compiledDest.toString,
"-classpath", (compilerJars ++ compilerBridgeClasspath).mkString(File.pathSeparator)
- ) ++ os.walk(sourceFolder.path).filter(_.ext == "scala").map(_.toString)
+ ) ++ sources.map(_.toString)
+
+ val allScala = sources.forall(_.ext == "scala")
+ val allJava = sources.forall(_.ext == "java")
+ if (allJava) {
+ import scala.sys.process._
+ (Seq("javac") ++ argsArray).!
+ } else if (allScala) {
+ val compilerMain = classloader.loadClass(
+ if (isDotty(scalaVersion)) "dotty.tools.dotc.Main"
+ else "scala.tools.nsc.Main"
+ )
+ compilerMain.getMethod("process", classOf[Array[String]])
+ .invoke(null, argsArray)
+ } else {
+ throw new IllegalArgumentException("Currently not implemented case.")
+ }
- compilerMain.getMethod("process", classOf[Array[String]])
- .invoke(null, argsArray)
}
compiledDest
}
@@ -323,8 +336,7 @@ class ZincWorkerImpl(compilerBridge: Either[
pr = {
val prev = store.get()
PreviousResult.of(prev.map(_.getAnalysis), prev.map(_.getMiniSetup))
- },
- Optional.empty[java.io.File]
+ }
)
try {
diff --git a/scalanativelib/src/ScalaNativeModule.scala b/scalanativelib/src/ScalaNativeModule.scala
index 38525032..b04b00a1 100644
--- a/scalanativelib/src/ScalaNativeModule.scala
+++ b/scalanativelib/src/ScalaNativeModule.scala
@@ -3,7 +3,6 @@ package scalanativelib
import java.net.URLClassLoader
-import coursier.Cache
import coursier.maven.MavenRepository
import mill.define.{Target, Task}
import mill.api.Result
@@ -50,7 +49,7 @@ trait ScalaNativeModule extends ScalaModule { outer =>
Result.Success(Agg(workerPath.split(',').map(p => PathRef(os.Path(p), quick = true)): _*))
else
Lib.resolveDependencies(
- Seq(Cache.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")),
+ Seq(coursier.LocalRepositories.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")),
Lib.depToDependency(_, "2.12.4", ""),
Seq(ivy"com.lihaoyi::mill-scalanativelib-worker-${scalaNativeBinaryVersion()}:${sys.props("MILL_VERSION")}"),
ctx = Some(implicitly[mill.util.Ctx.Log])
@@ -82,7 +81,7 @@ trait ScalaNativeModule extends ScalaModule { outer =>
def bridgeFullClassPath = T {
Lib.resolveDependencies(
- Seq(Cache.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")),
+ Seq(coursier.LocalRepositories.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")),
Lib.depToDependency(_, scalaVersion(), platformSuffix()),
toolsIvyDeps(),
ctx = Some(implicitly[mill.util.Ctx.Log])
@@ -202,7 +201,7 @@ trait TestScalaNativeModule extends ScalaNativeModule with TestModule { testOute
Lib.resolveDependencies(
repositories,
Lib.depToDependency(_, scalaVersion(), ""),
- transitiveIvyDeps().filter(d => d.cross.isBinary && supportedTestFrameworks(d.dep.module.name)),
+ transitiveIvyDeps().filter(d => d.cross.isBinary && supportedTestFrameworks(d.dep.module.name.value)),
ctx = Some(implicitly[mill.util.Ctx.Log])
)
}
diff --git a/scratch/build.sc b/scratch/build.sc
index 0a33c86e..d9271f2d 100644
--- a/scratch/build.sc
+++ b/scratch/build.sc
@@ -1,31 +1,7 @@
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!"
- }
+object core extends ScalaModule{
+ def scalaVersion = "2.13.0-RC2"
}