Skip to content
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

Merged
merged 208 commits into from
Apr 2, 2023
Merged
Changes from 168 commits
Commits
Show all changes
208 commits
Select commit Hold shift + click to select a range
ebf0cc8
./mill -i -w dev.run example/example-1 foo.run passes, printing Hello…
lihaoyi Mar 21, 2023
eb243fd
remove the ammonite dependency, keep ammonite-util for now
lihaoyi Mar 21, 2023
658343a
break up MillMain
lihaoyi Mar 21, 2023
c86bd71
try to properly propagate watched paths
lihaoyi Mar 21, 2023
592073a
basic file watching works again
lihaoyi Mar 21, 2023
6005df4
minor cleanup
lihaoyi Mar 21, 2023
884fdae
remove final ammonite dependencies
lihaoyi Mar 21, 2023
69e2ebd
first file and ivy import work
lihaoyi Mar 21, 2023
58dc796
.
lihaoyi Mar 22, 2023
7e9f902
generate import trees
lihaoyi Mar 22, 2023
2a1b48f
get most tests compiling, except for entrypoint.test
lihaoyi Mar 22, 2023
47090d7
everything compiles
lihaoyi Mar 22, 2023
5551be7
fix ScriptTestSuite base path
lihaoyi Mar 22, 2023
a20bd9b
add moduledefs plugin to MillBootstrapModule to fix mill.integration…
lihaoyi Mar 22, 2023
40e502b
copy enclosing mill launcher to have proper file extension to make Zi…
lihaoyi Mar 22, 2023
5a9c3d9
vendor Ammonite's LineNumberPlugin, with some basic tests
lihaoyi Mar 22, 2023
672cb4b
consolidate integration tests under integration/ folder
lihaoyi Mar 22, 2023
5c16771
normalize MILL_BSP_WORKER mill module classpath handling in tests
lihaoyi Mar 22, 2023
1e95378
fix integration JavaCompileJarTests
lihaoyi Mar 22, 2023
c4011eb
Merge branch 'main' into remove-ammonite
lihaoyi Mar 22, 2023
eba011b
mill.integration.local.ForeignBuildsTest.checkProjectPaths and half o…
lihaoyi Mar 22, 2023
c3c346a
rest of mill.integration.local.ForeignBuildsTest now pass
lihaoyi Mar 22, 2023
d9bcb45
mill.integration.local.GenIdeaTests passes
lihaoyi Mar 22, 2023
f63ed25
remove old MILL_BUILD_LIBRARIES property
lihaoyi Mar 22, 2023
6f3c388
fix mill.eval.JavaCompileJarTests.javac
lihaoyi Mar 22, 2023
b020c83
commit moved iml files
lihaoyi Mar 22, 2023
8a66e46
first few ScriptsInvalidationTests pass
lihaoyi Mar 23, 2023
e2fc232
ScriptsInvalidationForeignTests and ScriptsInvalidationTests now pass
lihaoyi Mar 23, 2023
c31a85f
GenIdeaExtendedTests passes
lihaoyi Mar 23, 2023
7d204cb
fix
lihaoyi Mar 23, 2023
d6edcc0
avoid uniqueness error
lihaoyi Mar 23, 2023
2f96862
fix BspInstallDebugTest
lihaoyi Mar 23, 2023
d6b9835
update workflows
lihaoyi Mar 23, 2023
1612eac
Fix handling of scripts with shebang lines or leading comments
lihaoyi Mar 23, 2023
63cea8d
add entrypoint to testArgs to dev.assembly
lihaoyi Mar 23, 2023
bf0d376
try to fix URL -> Path conversion on Windows
lihaoyi Mar 23, 2023
ece13bc
fix java 8
lihaoyi Mar 23, 2023
69cb89d
add more debugging logs
lihaoyi Mar 23, 2023
66eb2a1
fix-quotes
lihaoyi Mar 23, 2023
61cade2
more debugging
lihaoyi Mar 23, 2023
b45fdca
more debugging
lihaoyi Mar 23, 2023
eeb088e
fixcompile
lihaoyi Mar 23, 2023
8ac8819
fixcompile
lihaoyi Mar 23, 2023
b9b02d5
moredebugging
lihaoyi Mar 23, 2023
805c7e0
more debugging
lihaoyi Mar 23, 2023
a1a94d1
debug
lihaoyi Mar 23, 2023
5ef72bf
debug
lihaoyi Mar 23, 2023
06b18a4
.
lihaoyi Mar 23, 2023
6541de0
.
lihaoyi Mar 23, 2023
d4fc01a
.
lihaoyi Mar 23, 2023
333aec8
cleanup
lihaoyi Mar 23, 2023
2201193
add back utest debugging
lihaoyi Mar 23, 2023
f9b0c59
more debugging
lihaoyi Mar 23, 2023
3887098
revert example changes
lihaoyi Mar 23, 2023
f64ddd0
.
lihaoyi Mar 23, 2023
29430fa
try different way of counting MILL_USER_CODE_START_MARKER that is rob…
lihaoyi Mar 23, 2023
770bcef
different way of copying mill-launcher.jar to try and avoid windows c…
lihaoyi Mar 23, 2023
3e8e471
use different mill-launcher jars for every mill version
lihaoyi Mar 23, 2023
0e3e5eb
add guard around copy-mill-launcher logic to try and avoid windows pe…
lihaoyi Mar 24, 2023
f5905a1
try to fix compile error positioning problems
lihaoyi Mar 24, 2023
6936c72
debug ParseErrorTests
lihaoyi Mar 24, 2023
83dc681
.
lihaoyi Mar 24, 2023
859e4fc
.
lihaoyi Mar 24, 2023
b08bb20
fixcompile
lihaoyi Mar 24, 2023
4fe5994
simplify ParseErrorTests
lihaoyi Mar 24, 2023
7e8eeb4
.
lihaoyi Mar 24, 2023
6968a66
tweaks
lihaoyi Mar 24, 2023
84ed12c
tweaks
lihaoyi Mar 24, 2023
7572c31
cleanup
lihaoyi Mar 25, 2023
a57fb9c
cleanup
lihaoyi Mar 25, 2023
705b4a3
try to re-organize MillBootstrap.scala
lihaoyi Mar 25, 2023
71c4285
bootstrap -> boot, consolidate watched logic
lihaoyi Mar 25, 2023
e1531b5
cleanup
lihaoyi Mar 25, 2023
9dcbdb2
kill AmmoniteUtils
lihaoyi Mar 25, 2023
bcc01a8
tweak file discovery and watching logic
lihaoyi Mar 25, 2023
a9ea03c
.
lihaoyi Mar 25, 2023
06c9f2d
fixbuild
lihaoyi Mar 25, 2023
62b38dc
add MILL_VERSION support
lihaoyi Mar 25, 2023
2833f0f
.
lihaoyi Mar 25, 2023
a113a1f
fix tests
lihaoyi Mar 25, 2023
3029b43
fix MillBuildModule.millSourcePath
lihaoyi Mar 25, 2023
4ac1e98
.
lihaoyi Mar 25, 2023
4b251af
properly manage evaluator state in the watchLoop
lihaoyi Mar 25, 2023
b2b64ca
simplify import graph handling
lihaoyi Mar 25, 2023
a5ebff1
remove unused EvaluatoreState#rootModule field, which can anyway be r…
lihaoyi Mar 25, 2023
b1f566d
remove systemProperties from EvaluatorState
lihaoyi Mar 25, 2023
c0d0f41
metabuild refactor compiles, doesn't pass any tests yet
lihaoyi Mar 25, 2023
404dbb7
basic meta-builds and meta-meta-builds now work
lihaoyi Mar 25, 2023
96c3d03
add first MetaMetaBuildTests
lihaoyi Mar 26, 2023
5405c55
metabuild refactor compiles, doesn't pass any tests yet
lihaoyi Mar 26, 2023
4b674d2
wip trying to adjust file paths
lihaoyi Mar 26, 2023
463de44
add MetaMetaBuildTests for compile and runtime errors
lihaoyi Mar 26, 2023
394ec47
remove [mill-build] prefix from errors
lihaoyi Mar 26, 2023
f776f13
custom top-level modules
lihaoyi Mar 26, 2023
ee99d4c
some invalidation tests pass
lihaoyi Mar 26, 2023
9b85004
integration.local tests pass
lihaoyi Mar 26, 2023
8ed5647
merge
lihaoyi Mar 26, 2023
eb6110e
remove ScriptTestSuite
lihaoyi Mar 26, 2023
42390f3
rename forked-server -> server
lihaoyi Mar 26, 2023
2c2226b
rename forked-server -> server
lihaoyi Mar 26, 2023
30b4a0d
simplify EvalResult
lihaoyi Mar 26, 2023
5270206
.
lihaoyi Mar 26, 2023
5782b00
fix github actions
lihaoyi Mar 26, 2023
94bd39f
rename MultiEvaluatorState -> RunnerState
lihaoyi Mar 26, 2023
088284d
fix ordering of previousEvalStates
lihaoyi Mar 27, 2023
ed6e243
fix integration tests
lihaoyi Mar 27, 2023
449668e
Fix mill.integration.server.MetaMetaBuildTests
lihaoyi Mar 27, 2023
4061e3f
simplify
lihaoyi Mar 27, 2023
ef3c6a5
simplify
lihaoyi Mar 27, 2023
4cc82f3
persist mill process in integration.server.test until end of each tes…
lihaoyi Mar 27, 2023
f657f7f
fix tests
lihaoyi Mar 27, 2023
1132540
add watched paths testing to the MultiLevelModuleTests
lihaoyi Mar 27, 2023
c6240d8
simplify MultiLevelBuildTests
lihaoyi Mar 27, 2023
e683d18
comment
lihaoyi Mar 27, 2023
dac3fff
cleanup
lihaoyi Mar 27, 2023
cf45f30
add TopLevelModuleTests
lihaoyi Mar 27, 2023
3a23867
remove dead TerminalGroup
lihaoyi Mar 27, 2023
f79b497
wip trying to fix and test bootstrap caching
lihaoyi Mar 28, 2023
ca518b9
basically works
lihaoyi Mar 28, 2023
11141e9
classloader invalidation mostly works again...
lihaoyi Mar 28, 2023
2a99bb1
re-introduce grand-parent-build classpath hash
lihaoyi Mar 28, 2023
914170c
comment
lihaoyi Mar 28, 2023
f88bad7
wip
lihaoyi Mar 28, 2023
a1caa6b
.
lihaoyi Mar 28, 2023
6ab2317
quick PathRefs for MillBuildModul#unmanagedClasspath
lihaoyi Mar 28, 2023
e093627
merge
lihaoyi Mar 28, 2023
3fb75aa
cleanup-build-sc
lihaoyi Mar 28, 2023
1e6306a
move script hashing out of evaluator fast path
lihaoyi Mar 28, 2023
2556cec
.
lihaoyi Mar 28, 2023
0bf0c17
.
lihaoyi Mar 28, 2023
49c0e26
add error message for missing build file and MissingBuildFileTests
lihaoyi Mar 28, 2023
98cd69f
try to fix MultiLevelBuildTests on windows
lihaoyi Mar 28, 2023
9182df5
merge
lihaoyi Mar 28, 2023
da1c914
wip
lihaoyi Mar 28, 2023
4bf3d73
fix genidea tests
lihaoyi Mar 29, 2023
04686f6
make debug flag work in forked integration tests
lihaoyi Mar 29, 2023
feeb191
all tests pass
lihaoyi Mar 29, 2023
3d41495
tests-pass
lihaoyi Mar 29, 2023
226694a
.
lihaoyi Mar 29, 2023
7155332
comment
lihaoyi Mar 29, 2023
ab912c0
cleanup
lihaoyi Mar 29, 2023
70e3bda
fix
lihaoyi Mar 29, 2023
51c6247
fix
lihaoyi Mar 29, 2023
4bde7a3
fix
lihaoyi Mar 29, 2023
9cb76f2
tweak paths
lihaoyi Mar 30, 2023
86d97d8
update patch file
lihaoyi Mar 30, 2023
4783286
update ci scripts
lihaoyi Mar 30, 2023
26b954b
update ci scripts
lihaoyi Mar 30, 2023
c05b727
fixes
lihaoyi Mar 30, 2023
9683392
add invalid-meta-module, multiple-top-level-modules test cases
lihaoyi Mar 30, 2023
ed7dfdf
split up integration tests into smaller jobs
lihaoyi Mar 30, 2023
64fee00
add tests to 5-scala-publish repo
lihaoyi Mar 30, 2023
b309547
wip re-organizing examples folder
lihaoyi Mar 30, 2023
c4be342
example-tests-pass
lihaoyi Mar 30, 2023
db2fcc4
.
lihaoyi Mar 30, 2023
386ad88
BuildModule
lihaoyi Mar 30, 2023
0a4a361
update CI
lihaoyi Mar 30, 2023
a62c6b7
fixes
lihaoyi Mar 30, 2023
e4426f5
example explanations
lihaoyi Mar 30, 2023
2fbfc53
split IntegrationTestModule and IntegrationTestCrossModule
lihaoyi Mar 30, 2023
b122666
.
lihaoyi Mar 30, 2023
7b3d4e3
fix-patch
lihaoyi Mar 30, 2023
b6cde25
.
lihaoyi Mar 30, 2023
41a6c95
more comments
lihaoyi Mar 30, 2023
85c96fe
get example tests passing
lihaoyi Mar 30, 2023
84300f7
add assembly to the examples
lihaoyi Mar 30, 2023
6c0048d
break up example into subfolders
lihaoyi Mar 30, 2023
95737c5
add version-specific test sources
lihaoyi Mar 30, 2023
809c7c0
.
lihaoyi Mar 31, 2023
c8f4e80
update patch file
lihaoyi Mar 31, 2023
0074d25
add common-overrides and custom-tasks examples
lihaoyi Mar 31, 2023
91b3638
more web examples
lihaoyi Mar 31, 2023
1d190fc
5-webapp-scalajs-shared
lihaoyi Mar 31, 2023
7839169
fix fork
lihaoyi Mar 31, 2023
1a43868
.
lihaoyi Mar 31, 2023
b09ece3
fixes
lihaoyi Mar 31, 2023
fc31c48
fixes
lihaoyi Mar 31, 2023
2e05d5b
finally cleanup
lihaoyi Mar 31, 2023
122ffc3
try to get CI green
lihaoyi Mar 31, 2023
58e4dd5
reduce build matrix
lihaoyi Mar 31, 2023
b57ae36
reduce build matrix more
lihaoyi Mar 31, 2023
038e3f0
.
lihaoyi Mar 31, 2023
319d163
more stripping build matrix
lihaoyi Apr 1, 2023
077b8b9
tidying
lihaoyi Apr 1, 2023
7e1437a
add 3-import-file-ivy example
lihaoyi Apr 1, 2023
01f5ac9
fixes
lihaoyi Apr 1, 2023
34693da
make use of generatedSources in 4-mill-build-folder
lihaoyi Apr 1, 2023
ae05f1e
fix
lihaoyi Apr 1, 2023
7124998
example readme
lihaoyi Apr 1, 2023
ea7314e
tweak-comment
lihaoyi Apr 1, 2023
24e401b
.
lihaoyi Apr 1, 2023
e91b41e
better error
lihaoyi Apr 1, 2023
3210acc
fix
lihaoyi Apr 1, 2023
e6308fc
.
lihaoyi Apr 1, 2023
c49dbaa
standardize naming on RootModule
lihaoyi Apr 1, 2023
4ad7200
.
lihaoyi Apr 1, 2023
d9f3ebd
fix
lihaoyi Apr 1, 2023
0ad2efb
try-fix
lihaoyi Apr 1, 2023
1bc24f4
remove windows assembly from examples
lihaoyi Apr 1, 2023
41921ef
try to fudge windows paths
lihaoyi Apr 1, 2023
17f4907
.
lihaoyi Apr 1, 2023
101718f
debug
lihaoyi Apr 1, 2023
f24b4ac
debug
lihaoyi Apr 1, 2023
d070d7e
.
lihaoyi Apr 1, 2023
63affbf
.
lihaoyi Apr 1, 2023
97f896a
.
lihaoyi Apr 1, 2023
a3976ee
.
lihaoyi Apr 1, 2023
76abe80
.
lihaoyi Apr 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
@@ -33,9 +33,19 @@ jobs:
# unit and module tests
- ./mill -i -k "{main,scalalib,scalajslib,scalanativelib,testrunner,bsp}.__.test"
# additional integration tests
- ./mill -i -k "integration.{local,forked,forked-server}"
- ./mill -i -k "example.__.local.test"
- ./mill -i -k "example.__.fork.test"
- ./mill -i -k "example.__.server.test"
- ./mill -i -k "integration.feature.__.local.test"
- ./mill -i -k "integration.feature.__.fork.test"
- ./mill -i -k "integration.feature.__.server.test"
- ./mill -i -k "integration.failure.__.local.test"
- ./mill -i -k "integration.failure.__.fork.test"
- ./mill -i -k "integration.failure.__.server.test"
# integration tests of thirdparty repos
- ./mill -i -k "integration.thirdparty.{local,forked}"
- ./mill -i -k "integration.thirdparty.__.local.test"
- ./mill -i -k "integration.thirdparty.__.fork.test"
- ./mill -i -k "integration.thirdparty.__.server.test"
# contrib tests
- ./mill -i -k "contrib._.test"
- ./mill -i docs.antora.githubPages
@@ -108,8 +118,18 @@ jobs:
buildcmd:
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "{__.publishLocal,assembly,__.compile}"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "{main,scalalib,scalajslib,bsp}.__.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.{local,forked,forked-server}"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.thirdparty.{local,forked}"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "example.__.local.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "example.__.fork.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "example.__.server.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.feature.__.local.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.feature.__.fork.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.feature.__.server.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.failure.__.local.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.failure.__.fork.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.failure.__.server.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.thirdparty.__.local.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.thirdparty.__.fork.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "integration.thirdparty.__.server.test"
- cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -d -k "contrib.__.test"


32 changes: 15 additions & 17 deletions bsp/src/mill/bsp/BSP.scala
Original file line number Diff line number Diff line change
@@ -9,11 +9,11 @@ import mill.define.{Command, Discover, ExternalModule, Task}
import mill.eval.Evaluator
import mill.main.{BspServerHandle, BspServerResult, BspServerStarter}
import mill.scalalib.{CoursierModule, Dep}
import mill.util.PrintLogger
import mill.util.{PrintLogger, SystemStreams}
import os.Path

object BSP extends ExternalModule with CoursierModule with BspServerStarter {
implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]()
import mill.main.TokenReaders._

lazy val millDiscover: Discover[this.type] = Discover[this.type]

@@ -24,9 +24,11 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter {
}

private def bspWorkerLibs: T[Agg[PathRef]] = T {
resolveDeps(T.task {
bspWorkerIvyDeps().map(bindDependency())
})()
mill.modules.Util.millProjectModule(
"MILL_BSP_WORKER",
"mill-bsp-worker",
repositoriesTask()
)
Comment on lines +27 to +31
Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member

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.

}

/**
@@ -70,9 +72,7 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter {

override def startBspServer(
initialEvaluator: Option[Evaluator],
outStream: PrintStream,
errStream: PrintStream,
inStream: InputStream,
streams: SystemStreams,
workspaceDir: os.Path,
ammoniteHomeDir: os.Path,
canReload: Boolean,
@@ -85,13 +85,13 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter {
// This all goes to the BSP log file mill-bsp.stderr
override def log: Logger = new Logger {
override def colored: Boolean = false
override def errorStream: PrintStream = errStream
override def outputStream: PrintStream = errStream
override def errorStream: PrintStream = streams.err
override def outputStream: PrintStream = streams.err
override def inStream: InputStream = DummyInputStream
override def info(s: String): Unit = errStream.println(s)
override def error(s: String): Unit = errStream.println(s)
override def ticker(s: String): Unit = errStream.println(s)
override def debug(s: String): Unit = errStream.println(s)
override def info(s: String): Unit = streams.err.println(s)
override def error(s: String): Unit = streams.err.println(s)
override def ticker(s: String): Unit = streams.err.println(s)
override def debug(s: String): Unit = streams.err.println(s)
override def debugEnabled: Boolean = true
}
}
@@ -102,9 +102,7 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter {
case Result.Success(worker) =>
worker.startBspServer(
initialEvaluator,
outStream,
errStream,
inStream,
streams,
workspaceDir / Constants.bspDir,
canReload,
Seq(millServerHandle) ++ serverHandle.toSeq
5 changes: 2 additions & 3 deletions bsp/src/mill/bsp/BspWorker.scala
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import mill.api.{Ctx, PathRef, Result, internal}
import mill.define.Task
import mill.eval.Evaluator
import mill.main.{BspServerHandle, BspServerResult}
import mill.util.SystemStreams

import java.io.{InputStream, PrintStream}
import java.net.URL
@@ -21,9 +22,7 @@ trait BspWorker {

def startBspServer(
initialEvaluator: Option[Evaluator],
outStream: PrintStream,
errStream: PrintStream,
inStream: InputStream,
streams: SystemStreams,
logDir: os.Path,
canReload: Boolean,
serverHandles: Seq[Promise[BspServerHandle]]
23 changes: 11 additions & 12 deletions bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import mill.bsp.{BSP, BspWorker, Constants}
import mill.define.Task
import mill.eval.Evaluator
import mill.main.{BspServerHandle, BspServerResult}
import mill.util.SystemStreams
import org.eclipse.lsp4j.jsonrpc.Launcher

import java.io.{InputStream, PrintStream, PrintWriter}
@@ -62,9 +63,7 @@ class BspWorkerImpl() extends BspWorker {

override def startBspServer(
initialEvaluator: Option[Evaluator],
outStream: PrintStream,
errStream: PrintStream,
inStream: InputStream,
streams: SystemStreams,
logDir: os.Path,
canReload: Boolean,
serverHandles: Seq[Promise[BspServerHandle]]
@@ -77,7 +76,7 @@ class BspWorkerImpl() extends BspWorker {
bspVersion = Constants.bspProtocolVersion,
serverVersion = MillBuildInfo.millVersion,
serverName = Constants.serverName,
logStream = errStream,
logStream = streams.err,
canReload = canReload
) with MillJvmBuildServer with MillJavaBuildServer with MillScalaBuildServer

@@ -87,8 +86,8 @@ class BspWorkerImpl() extends BspWorker {

try {
val launcher = new Launcher.Builder[BuildClient]()
.setOutput(outStream)
.setInput(inStream)
.setOutput(streams.out)
.setInput(streams.in)
.setLocalService(millServer)
.setRemoteInterface(classOf[BuildClient])
.traceMessages(new PrintWriter(
@@ -112,22 +111,22 @@ class BspWorkerImpl() extends BspWorker {
val onReload = Promise[BspServerResult]()
millServer.onSessionEnd = Some { serverResult =>
if (!onReload.isCompleted) {
errStream.println("Unsetting evaluator on session end")
streams.err.println("Unsetting evaluator on session end")
millServer.updateEvaluator(None)
_lastResult = Some(serverResult)
onReload.success(serverResult)
}
}
Await.result(onReload.future, Duration.Inf).tap { r =>
errStream.println(s"Reload finished, result: ${r}")
streams.err.println(s"Reload finished, result: ${r}")
_lastResult = Some(r)
}
}

override def lastResult: Option[BspServerResult] = _lastResult

override def stop(): Unit = {
errStream.println("Stopping server via handle...")
streams.err.println("Stopping server via handle...")
listening.cancel(true)
}
}
@@ -137,17 +136,17 @@ class BspWorkerImpl() extends BspWorker {
()
} catch {
case _: CancellationException =>
errStream.println("The mill server was shut down.")
streams.err.println("The mill server was shut down.")
case e: Exception =>
errStream.println(
streams.err.println(
s"""An exception occurred while connecting to the client.
|Cause: ${e.getCause}
|Message: ${e.getMessage}
|Exception class: ${e.getClass}
|Stack Trace: ${e.getStackTrace}""".stripMargin
)
} finally {
errStream.println("Shutting down executor")
streams.err.println("Shutting down executor")
executor.shutdown()

}
5 changes: 2 additions & 3 deletions bsp/worker/src/mill/bsp/worker/MillBuildServer.scala
Original file line number Diff line number Diff line change
@@ -60,7 +60,8 @@ import mill.api.{DummyTestReporter, PathRef, Result, Strict, internal}
import mill.define.Segment.Label
import mill.define.{BaseModule, Discover, ExternalModule, Module, Segments, Task}
import mill.eval.Evaluator
import mill.main.{BspServerResult, EvaluatorScopt, MainModule}
import mill.main.{BspServerResult, MainModule}
import mill.main.TokenReaders._
import mill.scalalib.{JavaModule, SemanticDbJavaModule, TestModule}
import mill.scalalib.bsp.{BspModule, JvmBuildTarget, MillBuildModule, ScalaBuildTarget}
import mill.scalalib.internal.ModuleUtils
@@ -85,8 +86,6 @@ class MillBuildServer(
) extends ExternalModule
with BuildServer {

implicit def millScoptEvaluatorReads[T]: EvaluatorScopt[T] = new mill.main.EvaluatorScopt[T]()

lazy val millDiscover: Discover[MillBuildServer.this.type] = Discover[this.type]

var cancellator: Boolean => Unit = shutdownBefore => ()
301 changes: 177 additions & 124 deletions build.sc

Large diffs are not rendered by default.

39 changes: 24 additions & 15 deletions ci/mill-bootstrap.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/build.sc b/build.sc
index 3930b72ff..5cf3ee459 100644
index 2ced447bf..af0244fd3 100644
--- a/build.sc
+++ b/build.sc
@@ -2,22 +2,10 @@
@@ -9,7 +9,7 @@ index 3930b72ff..5cf3ee459 100644
-import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version_mill0.10:0.3.0`
-import $ivy.`com.github.lolgab::mill-mima_mill0.10:0.0.13`
import $ivy.`net.sourceforge.htmlcleaner:htmlcleaner:2.25`

// imports
-import com.github.lolgab.mill.mima
-import com.github.lolgab.mill.mima.{
@@ -25,10 +25,10 @@ index 3930b72ff..5cf3ee459 100644
import mill._
import mill.define.{Command, Source, Sources, Target, Task}
import mill.eval.Evaluator
@@ -184,12 +172,8 @@ object Deps {
@@ -178,12 +166,8 @@ object Deps {
val requests = ivy"com.lihaoyi::requests:0.8.0"
}

-def millVersion: T[String] = T { VcsVersion.vcsState().format() }
-def millLastTag: T[String] = T {
- VcsVersion.vcsState().lastTag.getOrElse(
@@ -40,10 +40,10 @@ index 3930b72ff..5cf3ee459 100644
def millBinPlatform: T[String] = T {
val tag = millLastTag()
if (tag.contains("-M")) tag
@@ -248,20 +232,7 @@ trait MillCoursierModule extends CoursierModule {
@@ -243,20 +227,7 @@ trait MillCoursierModule extends CoursierModule {
)
}

-trait MillMimaConfig extends mima.Mima {
- override def mimaPreviousVersions: T[Seq[String]] = Settings.mimaBaseVersions
- override def mimaPreviousArtifacts =
@@ -60,35 +60,44 @@ index 3930b72ff..5cf3ee459 100644
- lazy val issueFilterByModule: Map[MillMimaConfig, Seq[ProblemFilter]] = Map()
+trait MillMimaConfig extends Module {
}

/** A Module compiled with applied Mill-specific compiler plugins: mill-moduledefs. */
@@ -661,6 +632,7 @@ object scalajslib extends MillModule {
@@ -650,6 +621,7 @@ object scalajslib extends MillModule {
}
object worker extends Cross[WorkerModule]("1")
class WorkerModule(scalajsWorkerVersion: String) extends MillInternalModule {
+ override def millSourcePath: os.Path = super.millSourcePath / scalajsWorkerVersion
override def moduleDeps = Seq(scalajslib.`worker-api`)
override def ivyDeps = Agg(
Deps.Scalajs_1.scalajsLinker,
@@ -723,6 +695,7 @@ object contrib extends MillModule {
@@ -712,6 +684,7 @@ object contrib extends MillModule {

object worker extends Cross[WorkerModule](Deps.play.keys.toSeq: _*)
class WorkerModule(playBinary: String) extends MillInternalModule {
+ override def millSourcePath: os.Path = super.millSourcePath / playBinary
override def sources = T.sources {
// We want to avoid duplicating code as long as the Play APIs allow.
// But if newer Play versions introduce incompatibilities,
@@ -925,6 +898,7 @@ object scalanativelib extends MillModule {
@@ -914,6 +887,7 @@ object scalanativelib extends MillModule {
object worker extends Cross[WorkerModule]("0.4")
class WorkerModule(scalaNativeWorkerVersion: String)
extends MillInternalModule {
+ override def millSourcePath: os.Path = super.millSourcePath / scalaNativeWorkerVersion
override def moduleDeps = Seq(scalanativelib.`worker-api`)
override def ivyDeps = scalaNativeWorkerVersion match {
case "0.4" =>
@@ -1576,53 +1550,7 @@ def launcher = T {
@@ -1033,7 +1007,7 @@ def installLocalTask(binFile: Task[String], ivyRepo: String = null): Task[os.Pat
// test it in the `test` CrossModule. We pass `test`'s sources to `lib` to
// and pass `lib`'s compile output back to `test`
trait IntegrationTestCrossModule extends IntegrationTestModule {
-
+ override def millSourcePath = super.millSourcePath / repoSlug
}
trait IntegrationTestModule extends MillScalaModule {
def repoSlug: String
@@ -1645,53 +1619,7 @@ def launcher = T {
}

def uploadToGithub(authKey: String) = T.command {
- val vcsState = VcsVersion.vcsState()
- val label = vcsState.format()
@@ -139,5 +148,5 @@ index 3930b72ff..5cf3ee459 100644
- }
+ // never upload a bootstrapped version
}
def validate(ev: Evaluator): Command[Unit] = T.command {

def validate(ev: Evaluator): Command[Unit] = T.command {
1 change: 1 addition & 0 deletions ci/test-mill-bootstrap-0.sh
Original file line number Diff line number Diff line change
@@ -40,3 +40,4 @@ ci/patch-mill-bootstrap.sh

# Use second build to run tests using Mill
target/mill-2 -i "{main,scalalib,scalajslib,scalanativelib,bsp}.__.test"
target/mill-2 -i "example[1-hello-world].server.test"
1 change: 1 addition & 0 deletions ci/test-mill-bootstrap-1.sh
Original file line number Diff line number Diff line change
@@ -39,4 +39,5 @@ rm -rf ~/.mill/ammonite
ci/patch-mill-bootstrap.sh

# Use second build to run tests using Mill
target/mill-2 -i "example[1-hello-world].server.test"
target/mill-2 -i "contrib.__.test"
1 change: 1 addition & 0 deletions ci/test-mill-dev.sh
Original file line number Diff line number Diff line change
@@ -18,3 +18,4 @@ ci/patch-mill-bootstrap.sh
out/dev/assembly.dest/mill -i -j 0 main.test.compile

out/dev/assembly.dest/mill -i "{main,scalalib,scalajslib,scalanativelib,bsp,contrib.twirllib,contrib.scalapblib}.test"
out/dev/assembly.dest/mill -i "example[1-hello-world].server.test"
3 changes: 2 additions & 1 deletion ci/test-mill-release.sh
Original file line number Diff line number Diff line change
@@ -23,4 +23,5 @@ ci/patch-mill-bootstrap.sh
export MILL_TEST_RELEASE="$(pwd)/target/mill-release"

# Run tests
"$MILL_TEST_RELEASE" -i integration.thirdparty.forked
"$MILL_TEST_RELEASE" -i "example[1-hello-world].server.test"
"$MILL_TEST_RELEASE" -i integration.thirdparty.__.fork.test
Original file line number Diff line number Diff line change
@@ -94,7 +94,7 @@ object ArtifactoryPublishModule extends ExternalModule {
}
}

implicit def millScoptTargetReads[T] = new mill.main.Tasks.Scopt[T]()
import mill.main.TokenReaders._

lazy val millDiscover: mill.define.Discover[this.type] = mill.define.Discover[this.type]
}
Original file line number Diff line number Diff line change
@@ -100,7 +100,7 @@ object BintrayPublishModule extends ExternalModule {
}
}

implicit def millScoptTargetReads[T] = new mill.main.Tasks.Scopt[T]()
import mill.main.TokenReaders._

lazy val millDiscover: mill.define.Discover[this.type] = mill.define.Discover[this.type]
}
Original file line number Diff line number Diff line change
@@ -58,7 +58,7 @@ object CodeartifactPublishModule extends ExternalModule {
)
}

implicit def millScoptTargetReads[T] = new mill.main.Tasks.Scopt[T]()
import mill.main.TokenReaders._

lazy val millDiscover: mill.define.Discover[this.type] =
mill.define.Discover[this.type]
Original file line number Diff line number Diff line change
@@ -79,7 +79,7 @@ object GitlabPublishModule extends ExternalModule {
)
}

implicit def millScoptTargetReads[T] = new mill.main.Tasks.Scopt[T]()
import mill.main.TokenReaders._

lazy val millDiscover: mill.define.Discover[this.type] = mill.define.Discover[this.type]
}
Original file line number Diff line number Diff line change
@@ -141,7 +141,7 @@ trait HelloWorldTests extends utest.TestSuite {

val resultPath = result.path.toIO.getPath.replace("""\""", "/")
val expectedEnd =
"mill/target/workspace/mill/contrib/scoverage/HelloWorldTests/eval/HelloWorld/core/scoverage/data/core/scoverage/data.dest"
"/target/workspace/mill/contrib/scoverage/HelloWorldTests/eval/HelloWorld/core/scoverage/data/core/scoverage/data.dest"

assert(
resultPath.endsWith(expectedEnd),
@@ -186,7 +186,7 @@ trait HelloWorldTests extends utest.TestSuite {
assert(evalCount > 0)
}
}
"test" - {
test("test") - {
"upstreamAssemblyClasspath" - workspaceTest(HelloWorld) { eval =>
val Right((result, evalCount)) =
eval.apply(HelloWorld.core.scoverage.upstreamAssemblyClasspath)
Original file line number Diff line number Diff line change
@@ -90,7 +90,7 @@ object VersionFileModule extends define.ExternalModule {
} yield proc.call()
}

implicit val millScoptTargetReads = new mill.main.Tasks.Scopt[Seq[os.proc]]()
import mill.main.TokenReaders._

lazy val millDiscover: mill.define.Discover[this.type] = mill.define.Discover[this.type]
}
31 changes: 31 additions & 0 deletions example/basic/1-hello-world/build.sc
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>
*/
8 changes: 8 additions & 0 deletions example/basic/1-hello-world/src/Foo.scala
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)
}
}
9 changes: 9 additions & 0 deletions example/basic/2-multi-module/bar/src/Bar.scala
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)
}
}
37 changes: 37 additions & 0 deletions example/basic/2-multi-module/build.sc
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>
*/
10 changes: 10 additions & 0 deletions example/basic/2-multi-module/foo/src/Foo.scala
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)
}
}
44 changes: 44 additions & 0 deletions example/basic/3-nested-modules/build.sc
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>
*/
11 changes: 11 additions & 0 deletions example/basic/3-nested-modules/qux/src/Qux.scala
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)
}
}
5 changes: 5 additions & 0 deletions example/basic/3-nested-modules/wrapper/bar/src/Bar.scala
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")
}
10 changes: 10 additions & 0 deletions example/basic/3-nested-modules/wrapper/foo/src/Foo.scala
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)
}
}
32 changes: 32 additions & 0 deletions example/basic/4-test-suite/build.sc
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
@@ -1,9 +1,9 @@
package foo
import utest._
object ExampleTests extends TestSuite {
object FooTests extends TestSuite {
def tests = Tests {
test("hello") {
val result = Example.hello()
val result = Foo.hello()
assert(result == "Hello World")
result
}
30 changes: 30 additions & 0 deletions example/basic/5-publish-module/build.sc
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")
}
8 changes: 8 additions & 0 deletions example/basic/6-cross-scala-version/bar/src/Bar.scala
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)
}
}
50 changes: 50 additions & 0 deletions example/basic/6-cross-scala-version/build.sc
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"
}
10 changes: 10 additions & 0 deletions example/basic/6-cross-scala-version/foo/src/Foo.scala
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
}
4 changes: 4 additions & 0 deletions example/basic/7-java-modules/bar/src/Bar.java
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;
}
26 changes: 26 additions & 0 deletions example/basic/7-java-modules/build.sc
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
*/
8 changes: 8 additions & 0 deletions example/basic/7-java-modules/foo/src/Foo.java
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"
}
5 changes: 5 additions & 0 deletions example/basic/8-scala-combined/bar/src/Bar.scala
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"))
}
}
}
12 changes: 12 additions & 0 deletions example/basic/8-scala-combined/bar/test/src/BarTests.scala
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
}
}
}
111 changes: 111 additions & 0 deletions example/basic/8-scala-combined/build.sc
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"
}
10 changes: 10 additions & 0 deletions example/basic/8-scala-combined/foo/src/Foo.scala
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)
}
}
12 changes: 12 additions & 0 deletions example/basic/8-scala-combined/foo/test/src/FooTests.scala
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
}
}
}
7 changes: 7 additions & 0 deletions example/basic/8-scala-combined/qux/src/Qux.java
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);
}
}
6 changes: 0 additions & 6 deletions example/example-1/build.sc

This file was deleted.

18 changes: 0 additions & 18 deletions example/example-2/build.sc

This file was deleted.

10 changes: 0 additions & 10 deletions example/example-3/build.sc

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
}
60 changes: 60 additions & 0 deletions example/misc/1-sbt-compat-modules/build.sc
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
}
}
}
47 changes: 47 additions & 0 deletions example/misc/2-dynamic-cross-module/build.sc
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")
}
}
24 changes: 24 additions & 0 deletions example/misc/3-two-level-build/build.sc
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>
*/
7 changes: 7 additions & 0 deletions example/misc/3-two-level-build/foo/src/Example.scala
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") + "?")
}
}
17 changes: 17 additions & 0 deletions example/misc/3-two-level-build/mill-build/build.sc
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"))
}
}
4 changes: 4 additions & 0 deletions integration/failure/compile-error/repo/bar.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def myScalaVersion = "2.13.2"


println(doesntExist)
8 changes: 8 additions & 0 deletions integration/failure/compile-error/repo/build.sc
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
3 changes: 3 additions & 0 deletions integration/failure/compile-error/repo/qux.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def myMsg = "<h1>world</h1>"

def myOtherMsg = myMsg.substring("0")
22 changes: 22 additions & 0 deletions integration/failure/compile-error/test/src/CompileErrorTests.scala
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"""))
}
}
}
Empty file.
Empty file.
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"))
}
}
}
File renamed without changes.
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"))
}
}
}
2 changes: 2 additions & 0 deletions integration/failure/multiple-top-level-modules/repo/build.sc
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$"))
}
}
}
4 changes: 4 additions & 0 deletions integration/failure/parse-error/repo/bar.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def myScalaVersion = "2.13.2"


println(doesntExist})
7 changes: 7 additions & 0 deletions integration/failure/parse-error/repo/build.sc
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
}

3 changes: 3 additions & 0 deletions integration/failure/parse-error/repo/qux.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def myMsg = "<h1>world</h1>"

System.out.println(doesntExist
22 changes: 22 additions & 0 deletions integration/failure/parse-error/test/src/ParseErrorTests.scala
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"""))
}
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package mill.bsp

import mill.util.ScriptTestSuite
package mill.integration
package local
import mill.bsp.Constants
import utest._

object BspInstallDebugTests extends ScriptTestSuite(false) {
override def workspaceSlug: String = "bsp-install"
override def scriptSourcePath: os.Path = os.pwd / "bsp" / "test" / "resources" / workspaceSlug
val bsp4jVersion = sys.props.getOrElse("BSP4J_VERSION", ???)
object BspInstallDebugTests extends IntegrationTestSuite {

val bsp4jVersion = sys.props.getOrElse("BSP4J_VERSION", ???)
// we purposely enable debugging in this simulated test env
override val debugLog: Boolean = true

Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package mill.bsp

import mill.util.ScriptTestSuite
package mill.integration
package local
import mill.bsp.Constants
import utest._

object BspInstallTests extends ScriptTestSuite(false) {
override def workspaceSlug: String = "bsp-install"
override def scriptSourcePath: os.Path = os.pwd / "bsp" / "test" / "resources" / workspaceSlug
object BspInstallTests extends IntegrationTestSuite {
val bsp4jVersion = sys.props.getOrElse("BSP4J_VERSION", ???)

def tests: Tests = Tests {
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package mill.bsp

import mill.util.ScriptTestSuite
package mill.integration
package local
import mill.bsp.Constants
import utest._

object BspModulesTests extends ScriptTestSuite(false) {
override def workspaceSlug: String = "bsp-modules"
override def scriptSourcePath: os.Path = os.pwd / "bsp" / "test" / "resources" / workspaceSlug
object BspModulesTests extends IntegrationTestSuite {
val bsp4jVersion = sys.props.getOrElse("BSP4J_VERSION", ???)

def tests: Tests = Tests {
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -4,8 +4,7 @@ import mill.api.PathRef
import utest._
import utest.framework.TestPath

class CrossTests(fork: Boolean, clientServer: Boolean)
extends IntegrationTestSuite("cross", fork, clientServer) {
object CrossTests extends IntegrationTestSuite {
val tests = Tests {
def testCrossSourcePath(expectedPath: os.SubPath)(implicit tp: TestPath): Unit = {
val target = tp.value.last
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package mill.integration

import mill.util.ScriptTestSuite
import utest._

class DocAnnotationsTests(fork: Boolean, clientServer: Boolean)
extends IntegrationTestSuite("docannotations", fork, clientServer) {
object DocAnnotationsTests extends IntegrationTestSuite {
val tests = Tests {
initWorkspace()
"test" - {
test("test") - {
val res = eval("inspect", "core.test.ivyDeps")
assert(res == true)
val inheritedIvyDeps = ujson.read(meta("inspect"))("value").str
10 changes: 10 additions & 0 deletions integration/feature/dynamic-cross-module/repo/build.sc
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]")
)
}
}
}
10 changes: 10 additions & 0 deletions integration/feature/editing/repo/build.sc
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
)
}
7 changes: 7 additions & 0 deletions integration/feature/editing/repo/foo/src/Example.scala
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") + "!")
}
}
17 changes: 17 additions & 0 deletions integration/feature/editing/repo/mill-build/build.sc
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"))
}
}
16 changes: 16 additions & 0 deletions integration/feature/editing/repo/mill-build/mill-build/build.sc
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"))
}
}
359 changes: 359 additions & 0 deletions integration/feature/editing/test/src/MultiLevelBuildTests.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)

}
}
}
24 changes: 24 additions & 0 deletions integration/feature/foreign/test/src/ForeignBuildsTest.scala
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()
}
}
22 changes: 22 additions & 0 deletions integration/feature/foreign/test/src/ForeignConflictTest.scala
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
@@ -1,17 +1,14 @@
package mill.scalalib
package mill.integration
package local

import scala.util.Try

import mill.util.ScriptTestSuite
import os.Path
import utest._

object GenIdeaExtendedTests extends ScriptTestSuite(false) {
import scala.util.Try

override def workspaceSlug: String = "gen-idea-extended-hello-world"
object GenIdeaExtendedTests extends IntegrationTestSuite {

override def scriptSourcePath: Path =
os.pwd / "scalalib" / "test" / "resources" / workspaceSlug
override def scriptSourcePath = super.scriptSourcePath / "extended"

private val scalaVersionLibPart = "2_13_6"

@@ -35,8 +32,8 @@ object GenIdeaExtendedTests extends ScriptTestSuite(false) {
os.sub / "compiler.xml"
).map { resource =>
Try {
GenIdeaTests.assertIdeaXmlResourceMatchesFile(
workspaceSlug,
GenIdeaUtils.assertIdeaXmlResourceMatchesFile(
scriptSlug,
workspacePath,
resource
)
81 changes: 81 additions & 0 deletions integration/feature/gen-idea/test/src/GenIdeaTests.scala
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))
}
}

}
50 changes: 50 additions & 0 deletions integration/feature/gen-idea/test/src/GenIdeaUtils.scala
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 -->"
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package mill.integration

import mill.util.ScriptTestSuite
import utest._

class HygieneTests(fork: Boolean, clientServer: Boolean)
extends IntegrationTestSuite("hygiene", fork, clientServer) {
object HygieneTests extends IntegrationTestSuite {
val tests = Tests {
initWorkspace()

Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package mill.integration

import mill.util.ScriptTestSuite
import os.Path
import utest._

import scala.collection.mutable

class ScriptsInvalidationForeignTests(fork: Boolean, clientServer: Boolean)
extends IntegrationTestSuite("invalidation-foreign", fork, clientServer) {
object ScriptsInvalidationForeignTests extends IntegrationTestSuite {

override def buildPath = os.sub / "foreignA" / "build.sc"

def runTask(task: String) = {
val (successful, stdout) = evalStdout(task)
assert(successful)
stdout.map(_.trim)
val res = evalStdout(task)
assert(res.isSuccess)
res.out.linesIterator.map(_.trim).toVector
}

val tests = Tests {
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package mill.integration

import mill.util.ScriptTestSuite
import utest._

import scala.collection.mutable

class ScriptsInvalidationTests(fork: Boolean, clientServer: Boolean)
extends IntegrationTestSuite("invalidation", fork, clientServer) {
object ScriptsInvalidationTests extends IntegrationTestSuite {

def runTask(task: String) = {
val (successful, stdout) = evalStdout(task)
assert(successful)
stdout.map(_.trim)
val res = evalStdout(task)
assert(res.isSuccess)
res.out.linesIterator.map(_.trim).toVector
}

val tests = Tests {
13 changes: 13 additions & 0 deletions integration/feature/large-project/test/src/LargeProjectTests.scala
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"))
}
}
}
12 changes: 12 additions & 0 deletions integration/feature/mill-jvm-opts/test/src/MillJvmOptsTests.scala
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"))
}
}
}
Original file line number Diff line number Diff line change
@@ -3,8 +3,7 @@ package mill.integration
import utest._

// Regress test for issue https://github.com/com-lihaoyi/mill/issues/1901
class ZincIncrementalCompilationTests(fork: Boolean, clientServer: Boolean)
extends IntegrationTestSuite("zinc-incremental-compilation", fork, clientServer) {
object ZincIncrementalCompilationTests extends IntegrationTestSuite {
val tests = Tests {
initWorkspace()
"incremental compilation only compiles changed files" - {

This file was deleted.

13 changes: 0 additions & 13 deletions integration/forked/src/mill/integration/forked/forked-tests.scala

This file was deleted.

18 changes: 0 additions & 18 deletions integration/local/src/mill/integration/IntegrationTestSuite.scala

This file was deleted.

15 changes: 0 additions & 15 deletions integration/local/src/mill/integration/LargeProjectTests.scala

This file was deleted.

14 changes: 0 additions & 14 deletions integration/local/src/mill/integration/MillJvmOptsTests.scala

This file was deleted.

12 changes: 0 additions & 12 deletions integration/local/src/mill/integration/local/local-tests.scala

This file was deleted.

60 changes: 60 additions & 0 deletions integration/src/mill/integration/ExampleTestSuite.scala
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))

}
}
}
}
}
174 changes: 174 additions & 0 deletions integration/src/mill/integration/IntegrationTestSuite.scala
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
@@ -28,4 +28,4 @@ class AcyclicModule(val crossScalaVersion: String) extends CrossSbtModule with P
ivy"com.lihaoyi::utest:0.6.0"
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package mill.integration.thirdparty
package mill.integration
package thirdparty

import utest._

class AcyclicTests(fork: Boolean)
extends IntegrationTestSuite("MILL_ACYCLIC_REPO", "acyclic", fork) {
object AcyclicTests extends IntegrationTestSuite {
val tests = Tests {
initWorkspace()

Original file line number Diff line number Diff line change
@@ -174,7 +174,7 @@ object amm extends Cross[MainModule](fullCrossScalaVersions: _*) {
}
}
class MainModule(val crossScalaVersion: String) extends AmmModule
with AmmDependenciesResourceFileModule {
with AmmDependenciesResourceFileModule {

def artifactName = "ammonite"

@@ -293,13 +293,13 @@ def integrationTest(scalaVersion: String = sys.env("TRAVIS_SCALA_VERSION")) = T.
}

def generateConstantsFile(
version: String = buildVersion,
unstableVersion: String = "<fill-me-in-in-Constants.scala>",
curlUrl: String = "<fill-me-in-in-Constants.scala>",
unstableCurlUrl: String = "<fill-me-in-in-Constants.scala>",
oldCurlUrls: Seq[(String, String)] = Nil,
oldUnstableCurlUrls: Seq[(String, String)] = Nil
)(implicit ctx: mill.api.Ctx.Dest) = {
version: String = buildVersion,
unstableVersion: String = "<fill-me-in-in-Constants.scala>",
curlUrl: String = "<fill-me-in-in-Constants.scala>",
unstableCurlUrl: String = "<fill-me-in-in-Constants.scala>",
oldCurlUrls: Seq[(String, String)] = Nil,
oldUnstableCurlUrls: Seq[(String, String)] = Nil
)(implicit ctx: mill.api.Ctx.Dest) = {
val versionTxt = s"""
package ammonite
object Constants{
@@ -312,8 +312,8 @@ def generateConstantsFile(
)
val oldUnstableCurlUrls = Seq[(String, String)](
${oldUnstableCurlUrls.map { case (name, value) => s""" "$name" -> "$value" """ }.mkString(
",\n"
)}
",\n"
)}
)
}
"""
@@ -324,10 +324,10 @@ def generateConstantsFile(
}

def generateDependenciesFile(
scalaVersion: String,
fileName: String,
deps: Seq[coursier.Dependency]
)(implicit ctx: mill.api.Ctx.Dest) = {
scalaVersion: String,
fileName: String,
deps: Seq[coursier.Dependency]
)(implicit ctx: mill.api.Ctx.Dest) = {

val dir = ctx.dest / "extra-resources"
val dest = dir / fileName
@@ -347,4 +347,4 @@ def generateDependenciesFile(
os.write(dest, content.getBytes("UTF-8"), createFolders = true)

dir
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package mill.integration.thirdparty
package mill.integration
package thirdparty

import utest._

import scala.util.Properties

class AmmoniteTests(fork: Boolean)
extends IntegrationTestSuite("MILL_AMMONITE_REPO", "ammonite", fork) {
object AmmoniteTests extends IntegrationTestSuite{
val tests = Tests {
initWorkspace()

Original file line number Diff line number Diff line change
@@ -154,4 +154,4 @@ object simulator extends CaffeineModule {

def ivyDeps = super.ivyDeps() ++ testLibraries.testng
}
}
}
Original file line number Diff line number Diff line change
@@ -131,4 +131,4 @@ object benchmarkLibraries {
val rapidoid = ivy"org.rapidoid:rapidoid-commons:${benchmarkVersions.rapidoid}"
val slf4jNop = ivy"org.slf4j:slf4j-nop:${benchmarkVersions.slf4j}"
val tcache = ivy"com.trivago:triava:${benchmarkVersions.tcache}"
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package mill.integration.thirdparty
package mill.integration
package thirdparty

import utest._

class CaffeineTests(fork: Boolean)
extends IntegrationTestSuite("MILL_CAFFEINE_REPO", "caffeine", fork) {
object CaffeineTests extends IntegrationTestSuite {

override def initWorkspace() = {
val path = super.initWorkspace()
@@ -57,7 +57,7 @@ class CaffeineTests(fork: Boolean)

val tests = Tests {
initWorkspace()
"test" - {
test("test") - {
// Caffeine only can build using Java 9 or up. Java 8 results in weird
// type inference issues during the compile
if (!mill.main.client.Util.isJava9OrAbove) {

This file was deleted.

Original file line number Diff line number Diff line change
@@ -59,4 +59,4 @@ class JawnModule(crossVersion: String) extends mill.Module {
}
object spray extends Support(ivy"io.spray::spray-json:1.3.3")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package mill.integration.thirdparty
package mill.integration
package thirdparty

import mill.util.TestUtil
import utest._

class JawnTests(fork: Boolean) extends IntegrationTestSuite("MILL_JAWN_REPO", "jawn", fork) {
object JawnTests extends IntegrationTestSuite {
val tests = Tests {
initWorkspace()

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -143,4 +143,4 @@ object test extends ScalaModule {
def scalaVersion = scala212Version
def moduleDeps = Seq(upickleJvm(scala212Version))
def sources = T.sources { millSourcePath }
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package mill.integration.thirdparty
package mill.integration
package thirdparty

import utest._

import scala.util.Properties

class UpickleTests(fork: Boolean)
extends IntegrationTestSuite("MILL_UPICKLE_REPO", "upickle", fork) {
object UpickleTests extends ThirdPartyTestSuite("upickle") {
val tests = Tests {
initWorkspace()
"jvm21111" - {
3 changes: 2 additions & 1 deletion main/api/src/mill/api/PathRef.scala
Original file line number Diff line number Diff line change
@@ -20,7 +20,8 @@ case class PathRef private (
revalidate: PathRef.Revalidate
) {

def validate(): Boolean = PathRef.apply(path, quick).sig == sig
def recomputeSig() = PathRef.apply(path, quick).sig
def validate(): Boolean = recomputeSig() == sig

/* Hide case class specific copy method. */
private def copy(
6 changes: 3 additions & 3 deletions main/client/src/mill/main/client/IsolatedMillMainLoader.java
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ public static LoadResult load() {
long startTime = System.currentTimeMillis();
Optional<Method> millMainMethod = Optional.empty();
try {
Class<?> millMainClass = IsolatedMillMainLoader.class.getClassLoader().loadClass("mill.MillMain");
Class<?> millMainClass = IsolatedMillMainLoader.class.getClassLoader().loadClass("mill.runner.MillMain");
Method mainMethod = millMainClass.getMethod("main", String[].class);
millMainMethod = Optional.of(mainMethod);
} catch (ClassNotFoundException | NoSuchMethodException e) {
@@ -58,7 +58,7 @@ public static void runMain(String[] args) throws Exception {
mainMethod.invoke(null, new Object[]{args});
}
} else {
throw new RuntimeException("Cannot load mill.MillMain class");
throw new RuntimeException("Cannot load mill.runner.MillMain class");
}
}

@@ -67,7 +67,7 @@ private static int launchMillAsSubProcess(String[] args) throws Exception {

List<String> l = new ArrayList<>();
l.addAll(MillEnv.millLaunchJvmCommand(setJnaNoSys));
l.add("mill.MillMain");
l.add("mill.runner.MillMain");
l.addAll(Arrays.asList(args));

Process running = new ProcessBuilder()
2 changes: 1 addition & 1 deletion main/client/src/mill/main/client/MillClientMain.java
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ public static final int ExitServerCodeWhenVersionMismatch() {
static void initServer(String lockBase, boolean setJnaNoSys) throws IOException, URISyntaxException {
List<String> l = new ArrayList<>();
l.addAll(MillEnv.millLaunchJvmCommand(setJnaNoSys));
l.add("mill.main.MillServerMain");
l.add("mill.runner.MillServerMain");
l.add(lockBase);

File stdout = new java.io.File(lockBase + "/stdout");
2 changes: 2 additions & 0 deletions main/core/src/mill/define/BaseModule.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package mill.define
import os.Path

object BaseModule {
case class Implicit(value: BaseModule)
@@ -35,6 +36,7 @@ abstract class BaseModule(
override implicit def millModuleBasePath: Ctx.BasePath = Ctx.BasePath(millSourcePath)
implicit def millImplicitBaseModule: BaseModule.Implicit = BaseModule.Implicit(this)
def millDiscover: Discover[this.type]

}

abstract class ExternalModule(implicit
6 changes: 3 additions & 3 deletions main/core/src/mill/define/Graph.scala
Original file line number Diff line number Diff line change
@@ -43,20 +43,20 @@ object Graph {
* including the given targets.
*/
def transitiveTargets(sourceTargets: Agg[Task[_]]): Agg[Task[_]] = {
transitiveNodes(sourceTargets)
transitiveNodes(sourceTargets)(_.inputs)
}

/**
* Collects all transitive dependencies (nodes) of the given nodes,
* including the given nodes.
*/
def transitiveNodes[T <: GraphNode[T]](sourceNodes: Agg[T]): Agg[T] = {
def transitiveNodes[T](sourceNodes: Agg[T])(inputsFor: T => Seq[T]): Agg[T] = {
val transitiveNodes = new Agg.Mutable[T]
def rec(t: T): Unit = {
if (transitiveNodes.contains(t)) {} // do nothing
else {
transitiveNodes.append(t)
t.inputs.foreach(rec)
inputsFor(t).foreach(rec)
}
}

9 changes: 0 additions & 9 deletions main/core/src/mill/define/GraphNode.scala

This file was deleted.

11 changes: 0 additions & 11 deletions main/core/src/mill/define/ScriptNode.scala

This file was deleted.

2 changes: 1 addition & 1 deletion main/core/src/mill/define/Task.scala
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ import scala.reflect.macros.blackbox.Context
* Generally not instantiated manually, but instead constructed via the
* [[Target.apply]] & similar macros.
*/
abstract class Task[+T] extends Task.Ops[T] with Applyable[Task, T] with GraphNode[Task[_]] {
abstract class Task[+T] extends Task.Ops[T] with Applyable[Task, T] {

/**
* What other Targets does this Target depend on?
26 changes: 26 additions & 0 deletions main/core/src/mill/define/Watchable.scala
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()
}
}
Loading