Skip to content

Commit 4c3d2cb

Browse files
Allow users to use a custom output dir via an env var (#3530)
This allows users to change the Mill output directory (by default, `out` under the project root) to a directory of their choice, via the `MILL_OUTPUT_DIR` environment variable. Fixes #3144
1 parent 41ef503 commit 4c3d2cb

File tree

14 files changed

+154
-22
lines changed

14 files changed

+154
-22
lines changed

docs/modules/ROOT/pages/Out_Dir.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,7 @@ This is very useful if Mill is being unexpectedly slow, and you want to find out
111111

112112
`mill-server/*`::
113113
Each Mill server instance needs to keep some temporary files in one of these directories. Deleting it will also terminate the associated server instance, if it is still running.
114+
115+
== Using another location than the `out/` directory
116+
117+
include::example/depth/out-dir/1-custom-out.adoc[]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// The default location for Mill's output directory is `out/` under the project workspace.
2+
// A task `printDest` of a module `foo` will have a default scratch space folder
3+
// `out/foo/printDest.dest/`:
4+
package build
5+
6+
import mill._
7+
8+
object foo extends Module {
9+
def printDest = Task {
10+
println(T.dest)
11+
}
12+
}
13+
14+
/** Usage
15+
> ./mill foo.printDest
16+
...
17+
.../out/foo/printDest.dest
18+
*/
19+
20+
// If you'd rather use another location than `out/`, that lives
21+
// in a faster or a writable filesystem for example, you can change the output directory
22+
// via the `MILL_OUTPUT_DIR` environment variable.
23+
24+
/** Usage
25+
> MILL_OUTPUT_DIR=build-stuff/working-dir ./mill foo.printDest
26+
...
27+
.../build-stuff/working-dir/foo/printDest.dest
28+
*/

example/package.mill

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ object `package` extends RootModule with Module {
5959
object modules extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "modules"))
6060
object cross extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "cross"))
6161
object large extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "large"))
62+
object `out-dir` extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "out-dir"))
6263
object sandbox extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "sandbox"))
6364
object libraries extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "libraries"))
6465
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package build
2+
3+
import mill._
4+
import mill.scalalib._
5+
6+
object `package` extends RootModule with ScalaModule {
7+
def scalaVersion = scala.util.Properties.versionNumberString
8+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package mill.integration
2+
3+
import mill.main.client.{EnvVars, OutFiles}
4+
import mill.testkit.UtestIntegrationTestSuite
5+
import utest._
6+
7+
object OutputDirectoryTests extends UtestIntegrationTestSuite {
8+
9+
def tests: Tests = Tests {
10+
test("Output directory sanity check") - integrationTest { tester =>
11+
import tester._
12+
eval("__.compile").isSuccess ==> true
13+
val defaultOutDir = workspacePath / OutFiles.defaultOut
14+
assert(os.isDir(defaultOutDir))
15+
}
16+
17+
test("Output directory elsewhere in workspace") - integrationTest { tester =>
18+
import tester._
19+
eval(
20+
"__.compile",
21+
env = millTestSuiteEnv + (EnvVars.MILL_OUTPUT_DIR -> "testing/test-out")
22+
).isSuccess ==> true
23+
val expectedOutDir = workspacePath / "testing/test-out"
24+
val defaultOutDir = workspacePath / OutFiles.defaultOut
25+
assert(os.isDir(expectedOutDir))
26+
assert(!os.exists(defaultOutDir))
27+
}
28+
29+
test("Output directory outside workspace") - integrationTest { tester =>
30+
import tester._
31+
val outDir = os.temp.dir() / "tmp-out"
32+
eval(
33+
"__.compile",
34+
env = millTestSuiteEnv + (EnvVars.MILL_OUTPUT_DIR -> outDir.toString)
35+
).isSuccess ==> true
36+
val defaultOutDir = workspacePath / OutFiles.defaultOut
37+
assert(os.isDir(outDir))
38+
assert(!os.exists(defaultOutDir))
39+
}
40+
}
41+
}

main/client/src/mill/main/client/EnvVars.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ public class EnvVars {
2222

2323
public static final String MILL_JVM_OPTS_PATH = "MILL_JVM_OPTS_PATH";
2424

25+
26+
/**
27+
* Output directory where Mill workers' state and Mill tasks output should be
28+
* written to
29+
*/
30+
public static final String MILL_OUTPUT_DIR = "MILL_OUTPUT_DIR";
31+
2532
// INTERNAL ENVIRONMENT VARIABLES
2633
/**
2734
* Used to pass the Mill workspace root from the client to the server, so

main/client/src/mill/main/client/OutFiles.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@
55
* and documentation about what they do
66
*/
77
public class OutFiles {
8+
9+
final private static String envOutOrNull = System.getenv(EnvVars.MILL_OUTPUT_DIR);
10+
11+
/**
12+
* Default hard-coded value for the Mill `out/` folder path. Unless you know
13+
* what you are doing, you should favor using [[out]] instead.
14+
*/
15+
final public static String defaultOut = "out";
16+
817
/**
918
* Path of the Mill `out/` folder
1019
*/
11-
final public static String out = "out";
20+
final public static String out = envOutOrNull == null ? defaultOut : envOutOrNull;
1221

1322
/**
1423
* Path of the Mill "meta-build", used to compile the `build.sc` file so we can

runner/src/mill/runner/CodeGen.scala

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ object CodeGen {
1515
allScriptCode: Map[os.Path, String],
1616
targetDest: os.Path,
1717
enclosingClasspath: Seq[os.Path],
18-
millTopLevelProjectRoot: os.Path
18+
millTopLevelProjectRoot: os.Path,
19+
output: os.Path
1920
): Unit = {
2021
for (scriptSource <- scriptSources) {
2122
val scriptPath = scriptSource.path
@@ -94,6 +95,7 @@ object CodeGen {
9495
projectRoot,
9596
enclosingClasspath,
9697
millTopLevelProjectRoot,
98+
output,
9799
scriptPath,
98100
scriptFolderPath,
99101
childAliases,
@@ -112,6 +114,7 @@ object CodeGen {
112114
projectRoot: os.Path,
113115
enclosingClasspath: Seq[os.Path],
114116
millTopLevelProjectRoot: os.Path,
117+
output: os.Path,
115118
scriptPath: os.Path,
116119
scriptFolderPath: os.Path,
117120
childAliases: String,
@@ -126,7 +129,8 @@ object CodeGen {
126129
segments,
127130
scriptFolderPath,
128131
enclosingClasspath,
129-
millTopLevelProjectRoot
132+
millTopLevelProjectRoot,
133+
output
130134
)
131135

132136
val instrument = new ObjectDataInstrument(scriptCode)
@@ -183,13 +187,15 @@ object CodeGen {
183187
segments: Seq[String],
184188
scriptFolderPath: os.Path,
185189
enclosingClasspath: Seq[os.Path],
186-
millTopLevelProjectRoot: os.Path
190+
millTopLevelProjectRoot: os.Path,
191+
output: os.Path
187192
): String = {
188193
s"""import _root_.mill.runner.MillBuildRootModule
189194
|@_root_.scala.annotation.nowarn
190195
|object MillMiscInfo extends MillBuildRootModule.MillMiscInfo(
191196
| ${enclosingClasspath.map(p => literalize(p.toString))},
192197
| ${literalize(scriptFolderPath.toString)},
198+
| ${literalize(output.toString)},
193199
| ${literalize(millTopLevelProjectRoot.toString)},
194200
| _root_.scala.Seq(${segments.map(pprint.Util.literalize(_)).mkString(", ")})
195201
|)

runner/src/mill/runner/FileImportGraph.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ object FileImportGraph {
4343
* starting from `build.mill`, collecting the information necessary to
4444
* instantiate the [[MillRootModule]]
4545
*/
46-
def parseBuildFiles(topLevelProjectRoot: os.Path, projectRoot: os.Path): FileImportGraph = {
46+
def parseBuildFiles(
47+
topLevelProjectRoot: os.Path,
48+
projectRoot: os.Path,
49+
output: os.Path
50+
): FileImportGraph = {
4751
val seenScripts = mutable.Map.empty[os.Path, String]
4852
val seenIvy = mutable.Set.empty[String]
4953
val seenRepo = mutable.ListBuffer.empty[(String, os.Path)]
@@ -193,7 +197,7 @@ object FileImportGraph {
193197
projectRoot,
194198
followLinks = true,
195199
skip = p =>
196-
p == projectRoot / out ||
200+
p == output ||
197201
p == projectRoot / millBuild ||
198202
(os.isDir(p) && !os.exists(p / nestedBuildFileName))
199203
)

runner/src/mill/runner/MillBuildBootstrap.scala

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import mill.eval.Evaluator
88
import mill.main.RunScript
99
import mill.resolve.SelectMode
1010
import mill.define.{BaseModule, Discover, Segments}
11-
import mill.main.client.OutFiles._
11+
import mill.main.client.OutFiles.{millBuild, millRunnerState}
1212

1313
import java.net.URLClassLoader
1414

@@ -30,6 +30,7 @@ import java.net.URLClassLoader
3030
@internal
3131
class MillBuildBootstrap(
3232
projectRoot: os.Path,
33+
output: os.Path,
3334
home: os.Path,
3435
keepGoing: Boolean,
3536
imports: Seq[String],
@@ -46,15 +47,15 @@ class MillBuildBootstrap(
4647
) {
4748
import MillBuildBootstrap._
4849

49-
val millBootClasspath: Seq[os.Path] = prepareMillBootClasspath(projectRoot / out)
50+
val millBootClasspath: Seq[os.Path] = prepareMillBootClasspath(output)
5051
val millBootClasspathPathRefs: Seq[PathRef] = millBootClasspath.map(PathRef(_, quick = true))
5152

5253
def evaluate(): Watching.Result[RunnerState] = CliImports.withValue(imports) {
5354
val runnerState = evaluateRec(0)
5455

5556
for ((frame, depth) <- runnerState.frames.zipWithIndex) {
5657
os.write.over(
57-
recOut(projectRoot, depth) / millRunnerState,
58+
recOut(output, depth) / millRunnerState,
5859
upickle.default.write(frame.loggedData, indent = 4),
5960
createFolders = true
6061
)
@@ -102,7 +103,8 @@ class MillBuildBootstrap(
102103
} else {
103104
val parsedScriptFiles = FileImportGraph.parseBuildFiles(
104105
projectRoot,
105-
recRoot(projectRoot, depth) / os.up
106+
recRoot(projectRoot, depth) / os.up,
107+
output
106108
)
107109

108110
if (parsedScriptFiles.millImport) evaluateRec(depth + 1)
@@ -111,6 +113,7 @@ class MillBuildBootstrap(
111113
new MillBuildRootModule.BootstrapModule(
112114
projectRoot,
113115
recRoot(projectRoot, depth),
116+
output,
114117
millBootClasspath
115118
)(
116119
mill.main.RootModule.Info(
@@ -340,8 +343,8 @@ class MillBuildBootstrap(
340343
mill.eval.EvaluatorImpl(
341344
home,
342345
projectRoot,
343-
recOut(projectRoot, depth),
344-
recOut(projectRoot, depth),
346+
recOut(output, depth),
347+
recOut(output, depth),
345348
rootModule,
346349
PrefixLogger(logger, "", tickerContext = bootLogPrefix),
347350
classLoaderSigHash = millClassloaderSigHash,
@@ -422,8 +425,8 @@ object MillBuildBootstrap {
422425
projectRoot / Seq.fill(depth)(millBuild)
423426
}
424427

425-
def recOut(projectRoot: os.Path, depth: Int): os.Path = {
426-
projectRoot / out / Seq.fill(depth)(millBuild)
428+
def recOut(output: os.Path, depth: Int): os.Path = {
429+
output / Seq.fill(depth)(millBuild)
427430
}
428431

429432
}

0 commit comments

Comments
 (0)