diff options
52 files changed, 1235 insertions, 739 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 693bd85e..955f6371 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,6 +9,8 @@ clone_folder: c:\mill environment: matrix: + - COMPILER: default + JAVA_HOME: C:\Program Files\Java\jdk9 - COMPILER: cygwin CYGWIN_DIR: cygwin64 JAVA_HOME: C:\Program Files\Java\jdk9 @@ -17,26 +19,26 @@ environment: MSYS2_DIR: msys64 MSYSTEM: MINGW64 JAVA_HOME: C:\Program Files\Java\jdk1.8.0 - - COMPILER: msys2 - MSYS2_ARCH: x86_64 - MSYS2_DIR: msys64 - MSYSTEM: MINGW64 - JAVA_HOME: C:\Program Files\Java\jdk9 cache: - - '%LOCALAPPDATA%\Coursier\cache' + - '%LOCALAPPDATA%\Coursier\cache -> build.sc' install: - - SET MILL_URL=https://github.com/lihaoyi/mill/releases/download/0.1.7/0.1.7-8-b913c6 + - SET MILL_URL=https://github.com/lihaoyi/mill/releases/download/0.1.7/0.1.7-53-cc0407 build_script: + - if [%COMPILER%]==[default] ( + SET "PATH=%JAVA_HOME%\bin;%PATH%" && + MD C:\bin && + curl -Lo C:\bin\mill.bat %MILL_URL% && + cmd /C C:\bin\mill.bat -i all __.publishLocal release && + cmd /C C:\mill\out\release\dest\mill.bat -i all main.test scalajslib.test) - if [%COMPILER%]==[msys2] ( SET "PATH=%JAVA_HOME%\bin;C:\%MSYS2_DIR%\%MSYSTEM%\bin;C:\%MSYS2_DIR%\usr\bin;%PATH%" && C:\%MSYS2_DIR%\usr\bin\bash -lc 'mkdir -p /usr/local/bin' && C:\%MSYS2_DIR%\usr\bin\bash -lc "curl -Lo /usr/local/bin/mill %MILL_URL%" && C:\%MSYS2_DIR%\usr\bin\bash -lc 'chmod +x /usr/local/bin/mill' && C:\%MSYS2_DIR%\usr\bin\bash -lc "cd /c/mill && mill -i all __.publishLocal release" && - rd /s /q %USERPROFILE%\.mill && C:\%MSYS2_DIR%\usr\bin\bash -lc "cd /c/mill && out/release/dest/mill -i all main.test scalajslib.test") - if [%COMPILER%]==[cygwin] ( SET "PATH=%JAVA_HOME%\bin;C:\%CYGWIN_DIR%\bin;C:\%CYGWIN_DIR%\usr\bin;%PATH%" && diff --git a/.travis.yml b/.travis.yml index b63d7028..3a7f5fdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,29 +5,33 @@ dist: trusty matrix: include: - stage: build - env: CI_SCRIPT=ci/test-mill-0.sh - jdk: oraclejdk9 - - stage: build - env: CI_SCRIPT=ci/test-mill-0.sh + env: CI_SCRIPT=ci/test-mill-release.sh jdk: oraclejdk8 - stage: build - env: CI_SCRIPT=ci/test-mill-1.sh - jdk: oraclejdk8 + env: CI_SCRIPT=ci/test-mill-release.sh + jdk: oraclejdk9 + - stage: build - env: CI_SCRIPT=ci/test-mill-2.sh + env: CI_SCRIPT=ci/test-mill-dev.sh jdk: oraclejdk8 - stage: build env: CI_SCRIPT=ci/test-mill-dev.sh jdk: oraclejdk9 - stage: build - env: CI_SCRIPT=ci/test-mill-dev.sh + env: CI_SCRIPT=ci/test-mill-bootstrap.sh + jdk: oraclejdk9 + + - stage: build + env: CI_SCRIPT=ci/test-mill-0.sh jdk: oraclejdk8 - stage: build - env: CI_SCRIPT=ci/test-mill-release.sh - jdk: oraclejdk9 + env: CI_SCRIPT=ci/test-mill-1.sh + jdk: oraclejdk8 - stage: build - env: CI_SCRIPT=ci/test-mill-release.sh + env: CI_SCRIPT=ci/test-mill-2.sh jdk: oraclejdk8 + + - stage: release env: CI_SCRIPT="ci/on-master.py ci/release.sh" jdk: oraclejdk8 @@ -36,7 +40,7 @@ matrix: jdk: oraclejdk8 script: - - curl -L -o ~/bin/mill https://github.com/lihaoyi/mill/releases/download/0.1.7/0.1.7-8-b913c6 && chmod +x ~/bin/mill + - curl -L -o ~/bin/mill https://github.com/lihaoyi/mill/releases/download/0.1.7/0.1.7-53-cc0407 && chmod +x ~/bin/mill - export PATH=~/bin/mill:$PATH - "$CI_SCRIPT" @@ -11,7 +11,7 @@ import publish._ import mill.modules.Jvm.createAssembly import upickle.Js trait MillPublishModule extends PublishModule{ - def scalaVersion = "2.12.4" + def artifactName = "mill-" + super.artifactName() def publishVersion = build.publishVersion()._2 @@ -27,17 +27,18 @@ trait MillPublishModule extends PublishModule{ ) def javacOptions = Seq("-source", "1.8", "-target", "1.8") - } -object moduledefs extends MillPublishModule{ + +object moduledefs extends MillPublishModule with ScalaModule{ + def scalaVersion = T{ "2.12.4" } def ivyDeps = Agg( ivy"org.scala-lang:scala-compiler:${scalaVersion()}", ivy"com.lihaoyi::sourcecode:0.1.4" ) } -trait MillModule extends MillPublishModule{ outer => - +trait MillModule extends MillPublishModule with ScalaModule{ outer => + def scalaVersion = T{ "2.12.4" } def compileIvyDeps = Agg(ivy"com.lihaoyi::acyclic:0.1.7") def scalacOptions = Seq("-P:acyclic:force") def scalacPluginIvyDeps = Agg(ivy"com.lihaoyi::acyclic:0.1.7") @@ -63,12 +64,10 @@ trait MillModule extends MillPublishModule{ outer => } } -object clientserver extends MillModule{ +object client extends MillPublishModule{ def ivyDeps = Agg( - ivy"com.lihaoyi:::ammonite:1.1.0-7-33b728c", ivy"org.scala-sbt.ipcsocket:ipcsocket:1.0.0" ) - val test = new Tests(implicitly) } object core extends MillModule { @@ -80,7 +79,7 @@ object core extends MillModule { def ivyDeps = Agg( ivy"com.lihaoyi::sourcecode:0.1.4", - ivy"com.lihaoyi:::ammonite:1.1.0-7-33b728c", + ivy"com.lihaoyi:::ammonite:1.1.0-14-037b8eb", ) def generatedSources = T { @@ -89,7 +88,7 @@ object core extends MillModule { } object main extends MillModule { - def moduleDeps = Seq(core, clientserver) + def moduleDeps = Seq(core, client) def compileIvyDeps = Agg( @@ -205,37 +204,66 @@ object integration extends MillModule{ def forkArgs = testArgs() } -def launcherScript(isWin: Boolean, - jvmArgs: Seq[String], - classPath: Agg[String]) = { +private def universalScript(shellCommands: String, + cmdCommands: String, + shebang: Boolean = false): String = { + Seq( + if (shebang) "#!/usr/bin/env sh" else "", + "@ 2>/dev/null # 2>nul & echo off & goto BOF\r", + ":", + shellCommands.replaceAll("\r\n|\n", "\n"), + "exit", + Seq( + "", + ":BOF", + "@echo off", + cmdCommands.replaceAll("\r\n|\n", "\r\n"), + "exit /B %errorlevel%", + "" + ).mkString("\r\n") + ).filterNot(_.isEmpty).mkString("\n") +} + +def launcherScript(jvmArgs: Seq[String], + shellClassPath: Agg[String], + cmdClassPath: Agg[String]) = { val jvmArgsStr = jvmArgs.mkString(" ") - val classPathStr = if (isWin) classPath.mkString(";") else classPath.mkString(":") - if (isWin) - s"""::#! - |@echo off - |if "%1" == "-i" set _I_=true - |if "%1" == "--interactive" set _I_=true - |if defined _I_ ( - | java $jvmArgsStr %JAVA_OPTS% -cp "$classPathStr" mill.Main %* - |) else ( - | java $jvmArgsStr %JAVA_OPTS% -cp "$classPathStr" mill.clientserver.Client %* - |) - |EXIT /B %errorlevel% - """.stripMargin.split('\n').mkString("\r\n") - else - s"""#!/usr/bin/env sh - | - |case "$$1" in - | -i | --interactive ) - | exec java $jvmArgsStr $$JAVA_OPTS -cp "$classPathStr" mill.Main "$$@" - | ;; - | *) - | exec java $jvmArgsStr $$JAVA_OPTS -cp "$classPathStr" mill.clientserver.Client "$$@" - | ;; - |esac - """.stripMargin + universalScript( + shellCommands = { + def java(mainClass: String) = + s"""exec java $jvmArgsStr $$JAVA_OPTS -cp "${shellClassPath.mkString(":")}" $mainClass "$$@"""" + + s"""case "$$1" in + | -i | --interactive ) + | ${java("mill.Main")} + | ;; + | *) + | ${java("mill.client.Main")} + | ;; + |esac""".stripMargin + }, + cmdCommands = { + def java(mainClass: String) = + s"""java $jvmArgsStr %JAVA_OPTS% -cp "${cmdClassPath.mkString(";")}" $mainClass %*""" + + s"""if "%1" == "-i" set _I_=true + |if "%1" == "--interactive" set _I_=true + |if defined _I_ ( + | ${java("mill.Main")} + |) else ( + | ${java("mill.client.Main")} + |)""".stripMargin + } + ) } +val isBatch = + scala.util.Properties.isWin && + !(org.jline.utils.OSUtils.IS_CYGWIN + || org.jline.utils.OSUtils.IS_MINGW + || "MSYS" == System.getProperty("MSYSTEM")) + + object dev extends MillModule{ def moduleDeps = Seq(scalalib, scalajslib) def forkArgs = T{ @@ -243,7 +271,7 @@ object dev extends MillModule{ } def launcher = T{ val isWin = scala.util.Properties.isWin - val outputPath = T.ctx().dest / (if (isWin) "run.bat" else "run") + val outputPath = T.ctx().dest / (if (isBatch) "run.bat" else "run") write(outputPath, prependShellScript()) @@ -258,12 +286,15 @@ object dev extends MillModule{ } def assembly = T{ - val filename = if (scala.util.Properties.isWin) "mill.bat" else "mill" + val filename = if (isBatch) "mill.bat" else "mill" mv(super.assembly().path, T.ctx().dest / filename) PathRef(T.ctx().dest / filename) } - def prependShellScript = launcherScript(scala.util.Properties.isWin, forkArgs(), runClasspath().map(_.path.toString)) + def prependShellScript = T{ + val classpath = runClasspath().map(_.path.toString) + launcherScript(forkArgs(), classpath, classpath) + } def run(args: String*) = T.command{ args match{ @@ -282,22 +313,16 @@ object dev extends MillModule{ } } - -private def releaseHelper(dest: Path, - cp: Agg[Path], - ver: String, - isWin: Boolean) - (implicit ctx: mill.util.Ctx.Dest): PathRef = { - val (filename, arg) = - if (isWin) ("mill.bat", "%~dp0%~nx0") - else ("mill", "$0") +def release = T{ + val dest = T.ctx().dest + val filename = if (isBatch) "mill.bat" else "mill" mv( createAssembly( - cp, + dev.runClasspath().map(_.path), prependShellScript = launcherScript( - isWin, - Seq("-DMILL_VERSION=" + ver), - Agg(arg) + Seq("-DMILL_VERSION=" + publishVersion()._2), + Agg("$0"), + Agg("%~dpnx0") ) ).path, dest / filename @@ -305,30 +330,6 @@ private def releaseHelper(dest: Path, PathRef(dest / filename) } -def release = T{ - releaseHelper( - T.ctx().dest, - dev.runClasspath().map(_.path), - publishVersion()._2, - false) -} - -def releaseBatch = T{ - releaseHelper( - T.ctx().dest, - dev.runClasspath().map(_.path), - publishVersion()._2, - true) -} - -def releaseAll = T{ - val dest = T.ctx().dest - val cp = dev.runClasspath().map(_.path) - val ver = publishVersion()._2 - for (isWin <- Seq(false, true)) - yield (isWin, releaseHelper(dest, cp, ver, isWin)) -} - val isMasterCommit = { sys.env.get("TRAVIS_PULL_REQUEST") == Some("false") && (sys.env.get("TRAVIS_BRANCH") == Some("master") || sys.env("TRAVIS_TAG") != "") @@ -377,8 +378,5 @@ def uploadToGithub(authKey: String) = T.command{ .asString } - for ((isWin, pr) <- releaseAll()) - upload.apply(pr.path, releaseTag, - if (isWin) s"mill-$label.bat" // so browser downloads it as mill-<version>.bat (?) - else label, authKey) + upload.apply(release().path, releaseTag, label, authKey) } diff --git a/ci/test-mill-0.sh b/ci/test-mill-0.sh index 3d1470f1..91a1ebf3 100755 --- a/ci/test-mill-0.sh +++ b/ci/test-mill-0.sh @@ -5,5 +5,5 @@ set -eux # Starting from scratch... git clean -xdf -# Run tests using Mill built using SBT -mill all {clientserver,main,scalalib,scalajslib}.test +# Run tests +mill -i all {main,scalalib,scalajslib}.test diff --git a/ci/test-mill-1.sh b/ci/test-mill-1.sh index 079cb519..b0ed7bc2 100755 --- a/ci/test-mill-1.sh +++ b/ci/test-mill-1.sh @@ -5,5 +5,5 @@ set -eux # Starting from scratch... git clean -xdf -# Run tests using Mill built using SBT +# Run tests mill integration.test "mill.integration.local.{JawnTests,BetterFilesTests,UpickleTests}" diff --git a/ci/test-mill-2.sh b/ci/test-mill-2.sh index 3b0da706..ce61bb7c 100755 --- a/ci/test-mill-2.sh +++ b/ci/test-mill-2.sh @@ -5,5 +5,5 @@ set -eux # Starting from scratch... git clean -xdf -# Run tests using Mill built using SBT +# Run tests mill integration.test "mill.integration.local.{AcyclicTests,AmmoniteTests}" diff --git a/ci/test-mill-bootstrap.sh b/ci/test-mill-bootstrap.sh new file mode 100755 index 00000000..8010e700 --- /dev/null +++ b/ci/test-mill-bootstrap.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -eux + +# Starting from scratch... +git clean -xdf + +# First build +mill -i all __.publishLocal release +mv out/release/dest/mill ~/mill-1 + +# Clean up +git clean -xdf + +rm -rf ~/.mill + +# Differentiate first and second builds +echo "Build 2" > info.txt && git add info.txt && git commit -m "Add info.txt" + +# Second build +~/mill-1 -i all __.publishLocal release +mv out/release/dest/mill ~/mill-2 + +# Clean up +git clean -xdf + +rm -rf ~/.mill + +# Use second build to run tests using Mill +~/mill-2 -i all {main,scalalib,scalajslib}.test
\ No newline at end of file diff --git a/ci/test-mill-dev.sh b/ci/test-mill-dev.sh index ae8556fb..459f3eb1 100755 --- a/ci/test-mill-dev.sh +++ b/ci/test-mill-dev.sh @@ -8,8 +8,8 @@ git clean -xdf # Build Mill mill -i dev.assembly -rm -fR ~/.mill +rm -rf ~/.mill -# Second build & run tests using Mill -out/dev/assembly/dest/mill -i all {clientserver,main,scalalib,scalajslib}.test +# Second build & run tests +out/dev/assembly/dest/mill -i all {main,scalalib,scalajslib}.test diff --git a/ci/test-mill-release.sh b/ci/test-mill-release.sh index 19173827..e5ea2b67 100755 --- a/ci/test-mill-release.sh +++ b/ci/test-mill-release.sh @@ -5,12 +5,13 @@ set -eux # Starting from scratch... git clean -xdf +# Build Mill ci/publish-local.sh +# Clean up git clean -xdf -rm -fR ~/.mill - -# Second build & run tests using Mill +rm -rf ~/.mill +# Run tests ~/mill-release -i integration.test "mill.integration.forked.{AcyclicTests,UpickleTests,PlayJsonTests}" diff --git a/clientserver/src/mill/clientserver/ClientServer.java b/client/src/mill/client/ClientServer.java index 15c20f41..468f8ab3 100644 --- a/clientserver/src/mill/clientserver/ClientServer.java +++ b/client/src/mill/client/ClientServer.java @@ -1,4 +1,4 @@ -package mill.clientserver; +package mill.client; import java.io.IOException; diff --git a/clientserver/src/mill/clientserver/InputPumper.java b/client/src/mill/client/InputPumper.java index 9f4bfb82..1789d069 100644 --- a/clientserver/src/mill/clientserver/InputPumper.java +++ b/client/src/mill/client/InputPumper.java @@ -1,4 +1,4 @@ -package mill.clientserver; +package mill.client; import java.io.InputStream; import java.io.OutputStream; diff --git a/client/src/mill/client/Lock.java b/client/src/mill/client/Lock.java new file mode 100644 index 00000000..115529d3 --- /dev/null +++ b/client/src/mill/client/Lock.java @@ -0,0 +1,13 @@ +package mill.client; +public abstract class Lock{ + abstract public Locked lock() throws Exception; + abstract public Locked tryLock() throws Exception; + public void await() throws Exception{ + lock().release(); + } + + /** + * Returns `true` if the lock is *available for taking* + */ + abstract public boolean probe() throws Exception; +}
\ No newline at end of file diff --git a/client/src/mill/client/Locked.java b/client/src/mill/client/Locked.java new file mode 100644 index 00000000..6ba7dab5 --- /dev/null +++ b/client/src/mill/client/Locked.java @@ -0,0 +1,10 @@ +package mill.client; + +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.concurrent.locks.ReentrantLock; + + +public interface Locked{ + public void release() throws Exception; +}
\ No newline at end of file diff --git a/clientserver/src/mill/clientserver/Locks.java b/client/src/mill/client/Locks.java index 78c8dc6e..3b397fce 100644 --- a/clientserver/src/mill/clientserver/Locks.java +++ b/client/src/mill/client/Locks.java @@ -1,30 +1,14 @@ -package mill.clientserver; +package mill.client; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.util.concurrent.locks.ReentrantLock; - -abstract class Lock{ - abstract public Locked lock() throws Exception; - abstract public Locked tryLock() throws Exception; - public void await() throws Exception{ - lock().release(); - } - - /** - * Returns `true` if the lock is *available for taking* - */ - abstract public boolean probe() throws Exception; -} -interface Locked{ - void release() throws Exception; -} -class Locks{ +public class Locks{ public Lock processLock; public Lock serverLock; public Lock clientLock; - static Locks files(String lockBase) throws Exception{ + public static Locks files(String lockBase) throws Exception{ return new Locks(){{ processLock = new FileLock(lockBase + "/pid"); @@ -33,7 +17,7 @@ class Locks{ clientLock = new FileLock(lockBase + "/clientLock"); }}; } - static Locks memory(){ + public static Locks memory(){ return new Locks(){{ this.processLock = new MemoryLock(); this.serverLock = new MemoryLock(); diff --git a/clientserver/src/mill/clientserver/Client.java b/client/src/mill/client/Main.java index ccabc24d..109a9a9d 100644 --- a/clientserver/src/mill/clientserver/Client.java +++ b/client/src/mill/client/Main.java @@ -1,4 +1,4 @@ -package mill.clientserver; +package mill.client; import org.scalasbt.ipcsocket.*; @@ -9,10 +9,10 @@ import java.net.URL; import java.nio.channels.FileChannel; import java.util.*; -public class Client { +public class Main { static void initServer(String lockBase, boolean setJnaNoSys) throws IOException,URISyntaxException{ ArrayList<String> selfJars = new ArrayList<String>(); - ClassLoader current = Client.class.getClassLoader(); + ClassLoader current = Main.class.getClassLoader(); while(current != null){ if (current instanceof java.net.URLClassLoader) { URL[] urls = ((java.net.URLClassLoader) current).getURLs(); @@ -38,8 +38,9 @@ public class Client { } l.add("-cp"); l.add(String.join(File.pathSeparator, selfJars)); - l.add("mill.ServerMain"); + l.add("mill.main.ServerMain"); l.add(lockBase); + new java.lang.ProcessBuilder() .command(l) .redirectOutput(new java.io.File(lockBase + "/logs")) @@ -64,7 +65,7 @@ public class Client { lockFile.close(); channel.close(); } else { - int exitCode = Client.run( + int exitCode = Main.run( lockBase, new Runnable() { @Override diff --git a/clientserver/test/src/mill/clientserver/ClientServerTests.scala b/clientserver/test/src/mill/clientserver/ClientServerTests.scala deleted file mode 100644 index ac2063ef..00000000 --- a/clientserver/test/src/mill/clientserver/ClientServerTests.scala +++ /dev/null @@ -1,209 +0,0 @@ -package mill.clientserver -import java.io._ -import java.nio.file.Path - -import scala.collection.JavaConverters._ -import utest._ -class EchoServer extends ServerMain[Int]{ - def main0(args: Array[String], - stateCache: Option[Int], - mainInteractive: Boolean, - stdin: InputStream, - stdout: PrintStream, - stderr: PrintStream, - env: Map[String, String]) = { - - val reader = new BufferedReader(new InputStreamReader(stdin)) - val str = reader.readLine() - if (args.nonEmpty){ - stdout.println(str + args(0)) - } - env.toSeq.sortBy(_._1).foreach{ - case (key, value) => stdout.println(s"$key=$value") - } - stdout.flush() - if (args.nonEmpty){ - stderr.println(str.toUpperCase + args(0)) - } - stderr.flush() - (true, None) - } -} - -object ClientServerTests extends TestSuite{ - def initStreams() = { - val in = new ByteArrayInputStream("hello\n".getBytes()) - val out = new ByteArrayOutputStream() - val err = new ByteArrayOutputStream() - (in, out, err) - } - def init() = { - val tmpDir = java.nio.file.Files.createTempDirectory("") - val locks = Locks.memory() - - (tmpDir, locks) - } - - def spawnEchoServer(tmpDir : Path, locks: Locks): Unit = { - new Thread(() => new Server( - tmpDir.toString, - new EchoServer(), - () => (), - 1000, - locks - ).run()).start() - } - - def runClientAux(tmpDir : Path, locks: Locks) - (env : Map[String, String], args: Array[String]) = { - val (in, out, err) = initStreams() - Server.lockBlock(locks.clientLock){ - Client.run( - tmpDir.toString, - () => spawnEchoServer(tmpDir, locks), - locks, - in, - out, - err, - args, - env.asJava - ) - Thread.sleep(100) - (new String(out.toByteArray), new String(err.toByteArray)) - } - } - - def tests = Tests{ - 'hello - { - val (tmpDir, locks) = init() - def runClient(s: String) = runClientAux(tmpDir, locks)(Map.empty, Array(s)) - - // Make sure the simple "have the client start a server and - // exchange one message" workflow works from end to end. - - assert( - locks.clientLock.probe(), - locks.serverLock.probe(), - locks.processLock.probe() - ) - - val (out1, err1) = runClient("world") - - assert( - out1 == "helloworld\n", - err1 == "HELLOworld\n" - ) - - // Give a bit of time for the server to release the lock and - // re-acquire it to signal to the client that it's done - Thread.sleep(100) - - assert( - locks.clientLock.probe(), - !locks.serverLock.probe(), - !locks.processLock.probe() - ) - - // A seecond client in sequence connect to the same server - val (out2, err2) = runClient(" WORLD") - - assert( - out2 == "hello WORLD\n", - err2 == "HELLO WORLD\n" - ) - - // Make sure the server times out of not used for a while - Thread.sleep(2000) - assert( - locks.clientLock.probe(), - locks.serverLock.probe(), - locks.processLock.probe() - ) - - // Have a third client spawn/connect-to a new server at the same path - val (out3, err3) = runClient(" World") - assert( - out3 == "hello World\n", - err3 == "HELLO World\n" - ) - } - - 'envVars - { - val (tmpDir, locks) = init() - - def runClient(env : Map[String, String]) = runClientAux(tmpDir, locks)(env, Array()) - - // Make sure the simple "have the client start a server and - // exchange one message" workflow works from end to end. - - assert( - locks.clientLock.probe(), - locks.serverLock.probe(), - locks.processLock.probe() - ) - - def longString(s : String) = Array.fill(1000)(s).mkString - val b1000 = longString("b") - val c1000 = longString("c") - val a1000 = longString("a") - - val env = Map( - "a" -> a1000, - "b" -> b1000, - "c" -> c1000 - ) - - - val (out1, err1) = runClient(env) - val expected = s"a=$a1000\nb=$b1000\nc=$c1000\n" - - assert( - out1 == expected, - err1 == "" - ) - - // Give a bit of time for the server to release the lock and - // re-acquire it to signal to the client that it's done - Thread.sleep(100) - - assert( - locks.clientLock.probe(), - !locks.serverLock.probe(), - !locks.processLock.probe() - ) - - val path = List( - "/Users/foo/Library/Haskell/bin", - "/usr/local/git/bin", - "/sw/bin/", - "/usr/local/bin", - "/usr/local/", - "/usr/local/sbin", - "/usr/local/mysql/bin", - "/usr/local/bin", - "/usr/bin", - "/bin", - "/usr/sbin", - "/sbin", - "/opt/X11/bin", - "/usr/local/MacGPG2/bin", - "/Library/TeX/texbin", - "/usr/local/bin/", - "/Users/foo/bin", - "/Users/foo/go/bin", - "~/.bloop" - ) - - val pathEnvVar = path.mkString(":") - val (out2, err2) = runClient(Map("PATH" -> pathEnvVar)) - - val expected2 = s"PATH=$pathEnvVar\n" - - assert( - out2 == expected2, - err2 == "" - ) - - } - } -} diff --git a/core/src/mill/eval/Evaluator.scala b/core/src/mill/eval/Evaluator.scala index 7b3634ad..a728ec97 100644 --- a/core/src/mill/eval/Evaluator.scala +++ b/core/src/mill/eval/Evaluator.scala @@ -174,10 +174,10 @@ case class Evaluator[T](home: Path, newResults(labelledNamedTask.task) match{ case Result.Failure(_, Some((v, hashCode))) => - handleTaskResult(v, v.hashCode, paths.meta, inputsHash, labelledNamedTask) + handleTaskResult(v, v.##, paths.meta, inputsHash, labelledNamedTask) case Result.Success((v, hashCode)) => - handleTaskResult(v, v.hashCode, paths.meta, inputsHash, labelledNamedTask) + handleTaskResult(v, v.##, paths.meta, inputsHash, labelledNamedTask) case _ => // Wipe out any cached meta.json file that exists, so @@ -305,7 +305,7 @@ case class Evaluator[T](home: Path, newResults(task) = for(v <- res) yield { (v, if (task.isInstanceOf[Worker[_]]) inputsHash - else v.hashCode + else v.## ) } } @@ -359,7 +359,7 @@ object Evaluator{ def classLoaderSig = Thread.currentThread().getContextClassLoader match { case scl: SpecialClassLoader => scl.classpathSignature case ucl: URLClassLoader => - SpecialClassLoader.initialClasspathSignature(ucl).map{ case (k, v) => (Right(k), v)} + SpecialClassLoader.initialClasspathSignature(ucl) case _ => Nil } case class Timing(label: String, diff --git a/docs/pages/1 - Intro to Mill.md b/docs/pages/1 - Intro to Mill.md index d0ce29c7..305721d4 100644 --- a/docs/pages/1 - Intro to Mill.md +++ b/docs/pages/1 - Intro to Mill.md @@ -453,13 +453,12 @@ res2: mill.scalalib.CompilationResult = CompilationResult( ) ``` -You can run `mill` alone to open a build REPL; this is a Scala console with your +You can run `mill -i` to open a build REPL; this is a Scala console with your `build.sc` loaded, which lets you run tasks interactively. The task-running syntax is slightly different from the command-line, but more in-line with how you would depend on tasks from within your build file. -You can use this REPL to run build commands quicker, due to keeping the JVM warm -between runs, or to interactively explore your build to see what is available. +You can use this REPL to interactively explore your build to see what is available. ## Deploying your code @@ -543,6 +542,5 @@ You also need to specify `release` as `true` or `false`, depending on whether you just want to stage your module on `oss.sonatype.org` or you want Mill to complete the release process to Maven Central. -If you are publishing multiple artifacts, you can also use `target/bin/mill -mill.scalalib.PublishModule/publishAll1 as described +If you are publishing multiple artifacts, you can also use `mill mill.scalalib.PublishModule/publishAll` as described [here](http://www.lihaoyi.com/mill/page/common-project-layouts.html#publishing) diff --git a/docs/pages/2 - Configuring Mill.md b/docs/pages/2 - Configuring Mill.md index 4df4d5d4..a229fa72 100644 --- a/docs/pages/2 - Configuring Mill.md +++ b/docs/pages/2 - Configuring Mill.md @@ -414,7 +414,7 @@ object foo extends ScalaModule { Mill's `foo.run` by default will discover which main class to run from your compilation output, but if there is more than one or the main class comes from -some library you cna explicitly specify which one to use. This also adds the +some library you can explicitly specify which one to use. This also adds the main class to your `foo.jar` and `foo.assembly` jars. ## Downloading Non-Maven Jars diff --git a/integration/test/resources/play-json/build.sc b/integration/test/resources/play-json/build.sc index e5222ae1..c60eefba 100644 --- a/integration/test/resources/play-json/build.sc +++ b/integration/test/resources/play-json/build.sc @@ -1,5 +1,5 @@ import mill._, mill.scalalib._, mill.scalalib.publish._, mill.scalajslib._ -import $file.version +import $file.playJsonVersion import $file.reformat import reformat.Scalariform import $file.mima @@ -47,7 +47,7 @@ trait PlayJsonModule extends BaseModule with PublishModule with MiMa { def scalacOptions = Seq("-deprecation", "-feature", "-unchecked", "-encoding", "utf8") def javacOptions = Seq("-encoding", "UTF-8", "-Xlint:-options") - def publishVersion = version.current + def publishVersion = playJsonVersion.current } abstract class PlayJson(val platformSegment: String) extends PlayJsonModule { @@ -329,14 +329,14 @@ object release extends Module { private val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r private val MinorSnapshotVersion = raw"""(\d+)\.(\d+)\.(\d+)-SNAPSHOT""".r - private val releaseVersion = version.current match { + private val releaseVersion = playJsonVersion.current match { case MinorSnapshotVersion(major, minor, patch) => s"${major}.${minor}.${patch.toInt}" case ReleaseVersion(major, minor, patch) => s"${major}.${minor}.${patch.toInt}" } - private val nextVersion = version.current match { + private val nextVersion = playJsonVersion.current match { case v@MinorSnapshotVersion(major, minor, patch) => v case ReleaseVersion(major, minor, patch) => s"${major}.${minor}.${patch.toInt + 1}-SNAPSHOT" diff --git a/integration/test/resources/play-json/mima.sc b/integration/test/resources/play-json/mima.sc index ebab2c72..3902f2c7 100644 --- a/integration/test/resources/play-json/mima.sc +++ b/integration/test/resources/play-json/mima.sc @@ -18,12 +18,7 @@ trait MiMa extends ScalaModule with PublishModule { def previousDeps = T { Agg.from(previousVersions().map { version => - Dep.Java( - pomSettings().organization, - artifactId(), - version, - cross = false - ) + ivy"${pomSettings().organization}:${artifactId()}:${version}" }) } diff --git a/integration/test/resources/play-json/version.sc b/integration/test/resources/play-json/playJsonVersion.sc index e9dbb12f..e9dbb12f 100644 --- a/integration/test/resources/play-json/version.sc +++ b/integration/test/resources/play-json/playJsonVersion.sc diff --git a/main/src/mill/Main.scala b/main/src/mill/Main.scala index c9ec00ca..a349321e 100644 --- a/main/src/mill/Main.scala +++ b/main/src/mill/Main.scala @@ -8,27 +8,11 @@ import ammonite.main.Cli._ import ammonite.ops._ import ammonite.util.Util import io.github.retronym.java9rtexport.Export +import mill.client.ClientServer import mill.eval.Evaluator import mill.util.DummyInputStream -object ServerMain extends mill.clientserver.ServerMain[Evaluator.State]{ - def main0(args: Array[String], - stateCache: Option[Evaluator.State], - mainInteractive: Boolean, - stdin: InputStream, - stdout: PrintStream, - stderr: PrintStream, - env : Map[String, String]) = Main.main0( - args, - stateCache, - mainInteractive, - DummyInputStream, - stdout, - stderr, - env - ) -} object Main { def main(args: Array[String]): Unit = { @@ -126,7 +110,7 @@ object Main { env ) - if (mill.clientserver.ClientServer.isJava9OrAbove) { + if (ClientServer.isJava9OrAbove) { val rt = cliConfig.home / Export.rtJarName if (!exists(rt)) { runner.printInfo(s"Preparing Java ${System.getProperty("java.version")} runtime; this may take a minute or two ...") diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala index 7c84f74a..32281407 100644 --- a/main/src/mill/main/MainModule.scala +++ b/main/src/mill/main/MainModule.scala @@ -30,7 +30,11 @@ trait MainModule extends mill.Module{ implicit def millDiscover: mill.define.Discover[_] implicit def millScoptTasksReads[T] = new mill.main.Tasks.Scopt[T]() implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]() - + def version() = mill.T.command { + val res = System.getProperty("MILL_VERSION") + println(res) + res + } /** * Resolves a mill query string and prints out the tasks it resolves to. */ diff --git a/clientserver/src/mill/clientserver/Server.scala b/main/src/mill/main/Server.scala index 4deac55f..14aade4c 100644 --- a/clientserver/src/mill/clientserver/Server.scala +++ b/main/src/mill/main/Server.scala @@ -1,29 +1,51 @@ -package mill.clientserver +package mill.main import java.io._ import java.net.Socket +import mill.Main import scala.collection.JavaConverters._ import org.scalasbt.ipcsocket._ +import mill.client._ +import mill.eval.Evaluator +import mill.util.DummyInputStream trait ServerMain[T]{ + var stateCache = Option.empty[T] + def main0(args: Array[String], + stateCache: Option[T], + mainInteractive: Boolean, + stdin: InputStream, + stdout: PrintStream, + stderr: PrintStream, + env : Map[String, String]): (Boolean, Option[T]) +} + +object ServerMain extends mill.main.ServerMain[Evaluator.State]{ def main(args0: Array[String]): Unit = { new Server( args0(0), this, () => System.exit(0), 300000, - Locks.files(args0(0)) + mill.client.Locks.files(args0(0)) ).run() } - var stateCache = Option.empty[T] def main0(args: Array[String], - stateCache: Option[T], + stateCache: Option[Evaluator.State], mainInteractive: Boolean, stdin: InputStream, stdout: PrintStream, stderr: PrintStream, - env : Map[String, String]): (Boolean, Option[T]) + env : Map[String, String]) = Main.main0( + args, + stateCache, + mainInteractive, + DummyInputStream, + stdout, + stderr, + env + ) } diff --git a/main/src/mill/modules/Jvm.scala b/main/src/mill/modules/Jvm.scala index 92469988..e7fd6a79 100644 --- a/main/src/mill/modules/Jvm.scala +++ b/main/src/mill/modules/Jvm.scala @@ -1,6 +1,6 @@ package mill.modules -import java.io.{ByteArrayInputStream, FileOutputStream, File} +import java.io.{ByteArrayInputStream, File, FileOutputStream} import java.lang.reflect.Modifier import java.net.{URI, URLClassLoader} import java.nio.file.{FileSystems, Files, OpenOption, StandardOpenOption} @@ -9,7 +9,7 @@ import java.util.jar.{JarEntry, JarFile, JarOutputStream} import ammonite.ops._ import geny.Generator -import mill.clientserver.InputPumper +import mill.client.InputPumper import mill.eval.PathRef import mill.util.{Ctx, IO} import mill.util.Loose.Agg @@ -277,7 +277,7 @@ object Jvm { // Prepend shell script and make it executable if (prependShellScript.isEmpty) mv(tmp, output) else{ - val lineSep = if (isWin) "\r\n" else "\n" + val lineSep = if (!prependShellScript.endsWith("\n")) "\n\r\n" else "" val outputStream = newOutputStream(output.toNIO) IO.stream(new ByteArrayInputStream((prependShellScript + lineSep).getBytes()), outputStream) IO.stream(read.getInputStream(tmp), outputStream) @@ -319,30 +319,50 @@ object Jvm { } - def launcherShellScript(isWin: Boolean, - mainClass: String, - classPath: Agg[String], - jvmArgs: Seq[String]) = { - val cp = classPath.mkString(File.pathSeparator) - if (isWin) - s"""@echo off - | - |java ${jvmArgs.mkString(" ")} %JAVA_OPTS% -cp "$cp" $mainClass %* - """.stripMargin.split('\n').mkString("\r\n") - else - s"""#!/usr/bin/env sh - | - |exec java ${jvmArgs.mkString(" ")} $$JAVA_OPTS -cp "$cp" $mainClass "$$@" - """.stripMargin + def universalScript(shellCommands: String, + cmdCommands: String, + shebang: Boolean = false): String = { + Seq( + if (shebang) "#!/usr/bin/env sh" else "", + "@ 2>/dev/null # 2>nul & echo off & goto BOF\r", + ":", + shellCommands.replaceAll("\r\n|\n", "\n"), + "exit", + Seq( + "", + ":BOF", + "@echo off", + cmdCommands.replaceAll("\r\n|\n", "\r\n"), + "exit /B %errorlevel%", + "" + ).mkString("\r\n") + ).filterNot(_.isEmpty).mkString("\n") + } + + def launcherUniversalScript(mainClass: String, + shellClassPath: Agg[String], + cmdClassPath: Agg[String], + jvmArgs: Seq[String]) = { + universalScript( + shellCommands = + s"""exec java ${jvmArgs.mkString(" ")} $$JAVA_OPTS -cp "${shellClassPath.mkString(":")}" $mainClass "$$@"""", + cmdCommands = + s"""java ${jvmArgs.mkString(" ")} %JAVA_OPTS% -cp "${cmdClassPath.mkString(";")}" $mainClass %*""", + ) } def createLauncher(mainClass: String, classPath: Agg[Path], jvmArgs: Seq[String]) (implicit ctx: Ctx.Dest)= { val isWin = scala.util.Properties.isWin - val outputPath = ctx.dest / (if (isWin) "run.bat" else "run") - - write(outputPath, launcherShellScript(isWin, mainClass, classPath.map(_.toString), jvmArgs)) + val isBatch = isWin && + !(org.jline.utils.OSUtils.IS_CYGWIN + || org.jline.utils.OSUtils.IS_MINGW + || "MSYS" == System.getProperty("MSYSTEM")) + val outputPath = ctx.dest / (if (isBatch) "run.bat" else "run") + val classPathStrs = classPath.map(_.toString) + + write(outputPath, launcherUniversalScript(mainClass, classPathStrs, classPathStrs, jvmArgs)) if (!isWin) { val perms = Files.getPosixFilePermissions(outputPath.toNIO) diff --git a/main/test/src/mill/eval/EvaluationTests.scala b/main/test/src/mill/eval/EvaluationTests.scala index 66147963..9c215086 100644 --- a/main/test/src/mill/eval/EvaluationTests.scala +++ b/main/test/src/mill/eval/EvaluationTests.scala @@ -247,6 +247,32 @@ object EvaluationTests extends TestSuite{ !overriden.contains("object1") ) } + 'nullTasks - { + import nullTasks._ + val checker = new Checker(nullTasks) + checker(nullTarget1, null, Agg(nullTarget1), extraEvaled = -1) + checker(nullTarget1, null, Agg(), extraEvaled = -1) + checker(nullTarget2, null, Agg(nullTarget2), extraEvaled = -1) + checker(nullTarget2, null, Agg(), extraEvaled = -1) + checker(nullTarget3, null, Agg(nullTarget3), extraEvaled = -1) + checker(nullTarget3, null, Agg(), extraEvaled = -1) + checker(nullTarget4, null, Agg(nullTarget4), extraEvaled = -1) + checker(nullTarget4, null, Agg(), extraEvaled = -1) + + val nc1 = nullCommand1() + val nc2 = nullCommand2() + val nc3 = nullCommand3() + val nc4 = nullCommand4() + + checker(nc1, null, Agg(nc1), extraEvaled = -1, secondRunNoOp = false) + checker(nc1, null, Agg(nc1), extraEvaled = -1, secondRunNoOp = false) + checker(nc2, null, Agg(nc2), extraEvaled = -1, secondRunNoOp = false) + checker(nc2, null, Agg(nc2), extraEvaled = -1, secondRunNoOp = false) + checker(nc3, null, Agg(nc3), extraEvaled = -1, secondRunNoOp = false) + checker(nc3, null, Agg(nc3), extraEvaled = -1, secondRunNoOp = false) + checker(nc4, null, Agg(nc4), extraEvaled = -1, secondRunNoOp = false) + checker(nc4, null, Agg(nc4), extraEvaled = -1, secondRunNoOp = false) + } 'tasksAreUncached - { // Make sure the tasks `left` and `middle` re-compute every time, while diff --git a/main/test/src/mill/eval/JavaCompileJarTests.scala b/main/test/src/mill/eval/JavaCompileJarTests.scala index 1ac00c79..2e73b339 100644 --- a/main/test/src/mill/eval/JavaCompileJarTests.scala +++ b/main/test/src/mill/eval/JavaCompileJarTests.scala @@ -11,10 +11,10 @@ import mill.util.Strict.Agg import utest._ import mill._ object JavaCompileJarTests extends TestSuite{ - def compileAll(sources: Seq[PathRef])(implicit ctx: Dest) = { + def compileAll(sources: mill.util.Loose.Agg[PathRef])(implicit ctx: Dest) = { mkdir(ctx.dest) import ammonite.ops._ - %("javac", sources.map(_.path.toString()), "-d", ctx.dest)(wd = ctx.dest) + %("javac", sources.map(_.path.toString()).toSeq, "-d", ctx.dest)(wd = ctx.dest) PathRef(ctx.dest) } diff --git a/main/test/src/mill/main/ClientServerTests.scala b/main/test/src/mill/main/ClientServerTests.scala new file mode 100644 index 00000000..60c9c9e6 --- /dev/null +++ b/main/test/src/mill/main/ClientServerTests.scala @@ -0,0 +1,214 @@ +package mill.main +import java.io._ +import java.nio.file.Path + +import mill.client.{ClientServer, Locks} + +import scala.collection.JavaConverters._ +import utest._ +class EchoServer extends ServerMain[Int]{ + def main0(args: Array[String], + stateCache: Option[Int], + mainInteractive: Boolean, + stdin: InputStream, + stdout: PrintStream, + stderr: PrintStream, + env: Map[String, String]) = { + + val reader = new BufferedReader(new InputStreamReader(stdin)) + val str = reader.readLine() + if (args.nonEmpty){ + stdout.println(str + args(0)) + } + env.toSeq.sortBy(_._1).foreach{ + case (key, value) => stdout.println(s"$key=$value") + } + stdout.flush() + if (args.nonEmpty){ + stderr.println(str.toUpperCase + args(0)) + } + stderr.flush() + (true, None) + } +} + +object ClientServerTests extends TestSuite{ + def initStreams() = { + val in = new ByteArrayInputStream("hello\n".getBytes()) + val out = new ByteArrayOutputStream() + val err = new ByteArrayOutputStream() + (in, out, err) + } + def init() = { + val tmpDir = java.nio.file.Files.createTempDirectory("") + val locks = Locks.memory() + + (tmpDir, locks) + } + + def spawnEchoServer(tmpDir : Path, locks: Locks): Unit = { + new Thread(() => new Server( + tmpDir.toString, + new EchoServer(), + () => (), + 1000, + locks + ).run()).start() + } + + def runClientAux(tmpDir : Path, locks: Locks) + (env : Map[String, String], args: Array[String]) = { + val (in, out, err) = initStreams() + Server.lockBlock(locks.clientLock){ + mill.client.Main.run( + tmpDir.toString, + () => spawnEchoServer(tmpDir, locks), + locks, + in, + out, + err, + args, + env.asJava + ) + Thread.sleep(100) + (new String(out.toByteArray), new String(err.toByteArray)) + } + } + + def tests = Tests{ + 'hello - { + if (!ClientServer.isWindows){ + val (tmpDir, locks) = init() + def runClient(s: String) = runClientAux(tmpDir, locks)(Map.empty, Array(s)) + + // Make sure the simple "have the client start a server and + // exchange one message" workflow works from end to end. + + assert( + locks.clientLock.probe(), + locks.serverLock.probe(), + locks.processLock.probe() + ) + + val (out1, err1) = runClient("world") + + assert( + out1 == "helloworld\n", + err1 == "HELLOworld\n" + ) + + // Give a bit of time for the server to release the lock and + // re-acquire it to signal to the client that it's done + Thread.sleep(100) + + assert( + locks.clientLock.probe(), + !locks.serverLock.probe(), + !locks.processLock.probe() + ) + + // A seecond client in sequence connect to the same server + val (out2, err2) = runClient(" WORLD") + + assert( + out2 == "hello WORLD\n", + err2 == "HELLO WORLD\n" + ) + + // Make sure the server times out of not used for a while + Thread.sleep(2000) + assert( + locks.clientLock.probe(), + locks.serverLock.probe(), + locks.processLock.probe() + ) + + // Have a third client spawn/connect-to a new server at the same path + val (out3, err3) = runClient(" World") + assert( + out3 == "hello World\n", + err3 == "HELLO World\n" + ) + } + + 'envVars - { + if (!ClientServer.isWindows){ + val (tmpDir, locks) = init() + + def runClient(env : Map[String, String]) = runClientAux(tmpDir, locks)(env, Array()) + + // Make sure the simple "have the client start a server and + // exchange one message" workflow works from end to end. + + assert( + locks.clientLock.probe(), + locks.serverLock.probe(), + locks.processLock.probe() + ) + + def longString(s : String) = Array.fill(1000)(s).mkString + val b1000 = longString("b") + val c1000 = longString("c") + val a1000 = longString("a") + + val env = Map( + "a" -> a1000, + "b" -> b1000, + "c" -> c1000 + ) + + + val (out1, err1) = runClient(env) + val expected = s"a=$a1000\nb=$b1000\nc=$c1000\n" + + assert( + out1 == expected, + err1 == "" + ) + + // Give a bit of time for the server to release the lock and + // re-acquire it to signal to the client that it's done + Thread.sleep(100) + + assert( + locks.clientLock.probe(), + !locks.serverLock.probe(), + !locks.processLock.probe() + ) + + val path = List( + "/Users/foo/Library/Haskell/bin", + "/usr/local/git/bin", + "/sw/bin/", + "/usr/local/bin", + "/usr/local/", + "/usr/local/sbin", + "/usr/local/mysql/bin", + "/usr/local/bin", + "/usr/bin", + "/bin", + "/usr/sbin", + "/sbin", + "/opt/X11/bin", + "/usr/local/MacGPG2/bin", + "/Library/TeX/texbin", + "/usr/local/bin/", + "/Users/foo/bin", + "/Users/foo/go/bin", + "~/.bloop" + ) + + val pathEnvVar = path.mkString(":") + val (out2, err2) = runClient(Map("PATH" -> pathEnvVar)) + + val expected2 = s"PATH=$pathEnvVar\n" + + assert( + out2 == expected2, + err2 == "" + ) + } + } + } + } +} diff --git a/main/test/src/mill/util/ScriptTestSuite.scala b/main/test/src/mill/util/ScriptTestSuite.scala index f88007c5..bbca5d68 100644 --- a/main/test/src/mill/util/ScriptTestSuite.scala +++ b/main/test/src/mill/util/ScriptTestSuite.scala @@ -10,8 +10,8 @@ abstract class ScriptTestSuite(fork: Boolean) extends TestSuite{ def scriptSourcePath: Path val workspacePath = pwd / 'target / 'workspace / workspaceSlug - val stdOutErr = new PrintStream(new ByteArrayOutputStream()) -// val stdOutErr = new PrintStream(System.out) +// val stdOutErr = new PrintStream(new ByteArrayOutputStream()) + val stdOutErr = new PrintStream(System.out) val stdIn = new ByteArrayInputStream(Array()) lazy val runner = new mill.main.MainRunner( ammonite.main.Cli.Config(wd = workspacePath), @@ -21,7 +21,7 @@ abstract class ScriptTestSuite(fork: Boolean) extends TestSuite{ if (!fork) runner.runScript(workspacePath / "build.sc", s.toList) else{ try { - %%(home / "mill-release", "-i", s)(workspacePath) + %(home / "mill-release", "-i", s)(workspacePath) true }catch{case e: Throwable => false} } diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala index 11f72d02..83e03576 100644 --- a/main/test/src/mill/util/TestGraphs.scala +++ b/main/test/src/mill/util/TestGraphs.scala @@ -196,6 +196,23 @@ object TestGraphs{ override lazy val millDiscover: Discover[this.type] = Discover[this.type] } + object nullTasks extends TestUtil.BaseModule{ + val nullString: String = null + def nullTask1 = T.task{ nullString } + def nullTask2 = T.task{ nullTask1() } + + def nullTarget1 = T{ nullString } + def nullTarget2 = T{ nullTarget1() } + def nullTarget3 = T{ nullTask1() } + def nullTarget4 = T{ nullTask2() } + + def nullCommand1() = T.command{ nullString } + def nullCommand2() = T.command{ nullTarget1() } + def nullCommand3() = T.command{ nullTask1() } + def nullCommand4() = T.command{ nullTask2() } + + override lazy val millDiscover: Discover[this.type] = Discover[this.type] + } object singleCross extends TestUtil.BaseModule { object cross extends mill.Cross[Cross]("210", "211", "212") @@ -328,24 +328,41 @@ rm -rf out/ ## Changelog +### Master + +- Universal (combined batch/sh) script generation for launcher, assembly, and release + + For some shell (e.g., `ksh` or `fish`), a shebang line should be added, e.g., using GNU sed: + + ```bash + sed -i '1s;^;#!/usr/bin/env sh\n;' <mill-path> + ``` + + Or download directly with shebang added as follows: + + ```bash + sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L <mill-url>) > /usr/local/bin/mill && chmod +x /usr/local/bin/mill' + ``` + + On Windows, save `<mill-url>` as `mill.bat` + +- Windows client/server improvements + +- Windows repl support (note: MSYS2 subsystem/shell will be supported when jline3 v3.6.3 is released) + +- Fixed Java 9 support + ### 0.1.7 +- Windows batch (.bat) generation for launcher, assembly, and release + - Support for non-interactive (client/server) mode on Windows. - Mill requires an `sh` environment to run on Windows; - it is recommended to use [MSYS2](https://www.msys2.org) on Windows. - - [Cygwin](https://www.cygwin.com) can also be used, - but your mileage may vary when running mill on non-interactive (client/server) mode - (it failed intermittently in mill's AppVeyor tests). On Cygwin, run the following after downloading mill: ```bash sed -i '0,/-cp "\$0"/{s/-cp "\$0"/-cp `cygpath -w "\$0"`/}; 0,/-cp "\$0"/{s/-cp "\$0"/-cp `cygpath -w "\$0"`/}' <mill-path> ``` - - Mill also runs on [Git-Bash](https://gitforwindows.org) and [WSL](https://docs.microsoft.com/windows/wsl) - but only on interactive mode. - More fixes for Java 9 diff --git a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala index bddf894e..dbf693ff 100644 --- a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala +++ b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala @@ -36,7 +36,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer => MavenRepository("https://repo1.maven.org/maven2"), MavenRepository("https://oss.sonatype.org/content/repositories/releases") ), - "2.12.4", + Lib.depToDependency(_, "2.12.4", ""), Seq( ivy"com.lihaoyi::mill-scalajslib-jsbridges-${scalaJSBridgeVersion()}:${sys.props("MILL_VERSION")}" ) @@ -54,7 +54,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer => } resolveDependencies( repositories, - "2.12.4", + Lib.depToDependency(_, "2.12.4", ""), commonDeps :+ envDep ) } @@ -150,9 +150,7 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer => else scalaJSBinaryVersion() } - override def artifactSuffix: T[String] = T { - s"_sjs${artifactScalaJSVersion()}_${artifactScalaVersion()}" - } + override def artifactSuffix: T[String] = s"${platformSuffix()}_${artifactScalaVersion()}" override def platformSuffix = s"_sjs${artifactScalaJSVersion()}" diff --git a/scalalib/src/mill/scalalib/Dep.scala b/scalalib/src/mill/scalalib/Dep.scala index f20480b7..9719bd2d 100644 --- a/scalalib/src/mill/scalalib/Dep.scala +++ b/scalalib/src/mill/scalalib/Dep.scala @@ -3,23 +3,29 @@ import mill.util.JsonFormatters._ import upickle.default.{macroRW, ReadWriter => RW} sealed trait Dep { def configure(attributes: coursier.Attributes): Dep - def exclude(exclusions: (String, String)*): Dep = - this match { - case dep : Dep.Java => dep.copy(dep = dep.dep.copy(exclusions = dep.dep.exclusions ++ exclusions)) - case dep : Dep.Scala => dep.copy(dep = dep.dep.copy(exclusions = dep.dep.exclusions ++ exclusions)) - case dep : Dep.Point => dep.copy(dep = dep.dep.copy(exclusions = dep.dep.exclusions ++ exclusions)) - } + def force: Boolean + def forceVersion(): Dep = this match { + case dep : Dep.Java => dep.copy(force = true) + case dep : Dep.Scala => dep.copy(force = true) + case dep : Dep.Point => dep.copy(force = true) + } + def exclude(exclusions: (String, String)*): Dep = this match { + case dep : Dep.Java => dep.copy(dep = dep.dep.copy(exclusions = dep.dep.exclusions ++ exclusions)) + case dep : Dep.Scala => dep.copy(dep = dep.dep.copy(exclusions = dep.dep.exclusions ++ exclusions)) + case dep : Dep.Point => dep.copy(dep = dep.dep.copy(exclusions = dep.dep.exclusions ++ exclusions)) + } def excludeOrg(organizations: String*): Dep = exclude(organizations.map(_ -> "*"): _*) def excludeName(names: String*): Dep = exclude(names.map("*" -> _): _*) - def withConfiguration(configuration: String): Dep = - this match { - case dep : Dep.Java => dep.copy(dep = dep.dep.copy(configuration = configuration)) - case dep : Dep.Scala => dep.copy(dep = dep.dep.copy(configuration = configuration)) - case dep : Dep.Point => dep.copy(dep = dep.dep.copy(configuration = configuration)) - } + def withConfiguration(configuration: String): Dep = this match { + case dep : Dep.Java => dep.copy(dep = dep.dep.copy(configuration = configuration)) + case dep : Dep.Scala => dep.copy(dep = dep.dep.copy(configuration = configuration)) + case dep : Dep.Point => dep.copy(dep = dep.dep.copy(configuration = configuration)) + } } object Dep{ + val DefaultConfiguration = "default(compile)" + implicit def parse(signature: String) = { val parts = signature.split(';') val module = parts.head @@ -31,45 +37,45 @@ object Dep{ } } (module.split(':') match { - case Array(a, b, c) => Dep.Java(a, b, c, cross = false) - case Array(a, b, "", c) => Dep.Java(a, b, c, cross = true) - case Array(a, "", b, c) => Dep.Scala(a, b, c, cross = false) - case Array(a, "", b, "", c) => Dep.Scala(a, b, c, cross = true) - case Array(a, "", "", b, c) => Dep.Point(a, b, c, cross = false) - case Array(a, "", "", b, "", c) => Dep.Point(a, b, c, cross = true) + case Array(a, b, c) => Dep.Java(a, b, c, cross = false, force = false) + case Array(a, b, "", c) => Dep.Java(a, b, c, cross = true, force = false) + case Array(a, "", b, c) => Dep.Scala(a, b, c, cross = false, force = false) + case Array(a, "", b, "", c) => Dep.Scala(a, b, c, cross = true, force = false) + case Array(a, "", "", b, c) => Dep.Point(a, b, c, cross = false, force = false) + case Array(a, "", "", b, "", c) => Dep.Point(a, b, c, cross = true, force = false) case _ => throw new Exception(s"Unable to parse signature: [$signature]") }).configure(attributes = attributes) } def apply(org: String, name: String, version: String, cross: Boolean): Dep = { - this(coursier.Dependency(coursier.Module(org, name), version), cross) + this(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross) } - case class Java(dep: coursier.Dependency, cross: Boolean) extends Dep { + case class Java(dep: coursier.Dependency, cross: Boolean, force: Boolean) extends Dep { def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes)) } object Java{ implicit def rw: RW[Java] = macroRW - def apply(org: String, name: String, version: String, cross: Boolean): Dep = { - Java(coursier.Dependency(coursier.Module(org, name), version), cross) + def apply(org: String, name: String, version: String, cross: Boolean, force: Boolean): Dep = { + Java(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force) } } - implicit def default(dep: coursier.Dependency): Dep = new Java(dep, false) - def apply(dep: coursier.Dependency, cross: Boolean) = Scala(dep, cross) - case class Scala(dep: coursier.Dependency, cross: Boolean) extends Dep { + implicit def default(dep: coursier.Dependency): Dep = new Java(dep, false, false) + def apply(dep: coursier.Dependency, cross: Boolean) = Scala(dep, cross, false) + case class Scala(dep: coursier.Dependency, cross: Boolean, force: Boolean) extends Dep { def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes)) } object Scala{ implicit def rw: RW[Scala] = macroRW - def apply(org: String, name: String, version: String, cross: Boolean): Dep = { - Scala(coursier.Dependency(coursier.Module(org, name), version), cross) + def apply(org: String, name: String, version: String, cross: Boolean, force: Boolean): Dep = { + Scala(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force) } } - case class Point(dep: coursier.Dependency, cross: Boolean) extends Dep { + case class Point(dep: coursier.Dependency, cross: Boolean, force: Boolean) extends Dep { def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes)) } object Point{ implicit def rw: RW[Point] = macroRW - def apply(org: String, name: String, version: String, cross: Boolean): Dep = { - Point(coursier.Dependency(coursier.Module(org, name), version), cross) + def apply(org: String, name: String, version: String, cross: Boolean, force: Boolean): Dep = { + Point(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force) } } implicit def rw = RW.merge[Dep]( diff --git a/scalalib/src/mill/scalalib/GenIdea.scala b/scalalib/src/mill/scalalib/GenIdeaImpl.scala index b118f29b..3e90f269 100644 --- a/scalalib/src/mill/scalalib/GenIdea.scala +++ b/scalalib/src/mill/scalalib/GenIdeaImpl.scala @@ -12,10 +12,10 @@ import mill.util.Strict.Agg import scala.util.Try -object GenIdeaModule extends ExternalModule { +object GenIdea extends ExternalModule { def idea(ev: Evaluator[Any]) = T.command{ - mill.scalalib.GenIdea( + mill.scalalib.GenIdeaImpl( implicitly, ev.rootModule, ev.rootModule.millDiscover @@ -25,7 +25,7 @@ object GenIdeaModule extends ExternalModule { implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]() lazy val millDiscover = Discover[this.type] } -object GenIdea { +object GenIdeaImpl { def apply(ctx: Log with Home, rootModule: BaseModule, @@ -62,27 +62,31 @@ object GenIdea { fetchMillModules: Boolean = true): Seq[(RelPath, scala.xml.Node)] = { val modules = rootModule.millInternal.segmentsToModules.values - .collect{ case x: scalalib.ScalaModule => (x.millModuleSegments, x)} + .collect{ case x: scalalib.JavaModule => (x.millModuleSegments, x)} .toSeq val buildLibraryPaths = if (!fetchMillModules) Nil else sys.props.get("MILL_BUILD_LIBRARIES") match { - case Some(found) => Agg.from(found.split(',').map(Path(_)).distinct) + case Some(found) => found.split(',').map(Path(_)).distinct.toList case None => - val repos = modules.foldLeft(Set.empty[Repository]) { _ ++ _._2.scalaWorker.repositories } + val repos = modules.foldLeft(Set.empty[Repository]) { _ ++ _._2.repositories } val artifactNames = Seq("moduledefs", "core", "scalalib", "scalajslib") val Result.Success(res) = scalalib.Lib.resolveDependencies( repos.toList, - "2.12.4", + Lib.depToDependency(_, "2.12.4", ""), for(name <- artifactNames) yield ivy"com.lihaoyi::mill-$name:${sys.props("MILL_VERSION")}" ) - res.items.toSeq.map(_.path) + res.items.toList.map(_.path) } val resolved = for((path, mod) <- modules) yield { - val allIvyDeps = T.task{mod.transitiveIvyDeps() ++ mod.scalaLibraryIvyDeps() ++ mod.compileIvyDeps()} + val scalaLibraryIvyDeps = mod match{ + case x: ScalaModule => x.scalaLibraryIvyDeps + case _ => T.task{Nil} + } + val allIvyDeps = T.task{mod.transitiveIvyDeps() ++ scalaLibraryIvyDeps() ++ mod.compileIvyDeps()} val externalDependencies = T.task{ mod.resolveDeps(allIvyDeps)() ++ Task.traverse(mod.transitiveModuleDeps)(_.unmanagedClasspath)().flatten @@ -92,8 +96,10 @@ object GenIdea { mod.resolveDeps(allIvyDeps, sources = true)() } - val scalacPluginsIvyDeps = T.task{mod.scalacPluginIvyDeps()} - val scalacOptions = T.task{mod.scalacOptions()} + val (scalacPluginsIvyDeps, scalacOptions) = mod match{ + case mod: ScalaModule => T.task{mod.scalacPluginIvyDeps()} -> T.task{mod.scalacOptions()} + case _ => T.task(Loose.Agg[Dep]()) -> T.task(Seq()) + } val scalacPluginDependencies = T.task{ mod.resolveDeps(scalacPluginsIvyDeps)() } @@ -103,18 +109,27 @@ object GenIdea { val resolvedSp: Loose.Agg[PathRef] = evalOrElse(evaluator, scalacPluginDependencies, Loose.Agg.empty) val scalacOpts: Seq[String] = evalOrElse(evaluator, scalacOptions, Seq()) - (path, resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path), mod, - resolvedSp.map(_.path).filter(_.ext == "jar"), scalacOpts) + ( + path, + resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path), + mod, + resolvedSp.map(_.path).filter(_.ext == "jar"), + scalacOpts + ) } val moduleLabels = modules.map(_.swap).toMap val allResolved = resolved.flatMap(_._2) ++ buildLibraryPaths - val minResolvedLength = allResolved.map(_.segments.length).min - val commonPrefix = allResolved.map(_.segments.take(minResolvedLength)) - .transpose - .takeWhile(_.distinct.length == 1) - .length + val commonPrefix = + if (allResolved.isEmpty) 0 + else { + val minResolvedLength = allResolved.map(_.segments.length).min + allResolved.map(_.segments.take(minResolvedLength)) + .transpose + .takeWhile(_.distinct.length == 1) + .length + } // only resort to full long path names if the jar name is a duplicate val pathShortLibNameDuplicate = allResolved @@ -134,7 +149,7 @@ object GenIdea { .toMap val compilerSettings = resolved - .foldLeft(Map[(Loose.Agg[Path], Seq[String]), Vector[ScalaModule]]()) { + .foldLeft(Map[(Loose.Agg[Path], Seq[String]), Vector[JavaModule]]()) { (r, q) => val key = (q._4, q._5) r + (key -> (r.getOrElse(key, Vector()) :+ q._3)) @@ -190,7 +205,10 @@ object GenIdea { evaluator.outPath, mod.compile.ctx.segments ) - val Seq(scalaVersion: String) = evaluator.evaluate(Agg(mod.scalaVersion)).values + val scalaVersionOpt = mod match { + case x: ScalaModule => Some(evaluator.evaluate(Agg(x.scalaVersion)).values.head.asInstanceOf[String]) + case _ => None + } val generatedSourceOutPath = Evaluator.resolveDestPaths( evaluator.outPath, mod.generatedSources.ctx.segments @@ -198,7 +216,7 @@ object GenIdea { val elem = moduleXmlTemplate( mod.millModuleBasePath.value, - scalaVersion, + scalaVersionOpt, Strict.Agg.from(resourcesPathRefs.map(_.path)), Strict.Agg.from(normalSourcePaths), Strict.Agg.from(generatedSourcePaths), @@ -293,7 +311,7 @@ object GenIdea { </component> } def moduleXmlTemplate(basePath: Path, - scalaVersion: String, + scalaVersionOpt: Option[String], resourcePaths: Strict.Agg[Path], normalSourcePaths: Strict.Agg[Path], generatedSourcePaths: Strict.Agg[Path], @@ -326,7 +344,10 @@ object GenIdea { </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="library" name={s"scala-sdk-$scalaVersion"} level="application" /> + { + for(scalaVersion <- scalaVersionOpt.toSeq) + yield <orderEntry type="library" name={s"scala-sdk-$scalaVersion"} level="application" /> + } { for(name <- libNames.toSeq.sorted) @@ -340,7 +361,7 @@ object GenIdea { </component> </module> } - def scalaCompilerTemplate(settings: Map[(Loose.Agg[Path], Seq[String]), Seq[ScalaModule]]) = { + def scalaCompilerTemplate(settings: Map[(Loose.Agg[Path], Seq[String]), Seq[JavaModule]]) = { <project version="4"> <component name="ScalaCompilerConfiguration"> diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala new file mode 100644 index 00000000..b8ae0fd4 --- /dev/null +++ b/scalalib/src/mill/scalalib/JavaModule.scala @@ -0,0 +1,284 @@ +package mill +package scalalib + + +import ammonite.ops._ +import coursier.Repository +import mill.define.Task +import mill.define.TaskModule +import mill.eval.{PathRef, Result} +import mill.modules.Jvm +import mill.modules.Jvm.{createAssembly, createJar} +import Lib._ +import mill.scalalib.publish.{Artifact, Scope} +import mill.util.Loose.Agg + +/** + * Core configuration required to compile a single Scala compilation target + */ +trait JavaModule extends mill.Module with TaskModule { outer => + + def defaultCommandName() = "run" + + def resolvePublishDependency: Task[Dep => publish.Dependency] = T.task{ + Artifact.fromDepJava(_: Dep) + } + def resolveCoursierDependency: Task[Dep => coursier.Dependency] = T.task{ + Lib.depToDependencyJava(_: Dep) + } + + def mainClass: T[Option[String]] = None + + def finalMainClassOpt: T[Either[String, String]] = T{ + mainClass() match{ + case Some(m) => Right(m) + case None => Left("No main class specified or found") + } + } + + def finalMainClass: T[String] = T{ + finalMainClassOpt() match { + case Right(main) => Result.Success(main) + case Left(msg) => Result.Failure(msg) + } + } + + def ivyDeps = T{ Agg.empty[Dep] } + def compileIvyDeps = T{ Agg.empty[Dep] } + def runIvyDeps = T{ Agg.empty[Dep] } + + def javacOptions = T{ Seq.empty[String] } + + def moduleDeps = Seq.empty[JavaModule] + + + def transitiveModuleDeps: Seq[JavaModule] = { + Seq(this) ++ moduleDeps.flatMap(_.transitiveModuleDeps).distinct + } + def unmanagedClasspath = T{ Agg.empty[PathRef] } + + + def transitiveIvyDeps: T[Agg[Dep]] = T{ + ivyDeps() ++ Task.traverse(moduleDeps)(_.transitiveIvyDeps)().flatten + } + + def upstreamCompileOutput = T{ + Task.traverse(moduleDeps)(_.compile) + } + + def transitiveLocalClasspath: T[Agg[PathRef]] = T{ + Task.traverse(moduleDeps)(m => + T.task{m.localClasspath() ++ m.transitiveLocalClasspath()} + )().flatten + } + + def mapDependencies(d: coursier.Dependency) = d + + def resolveDeps(deps: Task[Agg[Dep]], sources: Boolean = false) = T.task{ + resolveDependencies( + repositories, + resolveCoursierDependency().apply(_), + deps(), + sources, + mapDependencies = Some(mapDependencies) + ) + } + + + def repositories: Seq[Repository] = ScalaWorkerModule.repositories + + def platformSuffix = T{ "" } + + private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r + + def prependShellScript: T[String] = T{ + mainClass() match{ + case None => "" + case Some(cls) => + val isWin = scala.util.Properties.isWin + mill.modules.Jvm.launcherUniversalScript( + cls, + Agg("$0"), Agg("%~dpnx0"), + forkArgs() + ) + } + } + + def sources = T.sources{ millSourcePath / 'src } + def resources = T.sources{ millSourcePath / 'resources } + def generatedSources = T{ Seq.empty[PathRef] } + def allSources = T{ sources() ++ generatedSources() } + + def allSourceFiles = T{ + for { + root <- allSources() + if exists(root.path) + path <- ls.rec(root.path) + if path.isFile && (path.ext == "scala" || path.ext == "java") + } yield PathRef(path) + } + + def compile: T[CompilationResult] = T{ + Lib.compileJava( + allSourceFiles().map(_.path.toIO).toArray, + compileClasspath().map(_.path.toIO).toArray, + javacOptions(), + upstreamCompileOutput() + ) + } + def localClasspath = T{ + resources() ++ Agg(compile().classes) + } + def compileClasspath = T{ + transitiveLocalClasspath() ++ + resources() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{compileIvyDeps() ++ transitiveIvyDeps()})() + } + + def upstreamAssemblyClasspath = T{ + transitiveLocalClasspath() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{runIvyDeps() ++ transitiveIvyDeps()})() + } + + def runClasspath = T{ + localClasspath() ++ + upstreamAssemblyClasspath() + } + + /** + * Build the assembly for upstream dependencies separate from the current classpath + * + * This should allow much faster assembly creation in the common case where + * upstream dependencies do not change + */ + def upstreamAssembly = T{ + createAssembly(upstreamAssemblyClasspath().map(_.path), mainClass()) + } + + def assembly = T{ + createAssembly( + Agg.from(localClasspath().map(_.path)), + mainClass(), + prependShellScript(), + Some(upstreamAssembly().path) + ) + } + + + def jar = T{ + createJar( + localClasspath().map(_.path).filter(exists), + mainClass() + ) + } + + def docJar = T[PathRef] { + val outDir = T.ctx().dest + + val javadocDir = outDir / 'javadoc + mkdir(javadocDir) + + val files = for{ + ref <- allSources() + if exists(ref.path) + p <- ls.rec(ref.path) + if p.isFile + } yield p.toNIO.toString + + val options = Seq("-d", javadocDir.toNIO.toString) + + if (files.nonEmpty) Jvm.baseInteractiveSubprocess( + commandArgs = Seq( + "javadoc" + ) ++ options ++ + Seq( + "-classpath", + compileClasspath() + .map(_.path) + .filter(_.ext != "pom") + .mkString(java.io.File.pathSeparator) + ) ++ + files.map(_.toString), + envArgs = Map(), + workingDir = T.ctx().dest + ) + + createJar(Agg(javadocDir))(outDir) + } + + def sourceJar = T { + createJar((allSources() ++ resources()).map(_.path).filter(exists)) + } + + def forkArgs = T{ Seq.empty[String] } + + def forkEnv = T{ sys.env.toMap } + + def launcher = T{ + Result.Success( + Jvm.createLauncher( + finalMainClass(), + runClasspath().map(_.path), + forkArgs() + ) + ) + } + + def ivyDepsTree(inverse: Boolean = false) = T.command { + val (flattened, resolution) = Lib.resolveDependenciesMetadata( + repositories, resolveCoursierDependency().apply(_), ivyDeps(), Some(mapDependencies) + ) + + println(coursier.util.Print.dependencyTree(flattened, resolution, + printExclusions = false, reverse = inverse)) + + Result.Success() + } + + def runLocal(args: String*) = T.command { + Jvm.runLocal( + finalMainClass(), + runClasspath().map(_.path), + args + ) + } + + def run(args: String*) = T.command{ + Jvm.interactiveSubprocess( + finalMainClass(), + runClasspath().map(_.path), + forkArgs(), + forkEnv(), + args, + workingDir = ammonite.ops.pwd + ) + } + + + def runMainLocal(mainClass: String, args: String*) = T.command { + Jvm.runLocal( + mainClass, + runClasspath().map(_.path), + args + ) + } + + def runMain(mainClass: String, args: String*) = T.command{ + Jvm.interactiveSubprocess( + mainClass, + runClasspath().map(_.path), + forkArgs(), + forkEnv(), + args, + workingDir = ammonite.ops.pwd + ) + } + + // publish artifact with name "mill_2.12.4" instead of "mill_2.12" + + def artifactName: T[String] = millModuleSegments.parts.mkString("-") + + def artifactId: T[String] = artifactName() +}
\ No newline at end of file diff --git a/scalalib/src/mill/scalalib/Lib.scala b/scalalib/src/mill/scalalib/Lib.scala index e6a7a255..7b4b5bdb 100644 --- a/scalalib/src/mill/scalalib/Lib.scala +++ b/scalalib/src/mill/scalalib/Lib.scala @@ -1,6 +1,9 @@ package mill package scalalib +import java.io.File +import javax.tools.ToolProvider + import ammonite.ops._ import ammonite.util.Util import coursier.{Cache, Dependency, Fetch, Repository, Resolution} @@ -15,6 +18,28 @@ object CompilationResult { case class CompilationResult(analysisFile: Path, classes: PathRef) object Lib{ + def compileJava(sources: Array[java.io.File], + classpath: Array[java.io.File], + javaOpts: Seq[String], + upstreamCompileOutput: Seq[CompilationResult]) + (implicit ctx: mill.util.Ctx) = { + val javac = ToolProvider.getSystemJavaCompiler() + + rm(ctx.dest / 'classes) + mkdir(ctx.dest / 'classes) + val cpArgs = + if(classpath.isEmpty) Seq() + else Seq("-cp", classpath.mkString(File.pathSeparator)) + + val args = Seq("-d", ctx.dest / 'classes) ++ cpArgs ++ javaOpts ++ sources + + javac.run( + ctx.log.inStream, ctx.log.outputStream, ctx.log.errorStream, + args.map(_.toString):_* + ) + if (ls(ctx.dest / 'classes).isEmpty) mill.eval.Result.Failure("Compilation Failed") + else mill.eval.Result.Success(CompilationResult(ctx.dest / 'zinc, PathRef(ctx.dest / 'classes))) + } private val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r private val MinorSnapshotVersion = raw"""(\d+)\.(\d+)\.([1-9]\d*)-SNAPSHOT""".r @@ -34,17 +59,23 @@ object Lib{ .toIO } - def depToDependency(dep: Dep, scalaVersion: String, platformSuffix: String = ""): Dependency = + + def depToDependencyJava(dep: Dep, platformSuffix: String = ""): Dependency = { dep match { - case Dep.Java(dep, cross) => + case Dep.Java(dep, cross, force) => dep.copy( module = dep.module.copy( name = dep.module.name + - (if (!cross) "" else platformSuffix) + (if (!cross) "" else platformSuffix) ) ) - case Dep.Scala(dep, cross) => + } + } + def depToDependency(dep: Dep, scalaVersion: String, platformSuffix: String = ""): Dependency = + dep match { + case d: Dep.Java => depToDependencyJava(dep) + case Dep.Scala(dep, cross, force) => dep.copy( module = dep.module.copy( name = @@ -53,7 +84,7 @@ object Lib{ "_" + scalaBinaryVersion(scalaVersion) ) ) - case Dep.Point(dep, cross) => + case Dep.Point(dep, cross, force) => dep.copy( module = dep.module.copy( name = @@ -65,7 +96,29 @@ object Lib{ } + def resolveDependenciesMetadata(repositories: Seq[Repository], + depToDependency: Dep => coursier.Dependency, + deps: TraversableOnce[Dep], + mapDependencies: Option[Dependency => Dependency] = None) = { + val depSeq = deps.toSeq + val flattened = depSeq.map(depToDependency) + + val forceVersions = depSeq.filter(_.force) + .map(depToDependency) + .map(mapDependencies.getOrElse(identity[Dependency](_))) + .map{d => d.module -> d.version} + .toMap + val start = Resolution( + flattened.map(mapDependencies.getOrElse(identity[Dependency](_))).toSet, + forceVersions = forceVersions, + mapDependencies = mapDependencies + ) + + val fetch = Fetch.from(repositories, Cache.fetch()) + val resolution = start.process.run(fetch).unsafePerformSync + (flattened, resolution) + } /** * Resolve dependencies using Coursier. * @@ -74,16 +127,14 @@ object Lib{ * `import $ivy` syntax. */ def resolveDependencies(repositories: Seq[Repository], - scalaVersion: String, + depToDependency: Dep => coursier.Dependency, deps: TraversableOnce[Dep], - platformSuffix: String = "", - sources: Boolean = false): Result[Agg[PathRef]] = { + sources: Boolean = false, + mapDependencies: Option[Dependency => Dependency] = None): Result[Agg[PathRef]] = { - val flattened = deps.map(depToDependency(_, scalaVersion, platformSuffix)).toSet - val start = Resolution(flattened) - - val fetch = Fetch.from(repositories, Cache.fetch()) - val resolution = start.process.run(fetch).unsafePerformSync + val (_, resolution) = resolveDependenciesMetadata( + repositories, depToDependency, deps, mapDependencies + ) val errs = resolution.metadataErrors if(errs.nonEmpty) { val header = @@ -130,16 +181,17 @@ object Lib{ } } def scalaCompilerIvyDeps(scalaVersion: String) = Agg[Dep]( - ivy"org.scala-lang:scala-compiler:$scalaVersion", - ivy"org.scala-lang:scala-reflect:$scalaVersion" + ivy"org.scala-lang:scala-compiler:$scalaVersion".forceVersion(), + ivy"org.scala-lang:scala-reflect:$scalaVersion".forceVersion() ) def scalaRuntimeIvyDeps(scalaVersion: String) = Agg[Dep]( - ivy"org.scala-lang:scala-library:$scalaVersion" + ivy"org.scala-lang:scala-library:$scalaVersion".forceVersion() ) def compilerBridgeIvyDep(scalaVersion: String) = Dep.Point( coursier.Dependency(coursier.Module("com.lihaoyi", "mill-bridge"), "0.1", transitive = false), - cross = false + cross = false, + force = false ) } diff --git a/scalalib/src/mill/scalalib/PublishModule.scala b/scalalib/src/mill/scalalib/PublishModule.scala index 3cc9fd30..2ab81269 100644 --- a/scalalib/src/mill/scalalib/PublishModule.scala +++ b/scalalib/src/mill/scalalib/PublishModule.scala @@ -9,22 +9,20 @@ import mill.util.Loose.Agg /** * Configuration necessary for publishing a Scala module to Maven Central or similar */ -trait PublishModule extends ScalaModule { outer => +trait PublishModule extends JavaModule { outer => import mill.scalalib.publish._ override def moduleDeps = Seq.empty[PublishModule] def pomSettings: T[PomSettings] def publishVersion: T[String] - def artifactId: T[String] = T { s"${artifactName()}${artifactSuffix()}" } + def publishSelfDependency = T{ Artifact(pomSettings().organization, artifactId(), publishVersion()), } def publishXmlDeps = T.task{ - val ivyPomDeps = ivyDeps().map( - Artifact.fromDep(_, scalaVersion(), Lib.scalaBinaryVersion(scalaVersion())) - ) + val ivyPomDeps = ivyDeps().map(resolvePublishDependency().apply(_)) val modulePomDeps = Task.sequence(moduleDeps.map(_.publishSelfDependency))() ivyPomDeps ++ modulePomDeps.map(Dependency(_, Scope.Compile)) } diff --git a/scalalib/src/mill/scalalib/ScalaModule.scala b/scalalib/src/mill/scalalib/ScalaModule.scala index dc39a4a8..b98f248e 100644 --- a/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/scalalib/src/mill/scalalib/ScalaModule.scala @@ -7,7 +7,7 @@ import mill.define.Task import mill.define.TaskModule import mill.eval.{PathRef, Result} import mill.modules.Jvm -import mill.modules.Jvm.{createAssembly, createJar, subprocess} +import mill.modules.Jvm.{createJar, subprocess} import Lib._ import mill.util.Loose.Agg import mill.util.DummyInputStream @@ -15,23 +15,29 @@ import mill.util.DummyInputStream /** * Core configuration required to compile a single Scala compilation target */ -trait ScalaModule extends mill.Module with TaskModule { outer => - def defaultCommandName() = "run" +trait ScalaModule extends JavaModule { outer => + def scalaWorker: ScalaWorkerModule = mill.scalalib.ScalaWorkerModule + trait Tests extends TestModule{ def scalaVersion = outer.scalaVersion() override def repositories = outer.repositories override def scalacPluginIvyDeps = outer.scalacPluginIvyDeps override def scalacOptions = outer.scalacOptions + override def javacOptions = outer.javacOptions override def scalaWorker = outer.scalaWorker override def moduleDeps = Seq(outer) } def scalaVersion: T[String] - def mainClass: T[Option[String]] = None + override def resolveCoursierDependency: Task[Dep => coursier.Dependency] = T.task{ + Lib.depToDependency(_: Dep, scalaVersion(), platformSuffix()) + } - def scalaWorker: ScalaWorkerModule = mill.scalalib.ScalaWorkerModule + override def resolvePublishDependency: Task[Dep => publish.Dependency] = T.task{ + publish.Artifact.fromDep(_: Dep, scalaVersion(), Lib.scalaBinaryVersion(scalaVersion())) + } - def finalMainClassOpt: T[Either[String, String]] = T{ + override def finalMainClassOpt: T[Either[String, String]] = T{ mainClass() match{ case Some(m) => Right(m) case None => @@ -47,58 +53,12 @@ trait ScalaModule extends mill.Module with TaskModule { outer => } } - def finalMainClass: T[String] = T{ - finalMainClassOpt() match { - case Right(main) => Result.Success(main) - case Left(msg) => Result.Failure(msg) - } - } - def ivyDeps = T{ Agg.empty[Dep] } - def compileIvyDeps = T{ Agg.empty[Dep] } def scalacPluginIvyDeps = T{ Agg.empty[Dep] } - def runIvyDeps = T{ Agg.empty[Dep] } def scalacOptions = T{ Seq.empty[String] } - def javacOptions = T{ Seq.empty[String] } - - def moduleDeps = Seq.empty[ScalaModule] - - - def transitiveModuleDeps: Seq[ScalaModule] = { - Seq(this) ++ moduleDeps.flatMap(_.transitiveModuleDeps).distinct - } - def unmanagedClasspath = T{ Agg.empty[PathRef] } - - - def transitiveIvyDeps: T[Agg[Dep]] = T{ - ivyDeps() ++ Task.traverse(moduleDeps)(_.transitiveIvyDeps)().flatten - } - - def upstreamCompileOutput = T{ - Task.traverse(moduleDeps)(_.compile) - } - - def transitiveLocalClasspath: T[Agg[PathRef]] = T{ - Task.traverse(moduleDeps)(m => - T.task{m.localClasspath() ++ m.transitiveLocalClasspath()} - )().flatten - } - - def resolveDeps(deps: Task[Agg[Dep]], sources: Boolean = false) = T.task{ - resolveDependencies( - repositories, - scalaVersion(), - deps(), - platformSuffix(), - sources - ) - } - - def repositories: Seq[Repository] = scalaWorker.repositories - - def platformSuffix = T{ "" } + override def repositories: Seq[Repository] = scalaWorker.repositories private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r @@ -110,7 +70,7 @@ trait ScalaModule extends mill.Module with TaskModule { outer => resolveDependencies( repositories, - scalaVersion0, + Lib.depToDependency(_, scalaVersion0, platformSuffix()), Seq(ivy"org.scala-sbt::compiler-bridge:1.1.0"), sources = true ).map(_.find(_.path.last == s"compiler-bridge_${scalaBinaryVersion0}-1.1.0-sources.jar").map(_.path).get) @@ -129,37 +89,20 @@ trait ScalaModule extends mill.Module with TaskModule { outer => T.task{scalaCompilerIvyDeps(scalaVersion()) ++ scalaRuntimeIvyDeps(scalaVersion())} )() } - - - def prependShellScript: T[String] = T{ - mainClass() match{ - case None => "" - case Some(cls) => - val isWin = scala.util.Properties.isWin - mill.modules.Jvm.launcherShellScript( - isWin, - cls, - Agg(if (isWin) "%~dp0%~nx0" else "$0"), - forkArgs() - ) - } + override def compileClasspath = T{ + transitiveLocalClasspath() ++ + resources() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{compileIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps()})() } - def sources = T.sources{ millSourcePath / 'src } - def resources = T.sources{ millSourcePath / 'resources } - def generatedSources = T{ Seq.empty[PathRef] } - def allSources = T{ sources() ++ generatedSources() } - - def allSourceFiles = T{ - for { - root <- allSources() - if exists(root.path) - path <- ls.rec(root.path) - if path.isFile && (path.ext == "scala" || path.ext == "java") - } yield PathRef(path) + override def upstreamAssemblyClasspath = T{ + transitiveLocalClasspath() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{runIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps()})() } - def compile: T[CompilationResult] = T.persistent{ + override def compile: T[CompilationResult] = T.persistent{ scalaWorker.worker().compileScala( scalaVersion(), allSourceFiles().map(_.path), @@ -172,55 +115,8 @@ trait ScalaModule extends mill.Module with TaskModule { outer => upstreamCompileOutput() ) } - def localClasspath = T{ - resources() ++ Agg(compile().classes) - } - def compileClasspath = T{ - transitiveLocalClasspath() ++ - resources() ++ - unmanagedClasspath() ++ - resolveDeps(T.task{compileIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps()})() - } - - def upstreamAssemblyClasspath = T{ - transitiveLocalClasspath() ++ - unmanagedClasspath() ++ - resolveDeps(T.task{runIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps()})() - } - - def runClasspath = T{ - localClasspath() ++ - upstreamAssemblyClasspath() - } - - /** - * Build the assembly for upstream dependencies separate from the current classpath - * - * This should allow much faster assembly creation in the common case where - * upstream dependencies do not change - */ - def upstreamAssembly = T{ - createAssembly(upstreamAssemblyClasspath().map(_.path), mainClass()) - } - - def assembly = T{ - createAssembly( - Agg.from(localClasspath().map(_.path)), - mainClass(), - prependShellScript(), - Some(upstreamAssembly().path) - ) - } - - - def jar = T{ - createJar( - localClasspath().map(_.path).filter(exists), - mainClass() - ) - } - def docJar = T { + override def docJar = T { val outDir = T.ctx().dest val javadocDir = outDir / 'javadoc @@ -233,7 +129,8 @@ trait ScalaModule extends mill.Module with TaskModule { outer => if p.isFile } yield p.toNIO.toString - val options = Seq("-d", javadocDir.toNIO.toString, "-usejavacp") + val pluginOptions = scalacPluginClasspath().map(pluginPathRef => s"-Xplugin:${pluginPathRef.path}") + val options = Seq("-d", javadocDir.toNIO.toString, "-usejavacp") ++ pluginOptions if (files.nonEmpty) subprocess( "scala.tools.nsc.ScalaDoc", @@ -244,77 +141,6 @@ trait ScalaModule extends mill.Module with TaskModule { outer => createJar(Agg(javadocDir))(outDir) } - def sourceJar = T { - createJar((allSources() ++ resources()).map(_.path).filter(exists)) - } - - def forkArgs = T{ Seq.empty[String] } - - def forkEnv = T{ sys.env.toMap } - - def launcher = T{ - Result.Success( - Jvm.createLauncher( - finalMainClass(), - runClasspath().map(_.path), - forkArgs() - ) - ) - } - - def ivyDepsTree(inverse: Boolean = false) = T.command { - import coursier.{Cache, Fetch, Resolution} - - val flattened = ivyDeps().map(depToDependency(_, scalaVersion(), platformSuffix())).toSeq - val start = Resolution(flattened.toSet) - val fetch = Fetch.from(repositories, Cache.fetch()) - val resolution = start.process.run(fetch).unsafePerformSync - - println(coursier.util.Print.dependencyTree(flattened, resolution, - printExclusions = false, reverse = inverse)) - - Result.Success() - } - - def runLocal(args: String*) = T.command { - Jvm.runLocal( - finalMainClass(), - runClasspath().map(_.path), - args - ) - } - - def run(args: String*) = T.command{ - Jvm.interactiveSubprocess( - finalMainClass(), - runClasspath().map(_.path), - forkArgs(), - forkEnv(), - args, - workingDir = ammonite.ops.pwd - ) - } - - - def runMainLocal(mainClass: String, args: String*) = T.command { - Jvm.runLocal( - mainClass, - runClasspath().map(_.path), - args - ) - } - - def runMain(mainClass: String, args: String*) = T.command{ - Jvm.interactiveSubprocess( - mainClass, - runClasspath().map(_.path), - forkArgs(), - forkEnv(), - args, - workingDir = ammonite.ops.pwd - ) - } - def console() = T.command{ if (T.ctx().log.inStream == DummyInputStream){ Result.Failure("repl needs to be run with the -i/--interactive flag") @@ -330,15 +156,22 @@ trait ScalaModule extends mill.Module with TaskModule { outer => } def ammoniteReplClasspath = T{ - resolveDeps(T.task{Agg(ivy"com.lihaoyi:::ammonite:1.1.0-7-33b728c")})() + localClasspath() ++ + transitiveLocalClasspath() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{ + runIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps() ++ + Agg(ivy"com.lihaoyi:::ammonite:1.1.0-12-f07633d") + })() } + def repl() = T.command{ if (T.ctx().log.inStream == DummyInputStream){ Result.Failure("repl needs to be run with the -i/--interactive flag") }else{ Jvm.interactiveSubprocess( mainClass = "ammonite.Main", - classPath = runClasspath().map(_.path) ++ ammoniteReplClasspath().map(_.path), + classPath = ammoniteReplClasspath().map(_.path), mainArgs = Nil, workingDir = pwd ) @@ -354,9 +187,11 @@ trait ScalaModule extends mill.Module with TaskModule { outer => if (crossFullScalaVersion()) scalaVersion() else Lib.scalaBinaryVersion(scalaVersion()) } - def artifactName: T[String] = millModuleSegments.parts.mkString("-") - def artifactSuffix: T[String] = T { s"_${artifactScalaVersion()}" } + def artifactSuffix: T[String] = s"_${artifactScalaVersion()}" + + override def artifactId: T[String] = artifactName() + artifactSuffix() + } diff --git a/scalalib/src/mill/scalalib/ScalaWorkerApi.scala b/scalalib/src/mill/scalalib/ScalaWorkerApi.scala index 9739089a..f6500ae8 100644 --- a/scalalib/src/mill/scalalib/ScalaWorkerApi.scala +++ b/scalalib/src/mill/scalalib/ScalaWorkerApi.scala @@ -29,7 +29,7 @@ trait ScalaWorkerModule extends mill.Module{ } else { resolveDependencies( repositories, - "2.12.4", + Lib.depToDependency(_, "2.12.4", ""), Seq(ivy"com.lihaoyi::mill-scalaworker:${sys.props("MILL_VERSION")}") ).map(_.map(_.path)) } @@ -48,7 +48,7 @@ trait ScalaWorkerModule extends mill.Module{ def compilerInterfaceClasspath = T{ resolveDependencies( repositories, - "2.12.4", + Lib.depToDependency(_, "2.12.4", ""), Seq(ivy"org.scala-sbt:compiler-interface:1.1.0") ) } @@ -56,6 +56,7 @@ trait ScalaWorkerModule extends mill.Module{ } trait ScalaWorkerApi { + def compileScala(scalaVersion: String, sources: Agg[Path], compilerBridgeSources: Path, diff --git a/scalalib/src/mill/scalalib/publish/Ivy.scala b/scalalib/src/mill/scalalib/publish/Ivy.scala index 3b271fa8..22e26ff6 100644 --- a/scalalib/src/mill/scalalib/publish/Ivy.scala +++ b/scalalib/src/mill/scalalib/publish/Ivy.scala @@ -42,10 +42,10 @@ object Ivy { private def renderDependency(dep: Dependency) = { if (dep.exclusions.isEmpty) - <dependency org={dep.artifact.group} name={dep.artifact.id} rev={dep.artifact.version} conf={s"${dep.scope}->default(${dep.configuration.getOrElse("compile")})"} /> + <dependency org={dep.artifact.group} name={dep.artifact.id} rev={dep.artifact.version} conf={s"${scopeToConf(dep.scope)}->${dep.configuration.getOrElse("default(compile)")}"} /> else - <dependency org={dep.artifact.group} name={dep.artifact.id} rev={dep.artifact.version} conf={s"${dep.scope}->default(${dep.configuration.getOrElse("compile")})"}> - {dep.exclusions.map(ex => <exclude org={ex._1} name={ex._2} matcher="exact"/>).toSeq} + <dependency org={dep.artifact.group} name={dep.artifact.id} rev={dep.artifact.version} conf={s"${scopeToConf(dep.scope)}->${dep.configuration.getOrElse("default(compile)")}"}> + {dep.exclusions.map(ex => <exclude org={ex._1} name={ex._2} matcher="exact"/>)} </dependency> } diff --git a/scalalib/src/mill/scalalib/publish/Pom.scala b/scalalib/src/mill/scalalib/publish/Pom.scala index 3c8ba4dc..84cf0632 100644 --- a/scalalib/src/mill/scalalib/publish/Pom.scala +++ b/scalalib/src/mill/scalalib/publish/Pom.scala @@ -108,7 +108,7 @@ object Pom { <groupId>{ex._1}</groupId> <artifactId>{ex._2}</artifactId> </exclude> - )}.toSeq + )} </exclusions> {scope} </dependency> diff --git a/scalalib/src/mill/scalalib/publish/settings.scala b/scalalib/src/mill/scalalib/publish/settings.scala index 34f7e7ad..2cd92eb2 100644 --- a/scalalib/src/mill/scalalib/publish/settings.scala +++ b/scalalib/src/mill/scalalib/publish/settings.scala @@ -7,19 +7,23 @@ case class Artifact(group: String, id: String, version: String) { } object Artifact { - - def fromDep(dep: Dep, - scalaFull: String, - scalaBin: String): Dependency = { + def fromDepJava(dep: Dep) = { dep match { - case Dep.Java(dep, cross) => + case Dep.Java(dep, cross, force) => Dependency( Artifact(dep.module.organization, dep.module.name, dep.version), Scope.Compile, - if (dep.configuration == "" ) None else Some(dep.configuration), + if (dep.configuration == "") None else Some(dep.configuration), dep.exclusions.toList ) - case Dep.Scala(dep, cross) => + } + } + def fromDep(dep: Dep, + scalaFull: String, + scalaBin: String): Dependency = { + dep match { + case d: Dep.Java => fromDepJava(d) + case Dep.Scala(dep, cross, force) => Dependency( Artifact( dep.module.organization, @@ -30,7 +34,7 @@ object Artifact { if (dep.configuration == "") None else Some(dep.configuration), dep.exclusions.toList ) - case Dep.Point(dep, cross) => + case Dep.Point(dep, cross, force) => Dependency( Artifact( dep.module.organization, diff --git a/scalalib/test/resources/hello-java/core/src/hello/Core.java b/scalalib/test/resources/hello-java/core/src/hello/Core.java new file mode 100644 index 00000000..bef1a5a0 --- /dev/null +++ b/scalalib/test/resources/hello-java/core/src/hello/Core.java @@ -0,0 +1,8 @@ +package hello; + +public class Core{ + public static String msg(){ + return "Hello World"; + } + +}
\ No newline at end of file diff --git a/scalalib/test/resources/hello-java/main/src/hello/Main.java b/scalalib/test/resources/hello-java/main/src/hello/Main.java new file mode 100644 index 00000000..44b927bd --- /dev/null +++ b/scalalib/test/resources/hello-java/main/src/hello/Main.java @@ -0,0 +1,7 @@ +package hello; + +public class Main{ + public static void main(String[] args){ + System.out.println(Core.msg() + " " + args[0]); + } +}
\ No newline at end of file diff --git a/scalalib/test/resources/hello-world-macros/core/src/Main.scala b/scalalib/test/resources/hello-world-macros/core/src/Main.scala new file mode 100644 index 00000000..20924a60 --- /dev/null +++ b/scalalib/test/resources/hello-world-macros/core/src/Main.scala @@ -0,0 +1,5 @@ +import monocle.macros._ +@Lenses case class Foo(bar: String) +object Main extends App { + println(Foo.bar.get(Foo("bar"))) +} diff --git a/scalalib/test/src/mill/scalalib/GenIdeaTests.scala b/scalalib/test/src/mill/scalalib/GenIdeaTests.scala index 0f776802..51665867 100644 --- a/scalalib/test/src/mill/scalalib/GenIdeaTests.scala +++ b/scalalib/test/src/mill/scalalib/GenIdeaTests.scala @@ -23,7 +23,7 @@ object GenIdeaTests extends TestSuite { 'genIdeaTests - { val pp = new scala.xml.PrettyPrinter(999, 4) - val layout = GenIdea.xmlFileLayout( + val layout = GenIdeaImpl.xmlFileLayout( helloWorldEvaluator.evaluator, HelloWorld, ("JDK_1_8", "1.8 (1)"), fetchMillModules = false) diff --git a/scalalib/test/src/mill/scalalib/HelloJavaTests.scala b/scalalib/test/src/mill/scalalib/HelloJavaTests.scala new file mode 100644 index 00000000..7794d5a5 --- /dev/null +++ b/scalalib/test/src/mill/scalalib/HelloJavaTests.scala @@ -0,0 +1,60 @@ +package mill.scalalib + + +import ammonite.ops.{%, %%, cp, ls, mkdir, pwd, rm, up} +import ammonite.ops.ImplicitWd._ +import mill.util.{TestEvaluator, TestUtil} +import utest._ +import utest.framework.TestPath + + +object HelloJavaTests extends TestSuite { + + object HelloJava extends TestUtil.BaseModule{ + def millSourcePath = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') + object core extends JavaModule + object main extends JavaModule{ + def moduleDeps = Seq(core) + } + } + val resourcePath = pwd / 'scalalib / 'test / 'resources / "hello-java" + + def init()(implicit tp: TestPath) = { + val eval = new TestEvaluator(HelloJava) + rm(HelloJava.millSourcePath) + rm(eval.outPath) + mkdir(HelloJava.millSourcePath / up) + cp(resourcePath, HelloJava.millSourcePath) + eval + } + def tests: Tests = Tests { + 'scalaVersion - { + val eval = init() + + val Right((res1, n1)) = eval.apply(HelloJava.core.compile) + val Right((res2, 0)) = eval.apply(HelloJava.core.compile) + val Right((res3, n2)) = eval.apply(HelloJava.main.compile) + + assert( + res1 == res2, + n1 != 0, + n2 != 0, + ls.rec(res1.classes.path).exists(_.last == "Core.class"), + !ls.rec(res1.classes.path).exists(_.last == "Main.class"), + ls.rec(res3.classes.path).exists(_.last == "Main.class"), + !ls.rec(res3.classes.path).exists(_.last == "Core.class") + ) + } + 'docJar - { + val eval = init() + + val Right((ref1, _)) = eval.apply(HelloJava.core.docJar) + val Right((ref2, _)) = eval.apply(HelloJava.main.docJar) + + assert( + %%("jar", "tf", ref1.path).out.lines.contains("hello/Core.html"), + %%("jar", "tf", ref2.path).out.lines.contains("hello/Main.html") + ) + } + } +} diff --git a/scalalib/test/src/mill/scalalib/HelloWorldTests.scala b/scalalib/test/src/mill/scalalib/HelloWorldTests.scala index 74078221..10f936f0 100644 --- a/scalalib/test/src/mill/scalalib/HelloWorldTests.scala +++ b/scalalib/test/src/mill/scalalib/HelloWorldTests.scala @@ -98,6 +98,38 @@ object HelloWorldTests extends TestSuite { override def ivyDeps = Agg(ivy"com.lihaoyi::sourcecode:0.1.4") } } + + object HelloWorldTypeLevel extends HelloBase{ + object foo extends ScalaModule { + def scalaVersion = "2.11.8" + override def mapDependencies(d: coursier.Dependency) = { + val artifacts = Set("scala-library", "scala-compiler", "scala-reflect") + if (d.module.organization != "org.scala-lang" || !artifacts(d.module.name)) d + else d.copy(module = d.module.copy(organization = "org.typelevel")) + } + + def ivyDeps = Agg( + ivy"com.github.julien-truffaut::monocle-macro::1.4.0" + ) + def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ Agg( + ivy"org.scalamacros:::paradise:2.1.0" + ) + } + } + + object HelloWorldMacros extends HelloBase{ + object core extends ScalaModule { + def scalaVersion = "2.12.4" + + def ivyDeps = Agg( + ivy"com.github.julien-truffaut::monocle-macro::1.4.0" + ) + def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ Agg( + ivy"org.scalamacros:::paradise:2.1.0" + ) + } + } + val resourcePath = pwd / 'scalalib / 'test / 'resources / "hello-world" def jarMainClass(jar: JarFile): Option[String] = { @@ -438,7 +470,42 @@ object HelloWorldTests extends TestSuite { !result2.exists(_.path.last == "sourcecode_2.12-0.1.3.jar") ) } - } - + 'typeLevel - workspaceTest(HelloWorldTypeLevel){ eval => + val classPathsToCheck = Seq( + HelloWorldTypeLevel.foo.runClasspath, + HelloWorldTypeLevel.foo.ammoniteReplClasspath, + HelloWorldTypeLevel.foo.compileClasspath + ) + for(cp <- classPathsToCheck){ + val Right((result, _)) = eval.apply(cp) + assert( + // Make sure every relevant piece org.scala-lang has been substituted for org.typelevel + !result.map(_.toString).exists(x => + x.contains("scala-lang") && + (x.contains("scala-library") || x.contains("scala-compiler") || x.contains("scala-reflect")) + ), + result.map(_.toString).exists(x => x.contains("typelevel") && x.contains("scala-library")) + ) + } + } + 'macros - { + // make sure macros are applied when compiling/running + 'runMain - workspaceTest( + HelloWorldMacros, + resourcePath = pwd / 'scalalib / 'test / 'resources / "hello-world-macros" + ){ eval => + val Right((_, evalCount)) = eval.apply(HelloWorldMacros.core.runMain("Main")) + assert(evalCount > 0) + } + // make sure macros are applied when compiling during scaladoc generation + 'docJar - workspaceTest( + HelloWorldMacros, + resourcePath = pwd / 'scalalib / 'test / 'resources / "hello-world-macros" + ){ eval => + val Right((_, evalCount)) = eval.apply(HelloWorldMacros.core.docJar) + assert(evalCount > 0) + } + } + } } diff --git a/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala b/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala index b1fcec3e..4240478c 100644 --- a/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala +++ b/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala @@ -10,7 +10,11 @@ import utest._ object ResolveDepsTests extends TestSuite { val repos = Seq(Cache.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")) - def evalDeps(deps: Agg[Dep]): Result[Agg[PathRef]] = Lib.resolveDependencies(repos, "2.12.4", deps) + def evalDeps(deps: Agg[Dep]): Result[Agg[PathRef]] = Lib.resolveDependencies( + repos, + Lib.depToDependency(_, "2.12.4", ""), + deps + ) val tests = Tests { 'resolveValidDeps - { @@ -26,6 +30,14 @@ object ResolveDepsTests extends TestSuite { assert(paths.items.next.path.toString.contains("natives-macos")) } + 'resolveTransitiveRuntimeDeps - { + val deps = Agg(ivy"org.mockito:mockito-core:2.7.22") + val Success(paths) = evalDeps(deps) + assert(paths.nonEmpty) + assert(paths.exists(_.path.toString.contains("objenesis"))) + assert(paths.exists(_.path.toString.contains("byte-buddy"))) + } + 'excludeTransitiveDeps - { val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".exclude("com.lihaoyi" -> "fansi_2.12")) val Success(paths) = evalDeps(deps) diff --git a/scalalib/test/src/mill/scalalib/publish/IvyTests.scala b/scalalib/test/src/mill/scalalib/publish/IvyTests.scala index 0f275dd9..d187f969 100644 --- a/scalalib/test/src/mill/scalalib/publish/IvyTests.scala +++ b/scalalib/test/src/mill/scalalib/publish/IvyTests.scala @@ -26,9 +26,9 @@ object IvyTests extends TestSuite { 'topLevel - { val info = singleNode(fullIvy \ "info") assert( - singleAttr(info, "organisation") == artifact.group - , singleAttr(info, "module") == artifact.id - , singleAttr(info, "revision") == artifact.version + singleAttr(info, "organisation") == artifact.group, + singleAttr(info, "module") == artifact.id, + singleAttr(info, "revision") == artifact.version ) } @@ -40,10 +40,10 @@ object IvyTests extends TestSuite { dependencies.zipWithIndex.foreach { case (dep, index) => assert( - singleAttr(dep, "org") == ivyDeps(index).artifact.group - , singleAttr(dep, "name") == ivyDeps(index).artifact.id - , singleAttr(dep, "rev") == ivyDeps(index).artifact.version - , (dep \ "exclude").zipWithIndex forall { case (exclude, j) => + singleAttr(dep, "org") == ivyDeps(index).artifact.group, + singleAttr(dep, "name") == ivyDeps(index).artifact.id, + singleAttr(dep, "rev") == ivyDeps(index).artifact.version, + (dep \ "exclude").zipWithIndex forall { case (exclude, j) => singleAttr(exclude, "org") == ivyDeps(index).exclusions(j)._1 && singleAttr(exclude, "name") == ivyDeps(index).exclusions(j)._2 } diff --git a/scalaworker/src/mill/scalaworker/ScalaWorker.scala b/scalaworker/src/mill/scalaworker/ScalaWorker.scala index 0411af92..9a4c317c 100644 --- a/scalaworker/src/mill/scalaworker/ScalaWorker.scala +++ b/scalaworker/src/mill/scalaworker/ScalaWorker.scala @@ -5,6 +5,7 @@ import java.lang.annotation.Annotation import java.net.URLClassLoader import java.util.Optional import java.util.zip.ZipInputStream +import javax.tools.ToolProvider import ammonite.ops.{Path, exists, ls, mkdir, rm, up} import ammonite.util.Colors @@ -18,9 +19,14 @@ import mill.scalalib.Lib.grepJar import mill.scalalib.TestRunner.Result import mill.util.{Ctx, PrintLogger} import sbt.internal.inc._ +import sbt.internal.inc.classfile.Analyze +import sbt.internal.inc.classpath.ClasspathUtilities +import sbt.internal.inc.javac.JavaCompiler import sbt.internal.util.{ConsoleOut, MainAppender} +import sbt.io.PathFinder import sbt.testing._ -import sbt.util.LogExchange +import sbt.util.{InterfaceUtil, LogExchange} +import xsbti.AnalysisCallback import scala.collection.mutable @@ -131,6 +137,7 @@ class ScalaWorker(ctx0: mill.util.Ctx, .getOrElse(Seq.empty[String]) } + def compileScala(scalaVersion: String, sources: Agg[Path], compilerBridgeSources: Path, |