diff options
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 @@ -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() @@ -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" } |