diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2018-02-25 22:33:23 -0800 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2018-02-26 00:00:29 -0800 |
commit | 2a59977d9c4aa23513d2c38a9fa151f9c11e8dc0 (patch) | |
tree | 6a16d7db4538e73c1fe53f2fda812f2d12e71cf8 | |
parent | 08e27d9b94e38f64f6680828c64ab3c0a8f5537f (diff) | |
download | mill-2a59977d9c4aa23513d2c38a9fa151f9c11e8dc0.tar.gz mill-2a59977d9c4aa23513d2c38a9fa151f9c11e8dc0.tar.bz2 mill-2a59977d9c4aa23513d2c38a9fa151f9c11e8dc0.zip |
Delete SBT build, use Mill for CI & development
Also re-organize the test matrix to split out the unit & integration tests into 3 separate builds, and removing the integration tests from the `-dev` and `-mill` jobs. That should speed up the test run while running all tests and ensuring the dev/release assemblies work
-rw-r--r-- | .travis.yml | 25 | ||||
-rw-r--r-- | build.sbt | 300 | ||||
-rwxr-xr-x | build.sc | 23 | ||||
-rwxr-xr-x | ci/publish-local.sh | 8 | ||||
-rwxr-xr-x | ci/release.sh | 6 | ||||
-rwxr-xr-x | ci/test-mill-0.sh | 9 | ||||
-rwxr-xr-x | ci/test-mill-1.sh | 9 | ||||
-rwxr-xr-x | ci/test-mill-2.sh | 9 | ||||
-rwxr-xr-x | ci/test-mill-built.sh | 18 | ||||
-rwxr-xr-x | ci/test-mill-dev.sh | 13 | ||||
-rwxr-xr-x | ci/test-mill-release.sh | 4 | ||||
-rwxr-xr-x | ci/test-sbt-built.sh | 13 | ||||
-rwxr-xr-x | ci/test-sbt.sh | 11 | ||||
-rw-r--r-- | readme.md | 79 |
14 files changed, 83 insertions, 444 deletions
diff --git a/.travis.yml b/.travis.yml index 57a75bef..45373358 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,20 +2,19 @@ language: scala sudo: required dist: trusty -scala: - - 2.12.4 - jdk: - oraclejdk8 matrix: include: - stage: build - env: CI_SCRIPT=ci/test-sbt.sh + env: CI_SCRIPT=ci/test-mill-0.sh + - stage: build + env: CI_SCRIPT=ci/test-mill-1.sh - stage: build - env: CI_SCRIPT=ci/test-sbt-built.sh + env: CI_SCRIPT=ci/test-mill-2.sh - stage: build - env: CI_SCRIPT=ci/test-mill-built.sh + env: CI_SCRIPT=ci/test-mill-dev.sh - stage: build env: CI_SCRIPT=ci/test-mill-release.sh - stage: release @@ -24,16 +23,10 @@ matrix: env: CI_SCRIPT="ci/on-master.py ci/publish-docs.sh" script: - - git fetch --unshallow --tags + - curl -L -o ~/bin/mill https://github.com/lihaoyi/mill/releases/download/0.1.3/0.1.3 && chmod +x ~/bin/mill + - export PATH=~/bin/mill:$PATH - "$CI_SCRIPT" + cache: directories: - - $HOME/.sbt/0.13/dependency - - $HOME/.sbt/boot/scala* - - $HOME/.sbt/launchers - - $HOME/.ivy2/cache - - $HOME/.coursier - -before_cache: - - find $HOME/.sbt -name "*.lock" -type f -delete - - find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete
\ No newline at end of file + - $HOME/.coursier
\ No newline at end of file diff --git a/build.sbt b/build.sbt deleted file mode 100644 index 47ba823f..00000000 --- a/build.sbt +++ /dev/null @@ -1,300 +0,0 @@ -import java.io.File - -parallelExecution := false - -val sharedSettings = Seq( - scalaVersion := "2.12.4", - organization := "com.lihaoyi", - libraryDependencies += "com.lihaoyi" %% "utest" % "0.6.0" % "test", - - testFrameworks += new TestFramework("mill.UTestFramework"), - - scalaSource in Compile := baseDirectory.value / "src", - resourceDirectory in Compile := baseDirectory.value / "resources", - - scalaSource in Test := baseDirectory.value / "test" / "src", - resourceDirectory in Test := baseDirectory.value / "test" / "resources", - - parallelExecution in Test := false, - test in assembly := {}, - - libraryDependencies += "com.lihaoyi" %% "acyclic" % "0.1.7" % "provided", - resolvers += Resolver.sonatypeRepo("releases"), - scalacOptions += "-P:acyclic:force", - autoCompilerPlugins := true, - addCompilerPlugin("com.lihaoyi" %% "acyclic" % "0.1.7"), - - libraryDependencies += "com.lihaoyi" % "ammonite" % "1.0.3-49-7fa03d0" cross CrossVersion.full, - mainClass in Test := Some("ammonite.Main") -) - -val pluginSettings = Seq( - scalacOptions in Test ++= { - val jarFile = (packageBin in (moduledefs, Compile)).value - val addPlugin = "-Xplugin:" + jarFile.getAbsolutePath - // add plugin timestamp to compiler options to trigger recompile of - // main after editing the plugin. (Otherwise a 'clean' is needed.) - val dummy = "-Jdummy=" + jarFile.lastModified - Seq(addPlugin, dummy) - } -) - -lazy val ammoniteRunner = project - .in(file("target/ammoniteRunner")) - .settings( - scalaVersion := "2.12.4", - target := baseDirectory.value, - libraryDependencies += - "com.lihaoyi" % "ammonite" % "1.0.3-21-05b5d32" cross CrossVersion.full - ) - - -def ammoniteRun(hole: SettingKey[File], args: String => List[String], suffix: String = "") = Def.task{ - val target = hole.value / suffix - if (!target.exists()) { - IO.createDirectory(target) - (runner in(ammoniteRunner, Compile)).value.run( - "ammonite.Main", - (dependencyClasspath in(ammoniteRunner, Compile)).value.files, - args(target.toString), - streams.value.log - ) - } - target -} - -lazy val clientserver = project - .settings( - sharedSettings, - pluginSettings, - name := "mill-core", - libraryDependencies ++= Seq( - "org.scala-sbt.ipcsocket" % "ipcsocket" % "1.0.0" - ) - ) - -lazy val core = project - .dependsOn(moduledefs) - .settings( - sharedSettings, - pluginSettings, - name := "mill-core", - libraryDependencies ++= Seq( - "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", - "com.lihaoyi" %% "sourcecode" % "0.1.4", - "com.lihaoyi" % "ammonite" % "1.0.3-21-05b5d32" cross CrossVersion.full - ), - sourceGenerators in Compile += { - ammoniteRun(sourceManaged in Compile, List("shared.sc", "generateCoreSources", _)) - .taskValue - .map(x => (x ** "*.scala").get) - } - ) - -lazy val main = project - .dependsOn(core, clientserver) - .settings( - sharedSettings, - pluginSettings, - name := "mill-main", - libraryDependencies ++= Seq( - "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided" - ), - - sourceGenerators in Test += { - ammoniteRun(sourceManaged in Test, List("shared.sc", "generateCoreTestSources", _)) - .taskValue - .map(x => (x ** "*.scala").get) - } - ) - -lazy val moduledefs = project - .settings( - sharedSettings, - name := "mill-moduledefs", - libraryDependencies ++= Seq( - "org.scala-lang" % "scala-compiler" % scalaVersion.value, - "com.lihaoyi" %% "sourcecode" % "0.1.4" - ), - publishArtifact in Compile := false - ) - -lazy val scalaWorkerProps = Def.task{ - Seq("-DMILL_SCALA_WORKER=" + (fullClasspath in (scalaworker, Compile)).value.map(_.data).mkString(",")) -} - -lazy val scalalib = project - .dependsOn(main % "compile->compile;test->test") - .settings( - sharedSettings, - pluginSettings, - name := "mill-scalalib", - fork := true, - baseDirectory in Test := (baseDirectory in Test).value / "..", - libraryDependencies ++= Seq( - "org.scala-sbt" % "test-interface" % "1.0" - ) - ) - -lazy val scalaworker: Project = project - .dependsOn(main, scalalib) - .settings( - sharedSettings, - pluginSettings, - name := "mill-scalaworker", - fork := true, - libraryDependencies ++= Seq( - "org.scala-sbt" %% "zinc" % "1.0.5" - ) - ) - -def genTask(m: Project) = Def.task{ - Seq((packageBin in (m, Compile)).value, (packageSrc in (m, Compile)).value) ++ - (externalDependencyClasspath in (m, Compile)).value.map(_.data) -} - -(javaOptions in scalalib) := { - scalaWorkerProps.value ++ - Seq("-DMILL_BUILD_LIBRARIES=" + - ( - genTask(moduledefs).value ++ - genTask(core).value ++ - genTask(main).value ++ - genTask(scalalib).value ++ - genTask(scalajslib).value - ).mkString(",") - ) -} -lazy val scalajslib = project - .dependsOn(scalalib % "compile->compile;test->test") - .settings( - sharedSettings, - name := "mill-scalajslib", - fork in Test := true, - baseDirectory in Test := (baseDirectory in Test).value / ".." - ) - -def jsbridge(binary: String, version: String) = - Project( - id = "scalajsbridge_" + binary.replace('.', '_'), - base = file("scalajslib/jsbridges/" + binary) - ).dependsOn(scalajslib) - .settings( - sharedSettings, - organization := "com.lihaoyi", - scalaVersion := "2.12.4", - name := "mill-js-bridge", - libraryDependencies ++= Seq( - "org.scala-js" %% "scalajs-tools" % version, - "org.scala-js" %% "scalajs-sbt-test-adapter" % version - ) - ) - -lazy val scalajsbridge_0_6 = jsbridge("0.6", "0.6.22") - .settings( - libraryDependencies ++= Seq( - "org.scala-js" %% "scalajs-js-envs" % "0.6.22" - ) - ) - -lazy val scalajsbridge_1_0 = jsbridge("1.0", "1.0.0-M2") - .settings( - libraryDependencies ++= Seq( - "org.scala-js" %% "scalajs-env-nodejs" % "1.0.0-M2" - ) - ) - -javaOptions in (scalajslib, Test) := jsbridgeProps.value.toSeq ++ scalaWorkerProps.value - -val jsbridgeProps = Def.task{ - val mapping = Map( - "MILL_SCALAJS_BRIDGE_0_6" -> - (packageBin in (scalajsbridge_0_6, Compile)).value.absolutePath.toString, - "MILL_SCALAJS_BRIDGE_1_0" -> - (packageBin in (scalajsbridge_1_0, Compile)).value.absolutePath.toString - ) - for((k, v) <- mapping) yield s"-D$k=$v" -} - -val testRepos = Map( - "MILL_ACYCLIC_REPO" -> ammoniteRun( - resourceManaged in test, - List("shared.sc", "downloadTestRepo", "lihaoyi/acyclic", "bc41cd09a287e2c270271e27ccdb3066173a8598", _), - suffix = "acyclic" - ), - "MILL_JAWN_REPO" -> ammoniteRun( - resourceManaged in test, - List("shared.sc", "downloadTestRepo", "non/jawn", "fd8dc2b41ce70269889320aeabf8614fe1e8fbcb", _), - suffix = "jawn" - ), - "MILL_BETTERFILES_REPO" -> ammoniteRun( - resourceManaged in test, - List("shared.sc", "downloadTestRepo", "pathikrit/better-files", "ba74ae9ef784dcf37f1b22c3990037a4fcc6b5f8", _), - suffix = "better-files" - ), - "MILL_AMMONITE_REPO" -> ammoniteRun( - resourceManaged in test, - List("shared.sc", "downloadTestRepo", "lihaoyi/ammonite", "96ea548d5e3b72ab6ad4d9765e205bf6cc1c82ac", _), - suffix = "ammonite" - ), - "MILL_UPICKLE_REPO" -> ammoniteRun( - resourceManaged in test, - List("shared.sc", "downloadTestRepo", "lihaoyi/upickle", "7f33085c890db7550a226c349832eabc3cd18769", _), - suffix = "upickle" - ) -) - -lazy val integration = project - .dependsOn(main % "compile->compile;test->test", scalalib, scalajslib) - .settings( - sharedSettings, - name := "integration", - fork := true, - baseDirectory in Test := (baseDirectory in Test).value / "..", - javaOptions in Test := { - val kvs = Seq( - "MILL_ACYCLIC_REPO" -> testRepos("MILL_ACYCLIC_REPO").value, - "MILL_AMMONITE_REPO" -> testRepos("MILL_AMMONITE_REPO").value, - "MILL_JAWN_REPO" -> testRepos("MILL_JAWN_REPO").value, - "MILL_BETTERFILES_REPO" -> testRepos("MILL_BETTERFILES_REPO").value, - "MILL_UPICKLE_REPO" -> testRepos("MILL_UPICKLE_REPO").value - ) - jsbridgeProps.value.toSeq ++ scalaWorkerProps.value ++ (for((k, v) <- kvs) yield s"-D$k=$v") - } - ) - -lazy val bin = project - .in(file("target/bin")) - .dependsOn(scalalib, scalajslib) - .settings( - sharedSettings, - target := baseDirectory.value, - fork := true, - connectInput in (Test, run) := true, - outputStrategy in (Test, run) := Some(StdoutOutput), - mainClass in (Test, run) := Some("mill.Main"), - baseDirectory in (Test, run) := (baseDirectory in (Compile, run)).value / ".." / "..", - javaOptions in (Test, run) := { - (javaOptions in (scalalib, Compile)).value ++ - jsbridgeProps.value.toSeq ++ - scalaWorkerProps.value - }, - assemblyOption in assembly := { - val extraArgs = (javaOptions in (Test, run)).value.mkString(" ") - (assemblyOption in assembly).value.copy( - prependShellScript = Some( - Seq( - "#!/usr/bin/env sh", - s"""exec java $extraArgs $$JAVA_OPTS -cp "$$0" mill.Main "$$@" """ - ) - ) - ) - }, - assembly in Test := { - val dest = target.value/"mill" - IO.copyFile(assembly.value, dest) - import sys.process._ - Seq("chmod", "+x", dest.getAbsolutePath).! - dest - } - ) @@ -236,6 +236,12 @@ object dev extends MillModule{ java.nio.file.Files.setPosixFilePermissions(outputPath.toNIO, perms) PathRef(outputPath) } + + def assembly = T{ + mv(super.assembly().path, T.ctx().dest / 'mill) + PathRef(T.ctx().dest / 'mill) + } + def prependShellScript = launcherScript(forkArgs(), runClasspath().map(_.path.toString)) def run(args: String*) = T.command{ @@ -257,14 +263,17 @@ object dev extends MillModule{ def release = T{ - createAssembly( - dev.runClasspath().map(_.path), - prependShellScript = launcherScript( - Seq("-DMILL_VERSION=" + publishVersion()._2), - Agg("$0") - ) - + mv( + createAssembly( + dev.runClasspath().map(_.path), + prependShellScript = launcherScript( + Seq("-DMILL_VERSION=" + publishVersion()._2), + Agg("$0") + ) + ).path, + T.ctx().dest / 'mill ) + PathRef(T.ctx().dest / 'mill) } val isMasterCommit = { diff --git a/ci/publish-local.sh b/ci/publish-local.sh index 0d7559e3..fc9e06b9 100755 --- a/ci/publish-local.sh +++ b/ci/publish-local.sh @@ -2,10 +2,6 @@ set -eux -# First build using SBT -sbt bin/test:assembly +mill all __.publishLocal release -# Build Mill using SBT -target/bin/mill all __.publishLocal release - -mv out/release/dest/out.jar ~/mill-release +mv out/release/dest/mill ~/mill-release diff --git a/ci/release.sh b/ci/release.sh index a233ad4d..9072ce96 100755 --- a/ci/release.sh +++ b/ci/release.sh @@ -2,14 +2,12 @@ set -eux -sbt bin/test:assembly - echo $GPG_PRIVATE_KEY_B64 | base64 --decode > gpg_key gpg --import gpg_key rm gpg_key -target/bin/mill mill.scalalib.PublishModule/publishAll \ +mill mill.scalalib.PublishModule/publishAll \ lihaoyi:$SONATYPE_PASSWORD \ $GPG_PASSWORD \ __.publishArtifacts \ @@ -17,4 +15,4 @@ target/bin/mill mill.scalalib.PublishModule/publishAll \ true \ -target/bin/mill uploadToGithub $GITHUB_ACCESS_TOKEN +mill uploadToGithub $GITHUB_ACCESS_TOKEN diff --git a/ci/test-mill-0.sh b/ci/test-mill-0.sh new file mode 100755 index 00000000..3d1470f1 --- /dev/null +++ b/ci/test-mill-0.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -eux + +# Starting from scratch... +git clean -xdf + +# Run tests using Mill built using SBT +mill all {clientserver,main,scalalib,scalajslib}.test diff --git a/ci/test-mill-1.sh b/ci/test-mill-1.sh new file mode 100755 index 00000000..079cb519 --- /dev/null +++ b/ci/test-mill-1.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -eux + +# Starting from scratch... +git clean -xdf + +# Run tests using Mill built using SBT +mill integration.test "mill.integration.local.{JawnTests,BetterFilesTests,UpickleTests}" diff --git a/ci/test-mill-2.sh b/ci/test-mill-2.sh new file mode 100755 index 00000000..3b0da706 --- /dev/null +++ b/ci/test-mill-2.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -eux + +# Starting from scratch... +git clean -xdf + +# Run tests using Mill built using SBT +mill integration.test "mill.integration.local.{AcyclicTests,AmmoniteTests}" diff --git a/ci/test-mill-built.sh b/ci/test-mill-built.sh deleted file mode 100755 index 85879106..00000000 --- a/ci/test-mill-built.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -# Starting from scratch... -git clean -xdf - -ci/publish-local.sh - -# Build Mill using SBT -target/bin/mill dev.assembly - -# Second build & run tests using Mill - - -out/dev/assembly/dest/out.jar -i all {clientserver,main,scalalib,scalajslib}.test -out/dev/assembly/dest/out.jar -i integration.test "mill.integration.forked.{AmmoniteTests,BetterFilesTests}" -out/dev/assembly/dest/out.jar -i dev.assembly diff --git a/ci/test-mill-dev.sh b/ci/test-mill-dev.sh new file mode 100755 index 00000000..cb83ef00 --- /dev/null +++ b/ci/test-mill-dev.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -eux + +# Starting from scratch... +git clean -xdf + +# Build Mill using SBT +mill dev.assembly + +# Second build & run tests using Mill +out/dev/assembly/dest/mill -i all {clientserver,main,scalalib,scalajslib}.test + diff --git a/ci/test-mill-release.sh b/ci/test-mill-release.sh index 838d1960..3c70410b 100755 --- a/ci/test-mill-release.sh +++ b/ci/test-mill-release.sh @@ -11,6 +11,4 @@ git clean -xdf # Second build & run tests using Mill -~/mill-release all {clientserver,main,scalalib,scalajslib}.test -~/mill-release integration.test "mill.integration.forked.{AcyclicTests,JawnTests,UpickleTests}" -~/mill-release dev.assembly +~/mill-release integration.test "mill.integration.forked.{AcyclicTests,UpickleTests}" diff --git a/ci/test-sbt-built.sh b/ci/test-sbt-built.sh deleted file mode 100755 index 3a85d345..00000000 --- a/ci/test-sbt-built.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -# Starting from scratch... -git clean -xdf - -sbt bin/test:assembly - -# Run tests using Mill built using SBT -target/bin/mill all {clientserver,main,scalalib,scalajslib}.test -target/bin/mill integration.test "mill.integration.local.{AcyclicTests,JawnTests,UpickleTests}" -target/bin/mill dev.assembly diff --git a/ci/test-sbt.sh b/ci/test-sbt.sh deleted file mode 100755 index a57b06e2..00000000 --- a/ci/test-sbt.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -# Starting from scratch... -git clean -xdf - -# First build & run tests using SBT -sbt core/test clientserver/test main/test scalalib/test scalajslib/test -sbt "integration/test-only -- mill.integration.local.{AmmoniteTests,BetterFilesTests}" -sbt bin/test:assembly @@ -20,17 +20,18 @@ own codebase. ## How to build and test -Run unit test suite: +Mill is built using Mill. To begin, first download & install Mill as described +in the documentation above. + +To run unit test suite: ```bash -sbt main/test mill main.test ``` Build a standalone executable jar: ```bash -sbt bin/test:assembly mill dev.assembly ``` @@ -41,27 +42,26 @@ e.g.: ```bash ./target/bin/mill core.compile -./out/dev/assembly/dest/out.jar core.compile -./out/dev/assembly/dest/out.jar main.test.compile -./out/dev/assembly/dest/out.jar main.test -./out/dev/assembly/dest/out.jar scalalib.assembly +./out/dev/assembly/dest/mill core.compile +./out/dev/assembly/dest/mill main.test.compile +./out/dev/assembly/dest/mill main.test +./out/dev/assembly/dest/mill scalalib.assembly ``` There is already a `watch` option that looks for changes on files, e.g.: ```bash -./target/bin/mill --watch core.compile -./out/dev/assembly/dest/out.jar --watch core.compile +./out/dev/assembly/dest/mill --watch core.compile ``` You can get Mill to show the JSON-structured output for a particular `Target` or `Command` using the `show` flag: ```bash -./out/dev/assembly/dest/out.jar show core.scalaVersion -./out/dev/assembly/dest/out.jar show core.compile -./out/dev/assembly/dest/out.jar show core.assemblyClasspath -./out/dev/assembly/dest/out.jar show main.test +./out/dev/assembly/dest/mill show core.scalaVersion +./out/dev/assembly/dest/mill show core.compile +./out/dev/assembly/dest/mill show core.assemblyClasspath +./out/dev/assembly/dest/mill show main.test ``` Output will be generated into a the `./out` folder. @@ -71,8 +71,6 @@ file in the repository root, you can skip the assembly process and directly run it via: ```bash -sbt "~bin/test:run main.test" -sbt "~bin/test:run" mill --watch dev.run . main.test mill --watch dev.run . ``` @@ -320,27 +318,6 @@ Each folder currently contains the following files: instead stored as separate binary files in `dest/` which are then referenced by `meta.json` via `PathRef`s -### Self Hosting - -You can use SBT to build a Mill executable, which itself is able to build more -Mill executables that can you can use to run Mill commands: - -```bash -git clean -xdf - -# Build Mill executable using SBT -sbt bin/test:assembly - -# Build Mill executable using the Mill executable generated by SBT -target/bin/mill dev.Assembly - -# Build Mill executable using the Mill executable generated by Mill itself -out/dev/assembly/dest/out.jar dev.assembly -``` - -Eventually, as Mill stabilizes, we will get rid of the SBT build entirely and -rely on previous versions of Mill to build itself. - ### Troubleshooting In case of troubles with caching and/or incremental compilation, you can always @@ -350,36 +327,6 @@ restart from scratch removing the `out` directory: rm -rf out/ ``` - -## Mill Goals and Roadmap - -The end goal of the Mill project is to develop a new Scala build tool to replace -SBT. Mill should satisfy most of the current use cases for SBT's functionality, -but hopefully needing much fewer features and much less complexity to do so. We -take inspiration from SBT, Make, Bazel, and many other existing build tools and -libraries. - -The immediate goal of Mill is to be feature-complete enough to: - -- Sustain its own development, without needing SBT -- Start porting over existing open-source Scala library builds from SBT to Mill - -https://github.com/lihaoyi/mill/issues/2 would kick off the process porting -`com.lihaoyi:acyclic`'s build to Mill, and from there we can flesh out the -missing features needed to port other builds: Scala.js support, Scala-Native -support, etc.. - -As the maintainer of many open-source libraries, all of the `com.lihaoyi` -libraries are fair game to be ported. - -Once a fair number of libraries have been ported, Mill should be in good enough -shape to release to the public, and we can try getting more people in the -community-at-large on board trying out Mill. This should hopefully happen by the -end of 2017. - -Until then, let's keep Mill private. If someone wants to poke their nose in and -see what's going on, we should expect them to contribute code! - ## Changelog ### 0.1.3 |