-
-
Notifications
You must be signed in to change notification settings - Fork 395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove Ammonite as a dependency, handle script running and bootstrapping ourselves #2377
Changes from 168 commits
ebf0cc8
eb243fd
658343a
c86bd71
592073a
6005df4
884fdae
69e2ebd
58dc796
7e9f902
2a1b48f
47090d7
5551be7
a20bd9b
40e502b
5a9c3d9
672cb4b
5c16771
1e95378
c4011eb
eba011b
c3c346a
d9bcb45
f63ed25
6f3c388
b020c83
8a66e46
e2fc232
c31a85f
7d204cb
d6edcc0
2f96862
d6b9835
1612eac
63cea8d
bf0d376
ece13bc
69cb89d
66eb2a1
61cade2
b45fdca
eeb088e
8ac8819
b9b02d5
805c7e0
a1a94d1
5ef72bf
06b18a4
6541de0
d4fc01a
333aec8
2201193
f9b0c59
3887098
f64ddd0
29430fa
770bcef
3e8e471
0e3e5eb
f5905a1
6936c72
83dc681
859e4fc
b08bb20
4fe5994
7e8eeb4
6968a66
84ed12c
7572c31
a57fb9c
705b4a3
71c4285
e1531b5
9dcbdb2
bcc01a8
a9ea03c
06c9f2d
62b38dc
2833f0f
a113a1f
3029b43
4ac1e98
4b251af
b2b64ca
a5ebff1
b1f566d
c0d0f41
404dbb7
96c3d03
5405c55
4b674d2
463de44
394ec47
f776f13
ee99d4c
9b85004
8ed5647
eb6110e
42390f3
2c2226b
30b4a0d
5270206
5782b00
94bd39f
088284d
ed6e243
449668e
4061e3f
ef3c6a5
4cc82f3
f657f7f
1132540
c6240d8
e683d18
dac3fff
cf45f30
3a23867
f79b497
ca518b9
11141e9
2a99bb1
914170c
f88bad7
a1caa6b
6ab2317
e093627
3fb75aa
1e6306a
2556cec
0bf0c17
49c0e26
98cd69f
9182df5
da1c914
4bf3d73
04686f6
feeb191
3d41495
226694a
7155332
ab912c0
70e3bda
51c6247
4bde7a3
9cb76f2
86d97d8
4783286
26b954b
c05b727
9683392
ed7dfdf
64fee00
b309547
c4be342
db2fcc4
386ad88
0a4a361
a62c6b7
e4426f5
2fbfc53
b122666
7b3d4e3
b6cde25
41a6c95
85c96fe
84300f7
6c0048d
95737c5
809c7c0
c8f4e80
0074d25
91b3638
1d190fc
7839169
1a43868
b09ece3
fc31c48
2e05d5b
122ffc3
58e4dd5
b57ae36
038e3f0
319d163
077b8b9
7e1437a
01f5ac9
34693da
ae05f1e
7124998
ea7314e
24e401b
e91b41e
3210acc
e6308fc
c49dbaa
4ad7200
d9f3ebd
0ad2efb
1bc24f4
41921ef
17f4907
101718f
f24b4ac
d070d7e
63affbf
97f896a
a3976ee
76abe80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import mill._, scalalib._ | ||
|
||
object foo extends BuildModule with ScalaModule { | ||
def scalaVersion = "2.13.2" | ||
def ivyDeps = Agg(ivy"com.lihaoyi::scalatags:0.8.2") | ||
} | ||
|
||
// This is a basic Mill build for a single `ScalaModule`, with a single | ||
// third-party dependency. As a single-module project, it `extends BuildModule` | ||
// to mark `object foo` as the top-level module in the build. This lets us | ||
// directly perform operations `./mill compile` or `./mill run` without needing | ||
// to prefix it as `foo.compile` or `foo.run`. | ||
// | ||
// You can run `assembly` to generate a standalone executable jar, which then | ||
// can be run from the command line or deployed to be run elsewhere. | ||
|
||
/* Example Usage | ||
> ./mill compile | ||
compiling 1 Scala source | ||
> ./mill run | ||
Foo.value: <h1>hello</h1> | ||
> ./mill show assembly | ||
out/assembly.dest/out.jar | ||
> ./out/assembly.dest/out.jar | ||
Foo.value: <h1>hello</h1> | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package foo | ||
import scalatags.Text.all._ | ||
object Foo { | ||
val value = h1("hello") | ||
def main(args: Array[String]): Unit = { | ||
println("Foo.value: " + Foo.value) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package bar | ||
import scalatags.Text.all._ | ||
object Bar { | ||
val value = p("world") | ||
|
||
def main(args: Array[String]): Unit = { | ||
println("Bar.value: " + bar.Bar.value) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import mill._, scalalib._ | ||
|
||
trait MyModule extends ScalaModule{ | ||
def scalaVersion = "2.13.2" | ||
def ivyDeps = Agg(ivy"com.lihaoyi::scalatags:0.8.2") | ||
} | ||
|
||
object foo extends MyModule { | ||
def moduleDeps = Seq(bar) | ||
} | ||
|
||
object bar extends MyModule | ||
|
||
// A simple Mill build with two modules, `foo` and `bar`. We don't mark either | ||
// module as top-level using `extends BuildModule`, so running tasks needs to | ||
// use the module name as the prefix e.g. `foo.run` or `bar.run` | ||
// | ||
// Note that we split out the configuration common to both modules into a | ||
// separate `trait MyModule`. This lets us avoid the need to copy-paste common | ||
// settings, while still letting us define any per-module configuration | ||
// specific to a particular module e.g. overriding `moduleDeps` to make `foo` | ||
// depend on `bar` | ||
|
||
/* Example Usage | ||
> ./mill resolve __.run | ||
foo.run | ||
bar.run | ||
> ./mill foo.run | ||
Foo.value: <h1>hello</h1> | ||
Bar.value: <p>world</p> | ||
> ./mill bar.run | ||
Bar.value: <p>world</p> | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package foo | ||
import scalatags.Text.all._ | ||
object Foo { | ||
val value = h1("hello") | ||
|
||
def main(args: Array[String]): Unit = { | ||
println("Foo.value: " + Foo.value) | ||
println("Bar.value: " + bar.Bar.value) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import mill._, scalalib._ | ||
|
||
trait MyModule extends ScalaModule{ | ||
def scalaVersion = "2.13.2" | ||
def ivyDeps = Agg(ivy"com.lihaoyi::scalatags:0.8.2") | ||
} | ||
|
||
object wrapper extends Module{ | ||
object foo extends MyModule { | ||
def moduleDeps = Seq(bar) | ||
} | ||
|
||
object bar extends MyModule | ||
} | ||
|
||
object qux extends MyModule { | ||
def moduleDeps = Seq(wrapper.bar, wrapper.foo) | ||
} | ||
|
||
// Modules can be nested arbitrarily deeply within each other. The outer module | ||
// can be the same kind of module as the ones within, or it can be a plain | ||
// `Module` if we just need a wrapper to put the modules in without any tasks | ||
// defined on the wrapper. | ||
// | ||
// Running tasks on the nested modules requires the full module path | ||
// `wrapper.foo.run` | ||
|
||
/* Example Usage | ||
> ./mill resolve __.run | ||
wrapper.foo.run | ||
wrapper.bar.run | ||
qux.run | ||
> ./mill qux.run | ||
Foo.value: <h1>hello</h1> | ||
Bar.value: <p>world</p> | ||
Qux.value: <p>today</p> | ||
> ./mill wrapper.foo.run | ||
Foo.value: <h1>hello</h1> | ||
Bar.value: <p>world</p> | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package qux | ||
import scalatags.Text.all._ | ||
object Qux { | ||
val value = p("today") | ||
|
||
def main(args: Array[String]): Unit = { | ||
println("Foo.value: " + foo.Foo.value) | ||
println("Bar.value: " + bar.Bar.value) | ||
println("Qux.value: " + qux.Qux.value) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package bar | ||
import scalatags.Text.all._ | ||
object Bar { | ||
val value = p("world") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package foo | ||
import scalatags.Text.all._ | ||
object Foo { | ||
val value = h1("hello") | ||
|
||
def main(args: Array[String]): Unit = { | ||
println("Foo.value: " + Foo.value) | ||
println("Bar.value: " + bar.Bar.value) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import mill._, scalalib._ | ||
|
||
object foo extends ScalaModule { | ||
def scalaVersion = "2.13.2" | ||
object test extends Tests { | ||
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.7.11") | ||
def testFramework = "utest.runner.Framework" | ||
} | ||
} | ||
|
||
// This build defines a single module with a test suite, configured to use | ||
// "uTest" as the testing framework. Test suites are themselves `ScalaModule`s, | ||
// and have all the normal tasks like `foo.test.compile` available to run, but | ||
// with an additional `.test` task that runs the tests. You can also run the | ||
// test suite directly, in which case it will run the `.test` task as the | ||
// default task for that module | ||
|
||
/* Example Usage | ||
> ./mill foo.compile | ||
compiling 1 Scala source | ||
> ./mill foo.test.compile | ||
compiling 1 Scala source | ||
> ./mill foo.test.test | ||
+ foo.FooTests.hello | ||
> ./mill foo.test | ||
+ foo.FooTests.hello | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
package foo | ||
object Example { | ||
object Foo { | ||
def main(args: Array[String]): Unit = { | ||
println(hello()) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import mill._, scalalib._, publish._ | ||
|
||
object foo extends ScalaModule with PublishModule { | ||
def scalaVersion = "2.13.10" | ||
def publishVersion = "0.0.1" | ||
|
||
def pomSettings = PomSettings( | ||
description = "Hello", | ||
organization = "com.lihaoyi", | ||
url = "https://github.com/lihaoyi/example", | ||
licenses = Seq(License.MIT), | ||
versionControl = VersionControl.github("lihaoyi", "example"), | ||
developers = Seq( | ||
Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi") | ||
) | ||
) | ||
} | ||
|
||
// This is an example `ScalaModule` with added publishing capabilities via | ||
// `PublishModule`. This requires that you define an additional | ||
// `publishVersion` and `pomSettings` with the relevant metadata, and provides | ||
// the `.publishLocal` and `publishSigned` tasks for publishing locally to the | ||
// machine or to the central maven repository | ||
|
||
/* Example Usage | ||
> ./mill foo.publishLocal | ||
Publishing Artifact(com.lihaoyi,foo_2.13,0.0.1) | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
package foo | ||
object Example { | ||
object Foo { | ||
def main(args: Array[String]): Unit = { | ||
println("Hello World") | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package bar | ||
object Bar { | ||
val value = "bar-value" | ||
|
||
def main(args: Array[String]): Unit = { | ||
println("Bar.value: " + bar.Bar.value) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import mill._, scalalib._ | ||
|
||
val scalaVersions = Seq("2.12.17", "2.13.10", "3.2.2") | ||
|
||
object foo extends Cross[FooModule](scalaVersions:_*) | ||
class FooModule(val crossScalaVersion: String) extends CrossScalaModule{ | ||
def moduleDeps = Seq(bar()) | ||
} | ||
|
||
object bar extends Cross[BarModule](scalaVersions:_*) | ||
class BarModule(val crossScalaVersion: String) extends CrossScalaModule | ||
|
||
// This is an example of cross-building a module across multiple Scala | ||
// versions. Each module is replaced by a `Cross` module, which is given a list | ||
// of strings you want the cross-module to be replicated for. You can then | ||
// specify the cross-modules with square brackets when you want to run tasks on | ||
// them. | ||
// | ||
// `CrossScalaModule`s support both shared sources within `src/` as well as | ||
// version specific sources in `src-x/`, `src-x.y/`, or `src-x.y.z/` that | ||
// apply to the cross-module with that version prefix. | ||
// | ||
// `CrossScalaModule` can depend on each other using `moduleDeps`, but require | ||
// the `()` suffix in `moduleDeps` to select the appropriate instance of the | ||
// cross-module to depend on. | ||
|
||
/* Example Usage | ||
> ./mill resolve __.run | ||
foo[2.12.17].run | ||
foo[2.13.10].run | ||
bar[2.12.17].run | ||
bar[2.13.10].run | ||
> ./mill foo[2.12.17].run | ||
Foo.value: Hello World Scala library version 2.12.17 | ||
Bar.value: bar-value | ||
Specific code for Scala 2.x | ||
Specific code for Scala 2.12.x | ||
> ./mill foo[2.13.10].run | ||
Foo.value: Hello World Scala library version 2.13.10 | ||
Bar.value: bar-value | ||
Specific code for Scala 2.x | ||
Specific code for Scala 2.13.x | ||
> ./mill bar[3.2.2].run | ||
Bar.value: bar-value | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package foo | ||
object MinorVersionSpecific { | ||
def text(): String = "Specific code for Scala 2.12.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package foo | ||
object MinorVersionSpecific { | ||
def text(): String = "Specific code for Scala 2.13.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package foo | ||
object MajorVersionSpecific { | ||
def text(): String = "Specific code for Scala 2.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package foo | ||
object MinorVersionSpecific { | ||
def text(): String = "Specific code for Scala 3.2.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package foo | ||
object MajorVersionSpecific { | ||
def text(): String = "Specific code for Scala 3.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package foo | ||
object Foo { | ||
def main(args: Array[String]): Unit = { | ||
println("Foo.value: " + foo.Foo.value) | ||
println("Bar.value: " + bar.Bar.value) | ||
println(MajorVersionSpecific.text()) | ||
println(MinorVersionSpecific.text()) | ||
} | ||
val value = "Hello World " + scala.util.Properties.versionMsg | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package bar; | ||
public class Bar{ | ||
public static final int value = 271828; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import mill._, scalalib._ | ||
|
||
object foo extends JavaModule{ | ||
def moduleDeps = Seq(bar) | ||
} | ||
|
||
object bar extends JavaModule | ||
|
||
// Mill also supports `JavaModule`s, which can only contain pure Java code | ||
// without any Scala. These have the same set of tasks as `ScalaModules`: | ||
// `compile`, `run`, etc., and can similarly depend on each other. | ||
|
||
/* Example Usage | ||
> ./mill resolve __.run | ||
foo.run | ||
bar.run | ||
> ./mill foo.compile | ||
compiling 1 Java source | ||
> ./mill foo.run | ||
Foo.value: 31337 | ||
Bar.value: 271828 | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package foo; | ||
public class Foo{ | ||
public static final int value = 31337; | ||
public static void main(String[] args){ | ||
System.out.println("Foo.value: " + foo.Foo.value); | ||
System.out.println("Bar.value: " + bar.Bar.value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package bar | ||
object BarVersionSpecific { | ||
def text(): String = "Specific code for Scala 2.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package bar | ||
object BarVersionSpecific { | ||
def text(): String = "Specific code for Scala 3.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package bar | ||
import scalatags.Text.all._ | ||
object Bar { | ||
val value = p("world", " ", BarVersionSpecific.text()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package bar | ||
import utest._ | ||
object BarVersionSpecificTests extends TestSuite { | ||
def tests = Tests { | ||
test("test") { | ||
assert(BarVersionspecific.text().contains("2.x")) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package bar | ||
import utest._ | ||
object BarVersionSpecificTests extends TestSuite { | ||
def tests = Tests { | ||
test("test") { | ||
assert(BarVersionspecific.text().contains("3.x")) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package bar | ||
import utest._ | ||
object BarTests extends TestSuite { | ||
def tests = Tests { | ||
test("test") { | ||
val result = Bar.value.toString | ||
val matcher = "<p>world Specific code for Scala [23].x</p>".r | ||
assert(matcher.matches(result)) | ||
result | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import mill._, scalalib._, publish._ | ||
|
||
trait MyModule extends PublishModule { | ||
def publishVersion = "0.0.1" | ||
|
||
def pomSettings = PomSettings( | ||
description = "Hello", | ||
organization = "com.lihaoyi", | ||
url = "https://github.com/lihaoyi/example", | ||
licenses = Seq(License.MIT), | ||
versionControl = VersionControl.github("lihaoyi", "example"), | ||
developers = Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi")) | ||
) | ||
} | ||
|
||
trait MyScalaModule extends ScalaModule with MyModule { | ||
def ivyDeps = Agg(ivy"com.lihaoyi::scalatags:0.12.0") | ||
object test extends Tests { | ||
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.7.11") | ||
def testFramework = "utest.runner.Framework" | ||
} | ||
} | ||
|
||
val scalaVersions = Seq("2.13.10", "3.2.2") | ||
|
||
object foo extends Cross[FooModule](scalaVersions:_*) | ||
class FooModule(val crossScalaVersion: String) extends MyScalaModule with CrossScalaModule{ | ||
def moduleDeps = Seq(bar(), qux) | ||
} | ||
|
||
object bar extends Cross[BarModule](scalaVersions:_*) | ||
class BarModule(val crossScalaVersion: String) extends MyScalaModule with CrossScalaModule{ | ||
def moduleDeps = Seq(qux) | ||
} | ||
|
||
object qux extends JavaModule with MyModule | ||
|
||
// A semi-realistic build setup, combining all the individual Mill concepts: | ||
// | ||
// - Two `CrossScalaModules` compiled against two Scala versions, that depend on | ||
// each other as well as on a `JavaModule` | ||
// | ||
// - With unit testing and publishing set up | ||
// | ||
// - With version-specific sources | ||
// | ||
// Note that for multi-module builds like this, using queries like `__.test` | ||
// or `__.publishLocal` to run tasks on multiple targets at once can be very | ||
// convenient. Also note that `ScalaModule`s can depend on `JavaModule`s, and | ||
// when multiple inter-dependent modules are published they automatically will | ||
// include the inter-module dependencies in the publish metadata. | ||
// | ||
// Also note how you can use `trait`s to bundle together common combinations of | ||
// modules: `MyScalaModule` not only defines a `ScalaModule` with some common | ||
// configuration, but it also defines a `object test` module within it with its | ||
// own configuration. This is a very useful technique for managing the often | ||
// repetitive module structure in a typical project | ||
|
||
/* Example Usage | ||
> ./mill resolve __.run | ||
bar[2.13.10].run | ||
bar[2.13.10].test.run | ||
bar[3.2.2].run | ||
bar[3.2.2].test.run | ||
foo[2.13.10].run | ||
foo[2.13.10].test.run | ||
foo[3.2.2].run | ||
foo[3.2.2].test.run | ||
qux.run | ||
> ./mill foo[2.13.10].run | ||
Foo.value: <h1>hello Scala 2.x</h1> | ||
Bar.value: <p>world Specific code for Scala 2.x</p> | ||
Qux.value: 31337 | ||
> ./mill bar[3.2.2].test | ||
bar.BarTests.test | ||
<p>world Specific code for Scala 3.x</p> | ||
> ./mill qux.run | ||
Qux.value: 31337 | ||
> ./mill __.compile | ||
> ./mill __.test | ||
+ bar.BarTests.test | ||
<p>world Specific code for Scala 2.x</p> | ||
+ bar.BarTests.test | ||
<p>world Specific code for Scala 3.x</p> | ||
+ foo.FooTests.test | ||
<h1>hello Scala 2.x</h1> | ||
+ foo.FooTests.test | ||
<h1>hello Scala 3.x</h1> | ||
> ./mill __.publishLocal | ||
Publishing Artifact(com.lihaoyi,foo_2.13,0.0.1) | ||
Publishing Artifact(com.lihaoyi,bar_2.13,0.0.1) | ||
Publishing Artifact(com.lihaoyi,foo_3,0.0.1) | ||
Publishing Artifact(com.lihaoyi,bar_3,0.0.1) | ||
Publishing Artifact(com.lihaoyi,qux,0.0.1) | ||
> ./mill show foo[2.13.10].assembly | ||
out/foo/2.13.10/assembly.dest/out.jar | ||
> ./out/foo/2.13.10/assembly.dest/out.jar | ||
Foo.value: <h1>hello Scala 2.x</h1> | ||
Bar.value: <p>world Specific code for Scala 2.x</p> | ||
Qux.value: 31337 | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package foo | ||
object FooVersionSpecific{ | ||
def value = "hello Scala 2.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package foo | ||
object FooVersionSpecific{ | ||
def value = "hello Scala 3.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package foo | ||
import scalatags.Text.all._ | ||
object Foo { | ||
val value = h1(FooVersionSpecific.value) | ||
def main(args: Array[String]): Unit = { | ||
println("Foo.value: " + Foo.value) | ||
println("Bar.value: " + bar.Bar.value) | ||
println("Qux.value: " + qux.Qux.value) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package foo | ||
import utest._ | ||
object FooTests extends TestSuite { | ||
def tests = Tests { | ||
test("test") { | ||
val result = Foo.value.toString | ||
val matcher = "<h1>hello Scala [23].x</h1>".r | ||
assert(matcher.matches(result)) | ||
result | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package qux; | ||
public class Qux{ | ||
public static final int value = 31337; | ||
public static void main(String[] args){ | ||
System.out.println("Qux.value: " + Qux.value); | ||
} | ||
} |
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package bar | ||
object MinorVersionSpecific { | ||
def text(): String = "Specific code for Scala 2.12.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package bar | ||
object MinorVersionSpecific { | ||
def text(): String = "Specific code for Scala 2.13.x" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package bar | ||
object Bar { | ||
def main(args: Array[String]): Unit = { | ||
println("Bar.value: " + bar.Bar.value) | ||
println(MinorVersionSpecific.text()) | ||
} | ||
val value = "Hello World " + scala.util.Properties.versionMsg | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import mill._, scalalib._ | ||
|
||
object foo extends SbtModule { | ||
def scalaVersion = "2.13.2" | ||
object test extends Tests { | ||
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.7.11") | ||
def testFramework = "utest.runner.Framework" | ||
} | ||
} | ||
|
||
|
||
object bar extends Cross[BarModule]("2.12.17", "2.13.10") | ||
class BarModule(val crossScalaVersion: String) extends CrossSbtModule{ | ||
object test extends Tests { | ||
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.7.11") | ||
|
||
def testFramework = "utest.runner.Framework" | ||
} | ||
} | ||
|
||
|
||
// `SbtModule`/`CrossSbtModule` are variants of `ScalaModule`/`CrossScalaModule` | ||
// that use the more verbose folder layout of SBT, Maven, and other tools: | ||
// | ||
// - `foo/src/main/scala` | ||
// - `foo/src/main/scala-2.12` | ||
// - `foo/src/main/scala-2.13` | ||
// - `foo/src/test/scala` | ||
// | ||
// Rather than Mill's | ||
// | ||
// - `foo/src` | ||
// - `foo/src-2.12` | ||
// - `foo/src-2.13` | ||
// - `foo/test/src` | ||
// | ||
// This is especially useful during migrations, where a particular module may | ||
// be built using both SBT and Mill at the same time | ||
|
||
/* Example Usage | ||
> ./mill foo.compile | ||
compiling 1 Scala source | ||
> ./mill foo.test.compile | ||
compiling 1 Scala source | ||
> ./mill foo.test.test | ||
+ foo.FooTests.hello | ||
> ./mill foo.test | ||
+ foo.FooTests.hello | ||
> ./mill bar[2.13.10].run | ||
Hello World Scala library version 2.13.10 | ||
> ./mill bar[2.12.17].run | ||
Hello World Scala library version 2.12.17 | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package foo | ||
object Foo { | ||
def main(args: Array[String]): Unit = { | ||
println(hello()) | ||
} | ||
def hello(): String = "Hello World" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package foo | ||
import utest._ | ||
object FooTests extends TestSuite { | ||
def tests = Tests { | ||
test("hello") { | ||
val result = Foo.hello() | ||
assert(result == "Hello World") | ||
result | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import mill._, scalalib._ | ||
|
||
val moduleNames = interp.watchValue(os.list(millSourcePath / "modules").map(_.last)) | ||
|
||
object modules extends Cross[FolderModule](moduleNames:_*) | ||
class FolderModule(name: String) extends ScalaModule{ | ||
def millSourcePath = super.millSourcePath / name | ||
def scalaVersion = "2.13.2" | ||
} | ||
|
||
// It is sometimes necessary for the instances of a cross-module to vary based | ||
// on some kind of runtime information: perhaps the list of modules is stored | ||
// in some config file, or is inferred based on the folders present on the | ||
// filesystem. | ||
// | ||
// In those cases, you can write arbitrary code to populate the cross-module | ||
// cases, as long as you wrap the value in a `interp.watchValue`. This ensures | ||
// that Mill is aware that the module structure depends on that value, and will | ||
// re-compute the value and re-create the module structure if the value changes. | ||
|
||
/* Example Usage | ||
> ./mill resolve modules[_] | ||
modules[bar] | ||
modules[foo] | ||
modules[qux] | ||
> ./mill modules[bar].run | ||
Hello World Bar | ||
> ./mill modules[new].run | ||
error: Cannot resolve modules[new] | ||
> cp -r modules/bar modules/new | ||
> sed -i 's/Bar/New/g' modules/new/src/Example.scala | ||
> ./mill resolve modules[_] | ||
modules[bar] | ||
modules[foo] | ||
modules[qux] | ||
modules[new] | ||
> ./mill modules[new].run | ||
Hello World New | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package bar | ||
object Example { | ||
def main(args: Array[String]): Unit = { | ||
println("Hello World Bar") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package foo | ||
object Example { | ||
def main(args: Array[String]): Unit = { | ||
println("Hello World Foo") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package qux | ||
object Example { | ||
def main(args: Array[String]): Unit = { | ||
println("Hello World Qux") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import mill._, scalalib._ | ||
import scalatags.Text.all._ | ||
|
||
object foo extends ScalaModule { | ||
def scalaVersion = "2.13.2" | ||
|
||
def forkEnv = Map( | ||
"snippet" -> frag(h1("hello"), p("world"), p(constant.Constant.scalatagsVersion)).render | ||
) | ||
} | ||
|
||
// | ||
|
||
/* Example Usage | ||
> ./mill foo.run | ||
<h1>hello</h1><p>world</p><p>0.8.2</p> | ||
> sed -i 's/0.8.2/0.12.0/g' mill-build/build.sc | ||
> ./mill foo.run | ||
<h1>hello</h1><p>world</p><p>0.12.0</p> | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package foo | ||
|
||
object Example { | ||
def main(args: Array[String]): Unit = { | ||
println(sys.env("snippet") + "?") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import mill._, scalalib._ | ||
|
||
object millbuild extends runner.MillBuildModule{ | ||
def ivyDeps = Agg(ivy"com.lihaoyi::scalatags:0.8.2") | ||
|
||
def generatedSources = T { | ||
os.write( | ||
T.dest / "Constant.scala", | ||
s"""package constant | ||
|object Constant{ | ||
| def scalatagsVersion = "0.8.2" | ||
|} | ||
|""".stripMargin | ||
) | ||
super.generatedSources() ++ Seq(PathRef(T.dest / "Constant.scala")) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
def myScalaVersion = "2.13.2" | ||
|
||
|
||
println(doesntExist) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import $file.{bar, qux} | ||
import mill._ | ||
import mill.scalalib._ | ||
object foo extends ScalaModule { | ||
def scalaVersion = bar.myScalaVersion | ||
} | ||
|
||
foo.noSuchMethod |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
def myMsg = "<h1>world</h1>" | ||
|
||
def myOtherMsg = myMsg.substring("0") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
package mill.integration | ||
|
||
import mill.util.Util | ||
import utest._ | ||
|
||
object CompileErrorTests extends IntegrationTestSuite { | ||
def captureOutErr = true | ||
val tests = Tests { | ||
initWorkspace() | ||
|
||
test { | ||
val res = evalStdout("foo.scalaVersion") | ||
|
||
assert(res.isSuccess == false) | ||
assert(res.err.contains("""bar.sc:4:9: not found: value doesntExist""")) | ||
assert(res.err.contains("""println(doesntExist)""")) | ||
assert(res.err.contains("""qux.sc:3:34: type mismatch;""")) | ||
assert(res.err.contains("""build.sc:8:5: value noSuchMethod is not a member of object build.this.foo""")) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package mill.integration | ||
|
||
import utest._ | ||
|
||
import scala.util.matching.Regex | ||
|
||
object InvalidMetaModuleTests extends IntegrationTestSuite { | ||
val tests = Tests { | ||
val workspaceRoot = initWorkspace() | ||
|
||
|
||
test("success"){ | ||
val res = evalStdout("resolve", "_") | ||
assert(res.isSuccess == false) | ||
assert(res.err.contains("Top-level module in mill-build/build.sc must be of class mill.runner.MillBuildModule")) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
|
||
package mill.integration | ||
|
||
import utest._ | ||
|
||
object MissingBuildFileTests extends IntegrationTestSuite { | ||
val tests = Tests { | ||
initWorkspace() | ||
|
||
test { | ||
val res = evalStdout("resolve", "_") | ||
assert(!res.isSuccess) | ||
assert(res.err.contains("build.sc file not found. Are you in a Mill project folder")) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
object foo extends BuildModule | ||
object bar extends BuildModule |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package mill.integration | ||
|
||
import utest._ | ||
|
||
import scala.util.matching.Regex | ||
|
||
object MultipleTopLevelModulesTests extends IntegrationTestSuite { | ||
val tests = Tests { | ||
val workspaceRoot = initWorkspace() | ||
|
||
test("success"){ | ||
val res = evalStdout("resolve", "_") | ||
assert(!res.isSuccess) | ||
assert(res.err.contains("Only one BaseModule can be defined in a build, not 2: millbuild.build$bar$,millbuild.build$foo$")) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
def myScalaVersion = "2.13.2" | ||
|
||
|
||
println(doesntExist}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import $file.{bar, qux} | ||
import mill._ | ||
import mill.scalalib._ | ||
object foo extends ScalaModule { | ||
def scalaVersion = bar.myScalaVersion | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
def myMsg = "<h1>world</h1>" | ||
|
||
System.out.println(doesntExist |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
package mill.integration | ||
|
||
import mill.util.Util | ||
import utest._ | ||
|
||
object ParseErrorTests extends IntegrationTestSuite { | ||
val tests = Tests { | ||
initWorkspace() | ||
|
||
test { | ||
val res = evalStdout("foo.scalaVersion") | ||
|
||
assert(res.isSuccess == false) | ||
|
||
assert(res.err.contains("""bar.sc:4:20 expected ")"""")) | ||
assert(res.err.contains("""println(doesntExist})""")) | ||
assert(res.err.contains("""qux.sc:3:31 expected ")"""")) | ||
assert(res.err.contains("""System.out.println(doesntExist""")) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import mill._, scalalib._ | ||
|
||
val moduleNames = interp.watchValue(os.list(millSourcePath / "modules").map(_.last)) | ||
|
||
object modules extends Cross[FolderModule](moduleNames:_*) | ||
class FolderModule(name: String) extends ScalaModule{ | ||
def millSourcePath = super.millSourcePath / name | ||
def scalaVersion = "2.13.2" | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package bar | ||
object Example { | ||
def main(args: Array[String]): Unit = { | ||
println("Hello World Bar") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package foo | ||
object Example { | ||
def main(args: Array[String]): Unit = { | ||
println("Hello World Foo") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package qux | ||
object Example { | ||
def main(args: Array[String]): Unit = { | ||
println("Hello World Qux") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package mill.integration | ||
|
||
import utest._ | ||
|
||
|
||
// Make sure that values watched by `interp.watchValue` properly cause the base | ||
// module to be re-evaluated, so that changes in the module tree that depend on | ||
// those changes can properly take place. This is most commonly due to defining | ||
// the cases of a cross-module to depend on the files present in a folder, in | ||
// which case the `interp.watchValue(os.list(...))` call will need to trigger | ||
// re-evaluation of the module tree when it changes. | ||
object DynamicCrossModuleTests extends IntegrationTestSuite { | ||
val tests = Tests { | ||
val wsRoot = initWorkspace() | ||
test("test") - { | ||
|
||
|
||
val res = evalStdout("resolve", "modules._") | ||
assert(res.isSuccess == true) | ||
assert( | ||
res.out.linesIterator.toSet == | ||
Set("modules[bar]", "modules[foo]", "modules[qux]") | ||
) | ||
|
||
val res2 = evalStdout("modules[bar].run") | ||
assert(res2.isSuccess == true) | ||
if (integrationTestMode != "local") { | ||
assert(res2.out.contains("Hello World Bar")) | ||
} | ||
|
||
val res3 = evalStdout("modules[new].run") | ||
assert(res3.isSuccess == false) | ||
assert(res3.err.contains("Cannot resolve modules[new]")) | ||
|
||
val res4 = evalStdout("modules[newer].run") | ||
assert(res4.isSuccess == false) | ||
assert(res4.err.contains("Cannot resolve modules[newer]")) | ||
|
||
os.copy(wsRoot / "modules" / "bar", wsRoot / "modules" / "new") | ||
mangleFile( | ||
wsRoot / "modules" / "new" / "src" / "Example.scala", | ||
_.replace("Bar", "New") | ||
) | ||
os.copy(wsRoot / "modules" / "bar", wsRoot / "modules" / "newer") | ||
mangleFile( | ||
wsRoot / "modules" / "newer" / "src" / "Example.scala", | ||
_.replace("Bar", "Newer") | ||
) | ||
|
||
val res5 = evalStdout("modules[new].run") | ||
assert(res5.isSuccess == true) | ||
if (integrationTestMode != "local") { | ||
assert(res5.out.contains("Hello World New")) | ||
} | ||
|
||
val res6 = evalStdout("modules[newer].run") | ||
assert(res6.isSuccess == true) | ||
if (integrationTestMode != "local") { | ||
assert(res6.out.contains("Hello World Newer")) | ||
} | ||
|
||
val res7 = evalStdout("resolve", "modules._") | ||
assert(res7.isSuccess == true) | ||
assert( | ||
res7.out.linesIterator.toSet == | ||
Set("modules[bar]", "modules[foo]", "modules[qux]", "modules[new]", "modules[newer]") | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import mill._, scalalib._ | ||
import scalatags.Text.all._ | ||
|
||
object foo extends ScalaModule { | ||
def scalaVersion = "2.13.2" | ||
|
||
def forkEnv = Map( | ||
"snippet" -> frag(h1("hello"), p("world"), p(constant.Constant.scalatagsVersion)).render | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package foo | ||
|
||
object Example { | ||
def main(args: Array[String]): Unit = { | ||
println(sys.env("snippet") + "!") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import mill._, scalalib._ | ||
|
||
object millbuild extends runner.MillBuildModule{ | ||
def ivyDeps = Agg(ivy"com.lihaoyi::scalatags:${constant.MetaConstant.scalatagsVersion}") | ||
|
||
def generatedSources = T { | ||
os.write( | ||
T.dest / "Constant.scala", | ||
s"""package constant | ||
|object Constant{ | ||
| def scalatagsVersion = "${constant.MetaConstant.scalatagsVersion}" | ||
|} | ||
|""".stripMargin | ||
) | ||
super.generatedSources() ++ Seq(PathRef(T.dest / "Constant.scala")) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// build.sc | ||
import mill._, scalalib._ | ||
|
||
object millbuild extends runner.MillBuildModule { | ||
def generatedSources = T{ | ||
os.write( | ||
T.dest / "MetaConstant.scala", | ||
"""package constant | ||
|object MetaConstant{ | ||
| def scalatagsVersion = "0.8.2" | ||
|} | ||
|""".stripMargin | ||
) | ||
super.generatedSources() ++ Seq(PathRef(T.dest / "MetaConstant.scala")) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,359 @@ | ||
package mill.integration | ||
|
||
import mill.runner.RunnerState | ||
import utest._ | ||
|
||
import scala.util.matching.Regex | ||
|
||
// Cause various kinds of changes - valid, parse errors, compile errors, | ||
// runtime errors - in various levels of build.sc meta-builds, ensuring | ||
// that the proper messages are reported, proper build classloaders are | ||
// re-used or invalidated, and the proper files end up getting watched | ||
// in all cases. | ||
object MultiLevelBuildTests extends IntegrationTestSuite { | ||
val tests = Tests { | ||
val wsRoot = initWorkspace() | ||
|
||
def runAssertSuccess(expected: String) = { | ||
val res = evalStdout("foo.run") | ||
assert(res.isSuccess == true) | ||
// Don't check foo.run stdout in local mode, because it the subprocess | ||
// println is not properly captured by the test harness | ||
if (integrationTestMode != "local") assert(res.out.contains(expected)) | ||
} | ||
|
||
val fooPaths = Seq( | ||
wsRoot / "foo" / "compile-resources", | ||
wsRoot / "foo" / "resources", | ||
wsRoot / "foo" / "src" | ||
) | ||
val buildPaths = Seq( | ||
wsRoot / "build.sc", | ||
wsRoot / "mill-build" / "compile-resources", | ||
wsRoot / "mill-build" / "resources", | ||
wsRoot / "mill-build" / "src" | ||
) | ||
val buildPaths2 = Seq( | ||
wsRoot / "mill-build" / "build.sc", | ||
wsRoot / "mill-build" / "mill-build" / "compile-resources", | ||
wsRoot / "mill-build" / "mill-build" / "resources", | ||
wsRoot / "mill-build" / "mill-build" / "src", | ||
) | ||
val buildPaths3 = Seq( | ||
wsRoot / "mill-build" / "mill-build" / "build.sc", | ||
wsRoot / "mill-build" / "mill-build" / "mill-build" / "compile-resources", | ||
wsRoot / "mill-build" / "mill-build" / "mill-build" / "resources", | ||
wsRoot / "mill-build" / "mill-build" / "mill-build" / "src" | ||
) | ||
|
||
def loadFrames(n: Int) = { | ||
for(depth <- Range(0, n)) | ||
yield { | ||
val path = wsRoot / "out" / Seq.fill(depth)("mill-build") / "mill-runner-state.json" | ||
upickle.default.read[RunnerState.Frame.Logged](os.read(path)) -> path | ||
} | ||
} | ||
|
||
/** | ||
* Verify that each level of the multi-level build ends upcausing the | ||
* appropriate files to get watched | ||
*/ | ||
def checkWatchedFiles(expected0: Seq[os.Path]*) = { | ||
for((expectedWatched0, (frame, path)) <- expected0.zip(loadFrames(expected0.length))){ | ||
val frameWatched = frame.evalWatched.map(_.path).sorted | ||
val expectedWatched = expectedWatched0.sorted | ||
assert(frameWatched == expectedWatched) | ||
} | ||
} | ||
|
||
def evalCheckErr(expected: String*) = { | ||
val res = evalStdout("foo.run") | ||
assert(res.isSuccess == false) | ||
// Prepend a "\n" to allow callsites to use "\n" to test for start of | ||
// line, even though the first line doesn't have a "\n" at the start | ||
val err = "\n" + res.err | ||
for (e <- expected) { | ||
assert(err.contains(e)) | ||
} | ||
} | ||
|
||
var savedClassLoaderIds = Seq.empty[Option[Int]] | ||
|
||
/** | ||
* Check whether the classloaders of the nested meta-builds are changing as | ||
* expected. `true` means a new classloader was created, `false` means | ||
* the previous classloader was re-used, `null` means there is no | ||
* classloader at that level | ||
*/ | ||
def checkChangedClassloaders(expectedChanged0: java.lang.Boolean*) = { | ||
val currentClassLoaderIds = | ||
for((frame, path) <- loadFrames(expectedChanged0.length)) | ||
yield frame.classLoaderIdentity | ||
|
||
val changed = currentClassLoaderIds | ||
.zipAll(savedClassLoaderIds, None, None) | ||
.map{case (cur, old) => | ||
if (cur.isEmpty) null | ||
else cur != old | ||
} | ||
|
||
val expectedChanged = | ||
if (integrationTestMode != "fork") expectedChanged0 | ||
else expectedChanged0.map{case java.lang.Boolean.FALSE => true case n => n} | ||
|
||
assert(changed == expectedChanged) | ||
|
||
savedClassLoaderIds = currentClassLoaderIds | ||
} | ||
|
||
test("validEdits"){ | ||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>!") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
// First run all classloaders are new, except level 0 running user code | ||
// which doesn't need generate a classloader which never changes | ||
checkChangedClassloaders(null, true, true, true) | ||
|
||
mangleFile(wsRoot / "foo" / "src" / "Example.scala", _.replace("!", "?")) | ||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>?") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
// Second run with no build changes, all classloaders are unchanged | ||
checkChangedClassloaders(null, false, false, false) | ||
|
||
mangleFile(wsRoot / "build.sc", _.replace("hello", "HELLO")) | ||
runAssertSuccess("<h1>HELLO</h1><p>world</p><p>0.8.2</p>?") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, false, false) | ||
|
||
mangleFile( | ||
wsRoot / "mill-build" / "build.sc", | ||
_.replace("def scalatagsVersion = ", "def scalatagsVersion = \"changed-\" + ") | ||
) | ||
runAssertSuccess("<h1>HELLO</h1><p>world</p><p>changed-0.8.2</p>?") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, true, false) | ||
|
||
mangleFile( | ||
wsRoot / "mill-build" / "mill-build" / "build.sc", | ||
_.replace("0.8.2", "0.12.0") | ||
) | ||
runAssertSuccess("<h1>HELLO</h1><p>world</p><p>changed-0.12.0</p>?") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, true, true) | ||
|
||
mangleFile( | ||
wsRoot / "mill-build" / "mill-build" / "build.sc", | ||
_.replace("0.12.0", "0.8.2") | ||
) | ||
runAssertSuccess("<h1>HELLO</h1><p>world</p><p>changed-0.8.2</p>?") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, true, true) | ||
|
||
mangleFile( | ||
wsRoot / "mill-build" / "build.sc", | ||
_.replace("def scalatagsVersion = \"changed-\" + ", "def scalatagsVersion = ") | ||
) | ||
runAssertSuccess("<h1>HELLO</h1><p>world</p><p>0.8.2</p>?") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, true, false) | ||
|
||
mangleFile(wsRoot / "build.sc", _.replace("HELLO", "hello")) | ||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>?") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, false, false) | ||
|
||
mangleFile(wsRoot / "foo" / "src" / "Example.scala", _.replace("?", "!")) | ||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>!") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, false, false, false) | ||
} | ||
|
||
test("parseErrorEdits") { | ||
def causeParseError(p: os.Path) = | ||
mangleFile(p, _.replace("extends", "extendx")) | ||
|
||
def fixParseError(p: os.Path) = | ||
mangleFile(p, _.replace("extendx", "extends")) | ||
|
||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>!") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, true, true) | ||
|
||
causeParseError(wsRoot / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"\ngenerateScriptSources build.sc" | ||
) | ||
checkWatchedFiles(Nil, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, false, false) | ||
|
||
causeParseError(wsRoot / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"\ngenerateScriptSources mill-build/build.sc" | ||
) | ||
checkWatchedFiles(Nil, Nil, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, null, false) | ||
|
||
causeParseError(wsRoot / "mill-build" / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"\ngenerateScriptSources mill-build/mill-build/build.sc" | ||
) | ||
checkWatchedFiles(Nil, Nil, Nil, buildPaths3) | ||
checkChangedClassloaders(null, null, null, null) | ||
|
||
fixParseError(wsRoot / "mill-build" / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"\ngenerateScriptSources mill-build/build.sc" | ||
) | ||
checkWatchedFiles(Nil, Nil, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, null, true) | ||
|
||
fixParseError(wsRoot / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"\ngenerateScriptSources build.sc" | ||
) | ||
checkWatchedFiles(Nil, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, true, false) | ||
|
||
fixParseError(wsRoot / "build.sc") | ||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>!") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, false, false) | ||
} | ||
|
||
test("compileErrorEdits") { | ||
def causeCompileError(p: os.Path) = | ||
mangleFile(p, _ + "\nimport doesnt.exist") | ||
|
||
def fixCompileError(p: os.Path) = | ||
mangleFile(p, _.replace("import doesnt.exist", "")) | ||
|
||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>!") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, true, true) | ||
|
||
causeCompileError(wsRoot / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
// Ensure the file path in the compile error is properly adjusted to point | ||
// at the original source file and not the generated file | ||
(wsRoot / "build.sc").toString, | ||
"not found: value doesnt" | ||
) | ||
checkWatchedFiles(Nil, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, false, false) | ||
|
||
causeCompileError(wsRoot / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
(wsRoot / "mill-build" / "build.sc").toString, | ||
"not found: value doesnt" | ||
) | ||
checkWatchedFiles(Nil, Nil, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, null, false) | ||
|
||
causeCompileError(wsRoot / "mill-build" / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
(wsRoot / "mill-build" / "mill-build" / "build.sc").toString, | ||
"not found: value doesnt" | ||
) | ||
checkWatchedFiles(Nil, Nil, Nil, buildPaths3) | ||
checkChangedClassloaders(null, null, null, null) | ||
|
||
fixCompileError(wsRoot / "mill-build" / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
(wsRoot / "mill-build" / "build.sc").toString, | ||
"not found: value doesnt" | ||
) | ||
checkWatchedFiles(Nil, Nil, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, null, true) | ||
|
||
fixCompileError(wsRoot / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
(wsRoot / "build.sc").toString, | ||
"not found: value doesnt" | ||
) | ||
checkWatchedFiles(Nil, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, true, false) | ||
|
||
fixCompileError(wsRoot / "build.sc") | ||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>!") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, false, false) | ||
} | ||
|
||
test("runtimeErrorEdits") { | ||
val runErrorSnippet = """{ | ||
|override def runClasspath = T{ | ||
| throw new Exception("boom") | ||
| super.runClasspath() | ||
|}""".stripMargin | ||
|
||
def causeRuntimeError(p: os.Path) = | ||
mangleFile(p, _.replaceFirst("\\{", runErrorSnippet)) | ||
|
||
def fixRuntimeError(p: os.Path) = | ||
mangleFile(p, _.replaceFirst(Regex.quote(runErrorSnippet), "\\{")) | ||
|
||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>!") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, true, true) | ||
|
||
causeRuntimeError(wsRoot / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"foo.runClasspath java.lang.Exception: boom" | ||
) | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, false, false) | ||
|
||
causeRuntimeError(wsRoot / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"build.sc", | ||
"runClasspath java.lang.Exception: boom" | ||
) | ||
checkWatchedFiles(Nil, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, true, false) | ||
|
||
causeRuntimeError(wsRoot / "mill-build" / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"build.sc", | ||
"runClasspath java.lang.Exception: boom" | ||
) | ||
checkWatchedFiles(Nil, Nil, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, null, true) | ||
|
||
fixRuntimeError(wsRoot / "mill-build" / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"build.sc", | ||
"runClasspath java.lang.Exception: boom" | ||
) | ||
checkWatchedFiles(Nil, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, null, true, true) | ||
|
||
fixRuntimeError(wsRoot / "mill-build" / "build.sc") | ||
evalCheckErr( | ||
"\n1 targets failed", | ||
"build.sc", | ||
"foo.runClasspath java.lang.Exception: boom" | ||
) | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, true, false) | ||
|
||
fixRuntimeError(wsRoot / "build.sc") | ||
runAssertSuccess("<h1>hello</h1><p>world</p><p>0.8.2</p>!") | ||
checkWatchedFiles(fooPaths, buildPaths, buildPaths2, buildPaths3) | ||
checkChangedClassloaders(null, true, false, false) | ||
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package mill.integration | ||
package local | ||
|
||
import utest._ | ||
import utest.framework.TestPath | ||
|
||
object ForeignBuildsTest extends IntegrationTestSuite { | ||
override def buildPath = os.sub / "project" / "build.sc" | ||
|
||
val tests = Tests { | ||
initWorkspace() | ||
def checkTarget()(implicit testPath: TestPath): Unit = assert(eval(testPath.value.last)) | ||
"checkProjectPaths" - checkTarget() | ||
"checkInnerPaths" - checkTarget() | ||
"checkOuterPaths" - checkTarget() | ||
"checkOuterInnerPaths" - checkTarget() | ||
"checkOtherPaths" - checkTarget() | ||
"checkProjectDests" - checkTarget() | ||
"checkInnerDests" - checkTarget() | ||
"checkOuterDests" - checkTarget() | ||
"checkOuterInnerDests" - checkTarget() | ||
"checkOtherDests" - checkTarget() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package mill.integration | ||
package local | ||
|
||
import utest._ | ||
|
||
object ForeignConflictTest extends IntegrationTestSuite { | ||
|
||
override def buildPath = os.sub / "conflict" / "build.sc" | ||
|
||
val tests = Tests { | ||
initWorkspace() | ||
test("test") - { | ||
// see https://github.com/lihaoyi/mill/issues/302 | ||
if (!mill.util.Util.java9OrAbove) { | ||
assert( | ||
eval("checkPaths"), | ||
eval("checkDests") | ||
) | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package mill.integration | ||
package local | ||
|
||
import os.Path | ||
import utest.{Tests, assert, _} | ||
|
||
|
||
import scala.util.Try | ||
import GenIdeaUtils._ | ||
|
||
object GenIdeaTests extends IntegrationTestSuite { | ||
|
||
override def scriptSourcePath = super.scriptSourcePath / "hello-world" | ||
private val scalaVersionLibPart = "2_12_5" | ||
|
||
|
||
def tests: Tests = Tests { | ||
test("helper assertPartialContentMatches works") { | ||
val testContent = | ||
s"""line 1 | ||
|line 2 | ||
|line 3 | ||
|line 4 | ||
|""".stripMargin | ||
|
||
assertPartialContentMatches(testContent, testContent) | ||
intercept[utest.AssertionError] { | ||
assertPartialContentMatches(testContent, "line 1") | ||
} | ||
assertPartialContentMatches( | ||
found = testContent, | ||
expected = | ||
s"""line 1${ignoreString}line 4 | ||
|""".stripMargin | ||
) | ||
intercept[utest.AssertionError] { | ||
assertPartialContentMatches( | ||
found = testContent, | ||
expected = | ||
s"""line 1${ignoreString}line 2${ignoreString}line 2${ignoreString}line 4 | ||
|""".stripMargin | ||
) | ||
} | ||
assertPartialContentMatches( | ||
found = testContent, | ||
expected = s"line 1${ignoreString}line 2$ignoreString" | ||
) | ||
intercept[utest.AssertionError] { | ||
assertPartialContentMatches( | ||
found = testContent, | ||
expected = s"line 1${ignoreString}line 2${ignoreString}line 2$ignoreString" | ||
) | ||
} | ||
() | ||
} | ||
|
||
test("genIdeaTests") { | ||
val workspacePath = initWorkspace() | ||
eval("mill.scalalib.GenIdea/idea") | ||
|
||
val checks = Seq( | ||
os.sub / "mill_modules" / "helloworld.iml", | ||
os.sub / "mill_modules" / "helloworld.test.iml", | ||
os.sub / "mill_modules" / "mill-build.iml", | ||
os.sub / "libraries" / s"scala_library_${scalaVersionLibPart}_jar.xml", | ||
os.sub / "modules.xml", | ||
os.sub / "misc.xml" | ||
).map { resource => | ||
Try { | ||
assertIdeaXmlResourceMatchesFile( | ||
scriptSlug, | ||
workspacePath, | ||
resource | ||
) | ||
} | ||
} | ||
assert(checks.forall(_.isSuccess)) | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package mill.integration | ||
import java.util.regex.Pattern | ||
import scala.util.Try | ||
import utest.assert | ||
object GenIdeaUtils{ | ||
|
||
/** | ||
* The resource content will loaded from the claspath and matched against the file. | ||
* It may contain the `<!-- IGNORE -->` String, to simulate wildcard-matches. | ||
*/ | ||
def assertIdeaXmlResourceMatchesFile(workspaceSlug: String, | ||
workspacePath: os.Path, | ||
resource: os.RelPath): Unit = { | ||
val expectedResourcePath = workspacePath / "idea" / resource | ||
val actualResourcePath = workspacePath / ".idea" / resource | ||
|
||
val expectedResourceString = os.read.lines(expectedResourcePath).mkString("\n") | ||
val actualResourceString = normaliseLibraryPaths(os.read(actualResourcePath), workspacePath) | ||
|
||
assertPartialContentMatches( | ||
found = actualResourceString, | ||
expected = expectedResourceString | ||
) | ||
} | ||
|
||
def assertPartialContentMatches(found: String, | ||
expected: String): Unit = { | ||
if (!expected.contains(ignoreString)) { | ||
assert(found == expected) | ||
} | ||
|
||
val pattern = | ||
"(?s)^\\Q" + expected.replaceAll(Pattern.quote(ignoreString), "\\\\E.*\\\\Q") + "\\E$" | ||
assert(Pattern.compile(pattern).matcher(found).matches()) | ||
} | ||
|
||
private def normaliseLibraryPaths(in: String, workspacePath: os.Path): String = { | ||
val coursierPath = os.Path(coursier.paths.CoursierPaths.cacheDirectory()) | ||
val path = | ||
Try("$PROJECT_DIR$/" + coursierPath.relativeTo(workspacePath)).getOrElse( | ||
coursierPath | ||
).toString().replace( | ||
"""\""", | ||
"/" | ||
) | ||
in.replace(path, "COURSIER_HOME") | ||
} | ||
|
||
val ignoreString = "<!-- IGNORE -->" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package mill.integration | ||
|
||
import utest._ | ||
|
||
object LargeProjectTests extends IntegrationTestSuite { | ||
val tests = Tests { | ||
initWorkspace() | ||
test("test") - { | ||
|
||
assert(eval("foo.common.one.compile")) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package mill.integration | ||
|
||
import utest._ | ||
|
||
object MillJvmOptsTests extends IntegrationTestSuite { | ||
val tests = Tests { | ||
initWorkspace() | ||
"JVM options from file .mill-jvm-opts are properly read" - { | ||
if (integrationTestMode != "local") assert(eval("checkJvmOpts")) | ||
} | ||
} | ||
} |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package mill.integration | ||
import utest._ | ||
object ExampleTestSuite extends IntegrationTestSuite{ | ||
val tests = Tests { | ||
val workspaceRoot = initWorkspace() | ||
|
||
test("exampleUsage") { | ||
val usageComment = | ||
os.read.lines(workspaceRoot / "build.sc") | ||
.dropWhile(_ != "/* Example Usage") | ||
.drop(1) | ||
.takeWhile(_ != "*/") | ||
.mkString("\n") | ||
|
||
val commandBlocks = usageComment.trim.split("\n\n") | ||
|
||
for(commandBlock <- commandBlocks){ | ||
val commandBlockLines = commandBlock.linesIterator.toVector | ||
println("ExampleTestSuite: " + commandBlockLines.head) | ||
commandBlockLines.head match{ | ||
case s"> ./$command" => | ||
|
||
val expectedSnippets = commandBlockLines.tail | ||
val evalResult = command match{ | ||
case s"mill $rest" => evalStdout(rest.split(" "): _*) | ||
case rest => | ||
val res = os.proc(rest.split(" ")).call(stdout=os.Pipe, stderr = os.Pipe, cwd = workspaceRoot) | ||
IntegrationTestSuite.EvalResult(res.exitCode == 0, res.out.text(), res.err.text()) | ||
} | ||
|
||
if (expectedSnippets.exists(_.startsWith("error: "))) assert(!evalResult.isSuccess) | ||
else assert(evalResult.isSuccess) | ||
|
||
val unwrappedExpected = expectedSnippets.map{ | ||
case s"error: $msg" => msg | ||
case msg => msg | ||
} | ||
|
||
for (expected <- unwrappedExpected) { | ||
if (integrationTestMode != "local") { | ||
println("ExampleTestSuite expected: " + expected) | ||
def plainText(s: String) = | ||
fansi.Str(s, errorMode = fansi.ErrorMode.Strip).plainText | ||
|
||
assert( | ||
plainText(evalResult.err).contains(expected) || | ||
plainText(evalResult.out).contains(expected) | ||
) | ||
} | ||
} | ||
case s"> cp -r $from $to" => | ||
os.copy(os.Path(from, workspaceRoot), os.Path(to, workspaceRoot)) | ||
case s"> sed -i 's/$oldStr/$newStr/g' $file" => | ||
mangleFile(os.Path(file, workspaceRoot), _.replace(oldStr, newStr)) | ||
|
||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package mill.integration | ||
|
||
import mainargs.Flag | ||
import mill.MillCliConfig | ||
import mill.define.SelectMode | ||
import mill.runner.{MillBuildBootstrap, MillMain, RunnerState, Watching} | ||
import mill.util.SystemStreams | ||
import os.Path | ||
import utest._ | ||
|
||
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, PrintStream} | ||
import java.nio.file.NoSuchFileException | ||
import scala.util.control.NonFatal | ||
|
||
object IntegrationTestSuite{ | ||
case class EvalResult(isSuccess: Boolean, out: String, err: String) | ||
|
||
} | ||
|
||
abstract class IntegrationTestSuite extends TestSuite{ | ||
val scriptSlug: String = sys.env("MILL_INTEGRATION_TEST_SLUG") | ||
|
||
val integrationTestMode: String = sys.env("MILL_INTEGRATION_TEST_MODE") | ||
assert(Set("local", "fork", "server").contains(integrationTestMode)) | ||
|
||
def workspacePath: os.Path = | ||
os.Path(sys.props.getOrElse("MILL_WORKSPACE_PATH", ???)) | ||
|
||
def scriptSourcePath: os.Path = os.Path(sys.env("MILL_INTEGRATION_REPO_ROOT")) | ||
|
||
def buildPath: os.SubPath = os.sub / "build.sc" | ||
|
||
def wd = workspacePath / buildPath / os.up | ||
|
||
val stdIn = new ByteArrayInputStream(Array()) | ||
val disableTicker = false | ||
val debugLog = false | ||
val keepGoing = false | ||
val userSpecifiedProperties = Map[String, String]() | ||
val threadCount = sys.props.get("MILL_THREAD_COUNT").map(_.toInt).orElse(Some(1)) | ||
|
||
var runnerState = RunnerState.empty | ||
|
||
private def runnerStdout(stdout: PrintStream, stderr: PrintStream, s: Seq[String]) = { | ||
|
||
val streams = new SystemStreams(stdout, stderr, stdIn) | ||
val config = MillCliConfig( | ||
debugLog = Flag(debugLog), | ||
keepGoing = Flag(keepGoing), | ||
disableTicker = Flag(disableTicker) | ||
) | ||
val logger = mill.runner.MillMain.getLogger( | ||
streams, | ||
config, | ||
mainInteractive = false, | ||
enableTicker = Some(false) | ||
) | ||
|
||
val (isSuccess, newRunnerState) = Watching.watchLoop( | ||
logger = logger, | ||
ringBell = config.ringBell.value, | ||
watch = config.watch.value, | ||
streams = streams, | ||
setIdle = _ => (), | ||
evaluate = (prevStateOpt: Option[RunnerState]) => { | ||
MillMain.adjustJvmProperties(userSpecifiedProperties, sys.props.toMap) | ||
new MillBuildBootstrap( | ||
projectRoot = wd, | ||
config = config, | ||
env = Map.empty, | ||
threadCount = threadCount, | ||
targetsAndParams = s.toList, | ||
prevRunnerState = runnerState, | ||
logger = logger, | ||
).evaluate() | ||
} | ||
) | ||
|
||
runnerState = newRunnerState | ||
|
||
(isSuccess, newRunnerState) | ||
} | ||
|
||
def eval(s: String*): Boolean = { | ||
if (integrationTestMode == "local") runnerStdout(System.out, System.err, s)._1 | ||
else evalFork(os.Inherit, os.Inherit, s) | ||
} | ||
|
||
def evalStdout(s: String*): IntegrationTestSuite.EvalResult = { | ||
if (integrationTestMode == "local") { | ||
val outputStream = new ByteArrayOutputStream | ||
val errorStream = new ByteArrayOutputStream | ||
val printOutStream = new PrintStream(outputStream) | ||
val printErrStream = new PrintStream(errorStream) | ||
val result = runnerStdout(printOutStream, printErrStream, s.toList) | ||
val stdoutArray = outputStream.toByteArray | ||
val stdout: Seq[String] = | ||
if (stdoutArray.isEmpty) Seq.empty | ||
else new String(stdoutArray).split('\n') | ||
val stderrArray = errorStream.toByteArray | ||
val stderr: Seq[String] = | ||
if (stderrArray.isEmpty) Seq.empty | ||
else new String(stderrArray).split('\n') | ||
IntegrationTestSuite.EvalResult(result._1, stdout.mkString("\n"), stderr.mkString("\n")) | ||
} else { | ||
val output = Seq.newBuilder[String] | ||
val error = Seq.newBuilder[String] | ||
val processOutput = os.ProcessOutput.Readlines(output += _) | ||
val processError = os.ProcessOutput.Readlines(error += _) | ||
|
||
val result = evalFork(processOutput, processError, s) | ||
IntegrationTestSuite.EvalResult(result, output.result().mkString("\n"), error.result().mkString("\n")) | ||
} | ||
} | ||
|
||
val millReleaseFileOpt = Option(System.getenv("MILL_TEST_RELEASE")).map(os.Path(_, os.pwd)) | ||
val millTestSuiteEnv = Map("MILL_TEST_SUITE" -> this.getClass().toString()) | ||
|
||
private def evalFork(stdout: os.ProcessOutput, stderr: os.ProcessOutput, s: Seq[String]): Boolean = { | ||
val serverArgs = if (integrationTestMode == "server") Seq() else Seq("--no-server") | ||
val debugArgs = if (debugLog) Seq("--debug") else Seq() | ||
try { | ||
os.proc(millReleaseFileOpt.get, serverArgs, debugArgs, s).call( | ||
cwd = wd, | ||
stdin = os.Inherit, | ||
stdout = stdout, | ||
stderr = stderr, | ||
env = millTestSuiteEnv | ||
) | ||
true | ||
} catch { | ||
case NonFatal(_) => false | ||
} | ||
} | ||
|
||
def meta(s: String): String = { | ||
val Seq((List(selector), _)) = | ||
mill.define.ParseArgs.apply(Seq(s), SelectMode.Single).getOrElse(???) | ||
|
||
val segments = selector._2.value.flatMap(_.pathSegments) | ||
os.read(wd / "out" / segments.init / s"${segments.last}.json") | ||
} | ||
|
||
def initWorkspace(): Path = { | ||
os.remove.all(workspacePath) | ||
os.makeDir.all(workspacePath / os.up) | ||
// The unzipped git repo snapshots we get from github come with a | ||
// wrapper-folder inside the zip file, so copy the wrapper folder to the | ||
// destination instead of the folder containing the wrapper. | ||
|
||
os.copy(scriptSourcePath, workspacePath) | ||
workspacePath | ||
} | ||
|
||
def mangleFile(p: os.Path, f: String => String) = os.write.over(p, f(os.read(p))) | ||
|
||
override def utestAfterEach(path: Seq[String]): Unit = { | ||
runnerState = RunnerState.empty | ||
if (integrationTestMode == "server") { | ||
// try to stop the server | ||
try { | ||
os.proc(millReleaseFileOpt.get, "shutdown").call( | ||
cwd = wd, | ||
stdin = os.Inherit, | ||
stdout = os.Inherit, | ||
stderr = os.Inherit, | ||
env = millTestSuiteEnv | ||
) | ||
} catch { | ||
case NonFatal(e) => | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -154,4 +154,4 @@ object simulator extends CaffeineModule { | |
|
||
def ivyDeps = super.ivyDeps() ++ testLibraries.testng | ||
} | ||
} | ||
} |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package mill.define | ||
|
||
import mill.api.internal | ||
|
||
/** | ||
* Represents something that can be watched by the Mill build tool. Most often | ||
* these are [[Watchable.Path]]s, but we use [[Watchable.Value]] to watch the | ||
* value of arbitrary computations e.g. watching the result of `os.list` to | ||
* react if a new folder is added. | ||
*/ | ||
@internal | ||
trait Watchable { | ||
def poll(): Long | ||
def signature: Long | ||
def validate(): Boolean = poll() == signature | ||
} | ||
@internal | ||
object Watchable{ | ||
case class Path(p: mill.api.PathRef) extends Watchable { | ||
def poll() = p.recomputeSig() | ||
def signature = p.sig | ||
} | ||
case class Value(f: () => Long, signature: Long) extends Watchable { | ||
def poll() = f() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see how it improves the testability, but I'd rather get rid of the
millProjectModule
hack altogether. It weakens the way Mill consumes project modules / plugins, and may be even a security vulnerability. Just using a local repo in the process of testings is definitely more time consuming, but it disables this attack vector and also runs tests under more realistic conditions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I'm willing to give up purely local testing. The test pyramid is a pyramid, with both faster/lower-fidelity tests at the bottom and slower/higher-fidelity tests at the top. In this case we basically get both cases for free.
If there's a security concern, we should spell it out and find some way to mitigate it, but I'm sure that we can find a solution without giving up the ability to do local testing entirely
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's understandable. We could hook into the resolve instead, and just feed all known jars directly. The potential injection is the smaller concern for me, it's rather the fact that we load the jars in a complete different way; e.g. when resolving via coursier, there might come different transitive deps as those we assemble in our build.
But this is a orthogonal issue, so let's not continue this thinking here. I'll open a placeholder dicussion, where we can outline alternatives which are easier to maintain as the current setup in
build.sc
, which I don't like so much.