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

Allow users to pass a custom output dir on the command-line #3530

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions integration/feature/output-directory/resources/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package build

import mill._
import mill.scalalib._

object `package` extends RootModule with ScalaModule {
def scalaVersion = scala.util.Properties.versionNumberString
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package mill.integration

import mill.main.client.OutFiles
import mill.testkit.UtestIntegrationTestSuite
import utest._

object OutputDirectoryTests extends UtestIntegrationTestSuite {

def tests: Tests = Tests {
test("Output directory sanity check") - integrationTest { tester =>
import tester._
eval("__.compile").isSuccess ==> true
val defaultOutDir = workspacePath / OutFiles.defaultOut
assert(os.isDir(defaultOutDir))
}

test("Output directory elsewhere in workspace") - integrationTest { tester =>
import tester._
eval(
"__.compile",
env = millTestSuiteEnv + ("MILL_OUTPUT" -> "testing/test-out")
).isSuccess ==> true
val expectedOutDir = workspacePath / "testing/test-out"
val defaultOutDir = workspacePath / OutFiles.defaultOut
assert(os.isDir(expectedOutDir))
assert(!os.exists(defaultOutDir))
}

test("Output directory elsewhere in workspace via CLI") - integrationTest { tester =>
import tester._
eval(("--output", "testing/test-out", "__.compile")).isSuccess ==> true
val expectedOutDir = workspacePath / "testing/test-out"
val defaultOutDir = workspacePath / OutFiles.defaultOut
assert(os.isDir(expectedOutDir))
// Seems this one is created nonetheless, but is empty
assert(!os.exists(defaultOutDir) || os.list(defaultOutDir).isEmpty)
}

test("Output directory outside workspace") - integrationTest { tester =>
import tester._
val outDir = os.temp.dir() / "tmp-out"
eval(
"__.compile",
env = millTestSuiteEnv + ("MILL_OUTPUT" -> outDir.toString)
).isSuccess ==> true
val defaultOutDir = workspacePath / OutFiles.defaultOut
assert(os.isDir(outDir))
assert(!os.exists(defaultOutDir))
}
}
}
7 changes: 6 additions & 1 deletion main/client/src/mill/main/client/OutFiles.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
* and documentation about what they do
*/
public class OutFiles {

final private static String envOutOrNull = System.getenv("MILL_OUTPUT");

final public static String defaultOut = "out";

/**
* Path of the Mill `out/` folder
*/
final public static String out = "out";
final public static String out = envOutOrNull == null ? defaultOut : envOutOrNull;

/**
* Path of the Mill "meta-build", used to compile the `build.sc` file so we can
Expand Down
12 changes: 9 additions & 3 deletions runner/src/mill/runner/CodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ object CodeGen {
allScriptCode: Map[os.Path, String],
targetDest: os.Path,
enclosingClasspath: Seq[os.Path],
millTopLevelProjectRoot: os.Path
millTopLevelProjectRoot: os.Path,
output: os.Path
): Unit = {
for (scriptSource <- scriptSources) {
val scriptPath = scriptSource.path
Expand Down Expand Up @@ -94,6 +95,7 @@ object CodeGen {
projectRoot,
enclosingClasspath,
millTopLevelProjectRoot,
output,
scriptPath,
scriptFolderPath,
childAliases,
Expand All @@ -112,6 +114,7 @@ object CodeGen {
projectRoot: os.Path,
enclosingClasspath: Seq[os.Path],
millTopLevelProjectRoot: os.Path,
output: os.Path,
scriptPath: os.Path,
scriptFolderPath: os.Path,
childAliases: String,
Expand All @@ -126,7 +129,8 @@ object CodeGen {
segments,
scriptFolderPath,
enclosingClasspath,
millTopLevelProjectRoot
millTopLevelProjectRoot,
output
)

val instrument = new ObjectDataInstrument(scriptCode)
Expand Down Expand Up @@ -182,13 +186,15 @@ object CodeGen {
segments: Seq[String],
scriptFolderPath: os.Path,
enclosingClasspath: Seq[os.Path],
millTopLevelProjectRoot: os.Path
millTopLevelProjectRoot: os.Path,
output: os.Path
): String = {
s"""import _root_.mill.runner.MillBuildRootModule
|@_root_.scala.annotation.nowarn
|object MillMiscInfo extends MillBuildRootModule.MillMiscInfo(
| ${enclosingClasspath.map(p => literalize(p.toString))},
| ${literalize(scriptFolderPath.toString)},
| ${literalize(output.toString)},
| ${literalize(millTopLevelProjectRoot.toString)},
| _root_.scala.Seq(${segments.map(pprint.Util.literalize(_)).mkString(", ")})
|)
Expand Down
8 changes: 6 additions & 2 deletions runner/src/mill/runner/FileImportGraph.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ object FileImportGraph {
* starting from `build.mill`, collecting the information necessary to
* instantiate the [[MillRootModule]]
*/
def parseBuildFiles(topLevelProjectRoot: os.Path, projectRoot: os.Path): FileImportGraph = {
def parseBuildFiles(
topLevelProjectRoot: os.Path,
projectRoot: os.Path,
output: os.Path
): FileImportGraph = {
val seenScripts = mutable.Map.empty[os.Path, String]
val seenIvy = mutable.Set.empty[String]
val seenRepo = mutable.ListBuffer.empty[(String, os.Path)]
Expand Down Expand Up @@ -193,7 +197,7 @@ object FileImportGraph {
projectRoot,
followLinks = true,
skip = p =>
p == projectRoot / out ||
p == output ||
p == projectRoot / millBuild ||
(os.isDir(p) && !os.exists(p / nestedBuildFileName))
)
Expand Down
19 changes: 11 additions & 8 deletions runner/src/mill/runner/MillBuildBootstrap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import mill.eval.Evaluator
import mill.main.RunScript
import mill.resolve.SelectMode
import mill.define.{BaseModule, Discover, Segments}
import mill.main.client.OutFiles._
import mill.main.client.OutFiles.{millBuild, millRunnerState}

import java.net.URLClassLoader

Expand All @@ -30,6 +30,7 @@ import java.net.URLClassLoader
@internal
class MillBuildBootstrap(
projectRoot: os.Path,
output: os.Path,
home: os.Path,
keepGoing: Boolean,
imports: Seq[String],
Expand All @@ -46,15 +47,15 @@ class MillBuildBootstrap(
) {
import MillBuildBootstrap._

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

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

for ((frame, depth) <- runnerState.frames.zipWithIndex) {
os.write.over(
recOut(projectRoot, depth) / millRunnerState,
recOut(output, depth) / millRunnerState,
upickle.default.write(frame.loggedData, indent = 4),
createFolders = true
)
Expand Down Expand Up @@ -102,7 +103,8 @@ class MillBuildBootstrap(
} else {
val parsedScriptFiles = FileImportGraph.parseBuildFiles(
projectRoot,
recRoot(projectRoot, depth) / os.up
recRoot(projectRoot, depth) / os.up,
output
)

if (parsedScriptFiles.millImport) evaluateRec(depth + 1)
Expand All @@ -111,6 +113,7 @@ class MillBuildBootstrap(
new MillBuildRootModule.BootstrapModule(
projectRoot,
recRoot(projectRoot, depth),
output,
millBootClasspath
)(
mill.main.RootModule.Info(
Expand Down Expand Up @@ -340,8 +343,8 @@ class MillBuildBootstrap(
mill.eval.EvaluatorImpl(
home,
projectRoot,
recOut(projectRoot, depth),
recOut(projectRoot, depth),
recOut(output, depth),
recOut(output, depth),
rootModule,
PrefixLogger(logger, "", tickerContext = bootLogPrefix),
classLoaderSigHash = millClassloaderSigHash,
Expand Down Expand Up @@ -422,8 +425,8 @@ object MillBuildBootstrap {
projectRoot / Seq.fill(depth)(millBuild)
}

def recOut(projectRoot: os.Path, depth: Int): os.Path = {
projectRoot / out / Seq.fill(depth)(millBuild)
def recOut(output: os.Path, depth: Int): os.Path = {
output / Seq.fill(depth)(millBuild)
}

}
11 changes: 9 additions & 2 deletions runner/src/mill/runner/MillBuildRootModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ abstract class MillBuildRootModule()(implicit
parsed.seenScripts,
T.dest,
millBuildRootModuleInfo.enclosingClasspath,
millBuildRootModuleInfo.topLevelProjectRoot
millBuildRootModuleInfo.topLevelProjectRoot,
millBuildRootModuleInfo.output
)
Result.Success(Seq(PathRef(T.dest)))
}
Expand Down Expand Up @@ -265,12 +266,14 @@ object MillBuildRootModule {
class BootstrapModule(
topLevelProjectRoot0: os.Path,
projectRoot: os.Path,
output: os.Path,
enclosingClasspath: Seq[os.Path]
)(implicit baseModuleInfo: RootModule.Info) extends MillBuildRootModule()(
implicitly,
MillBuildRootModule.Info(
enclosingClasspath,
projectRoot,
output,
topLevelProjectRoot0
)
) {
Expand All @@ -281,25 +284,29 @@ object MillBuildRootModule {
case class Info(
enclosingClasspath: Seq[os.Path],
projectRoot: os.Path,
output: os.Path,
topLevelProjectRoot: os.Path
)

def parseBuildFiles(millBuildRootModuleInfo: MillBuildRootModule.Info): FileImportGraph = {
FileImportGraph.parseBuildFiles(
millBuildRootModuleInfo.topLevelProjectRoot,
millBuildRootModuleInfo.projectRoot / os.up
millBuildRootModuleInfo.projectRoot / os.up,
millBuildRootModuleInfo.output
)
}

class MillMiscInfo(
enclosingClasspath: Seq[String],
projectRoot: String,
output: String,
topLevelProjectRoot: String,
segments: Seq[String]
) {
implicit lazy val millBuildRootModuleInfo: MillBuildRootModule.Info = MillBuildRootModule.Info(
enclosingClasspath.map(os.Path(_)),
os.Path(projectRoot),
os.Path(output),
os.Path(topLevelProjectRoot)
)
implicit lazy val millBaseModuleInfo: RootModule.Info = RootModule.Info(
Expand Down
8 changes: 8 additions & 0 deletions runner/src/mill/runner/MillCliConfig.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mill.runner

import mainargs.{Flag, Leftover, arg}
import mill.api.WorkspaceRoot
import mill.main.client.OutFiles

case class MillCliConfig(
@deprecated("No longer used", "Mill 0.12.0")
Expand All @@ -11,6 +13,12 @@ case class MillCliConfig(
"""(internal) The home directory where Mill looks for config and caches."""
)
home: os.Path = mill.api.Ctx.defaultHome,
@arg(
hidden = true,
doc =
"""(internal) The directory to write Mill tasks output to."""
)
output: os.Path = os.Path(OutFiles.out, WorkspaceRoot.workspaceRoot),
// We need to keep it, otherwise, a given --repl would be silently parsed as target and result in misleading error messages.
// Instead we fail programmatically when this flag is set.
@deprecated("No longer supported.", "Mill 0.11.0-M8")
Expand Down
2 changes: 2 additions & 0 deletions runner/src/mill/runner/MillMain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import mill.java9rtexport.Export
import mill.api.{MillException, SystemStreams, WorkspaceRoot, internal}
import mill.bsp.{BspContext, BspServerResult}
import mill.main.BuildInfo
import mill.main.client.OutFiles
import mill.util.PrintLogger

import java.lang.reflect.InvocationTargetException
Expand Down Expand Up @@ -230,6 +231,7 @@ object MillMain {

new MillBuildBootstrap(
projectRoot = WorkspaceRoot.workspaceRoot,
output = config.output,
home = config.home,
keepGoing = config.keepGoing.value,
imports = config.imports,
Expand Down
2 changes: 1 addition & 1 deletion testkit/src/mill/testkit/IntegrationTester.scala
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ object IntegrationTester {
)
}

private val millTestSuiteEnv = Map("MILL_TEST_SUITE" -> this.getClass().toString())
def millTestSuiteEnv = Map("MILL_TEST_SUITE" -> this.getClass().toString())

/**
* Helpers to read the `.json` metadata files belonging to a particular task
Expand Down
19 changes: 15 additions & 4 deletions testkit/src/mill/testkit/IntegrationTesterBase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,32 @@ trait IntegrationTesterBase {
os.makeDir.all(workspacePath)
Retry() {
val tmp = os.temp.dir()
if (os.exists(workspacePath / out)) os.move.into(workspacePath / out, tmp)
if (os.exists(os.Path(out, workspacePath))) os.move.into(os.Path(out, workspacePath), tmp)
os.remove.all(tmp)
}

os.list(workspacePath).foreach(os.remove.all(_))
os.list(workspaceSourcePath).filter(_.last != out).foreach(os.copy.into(_, workspacePath))
val outRelPathOpt = os.FilePath(out) match {
case relPath: os.RelPath if relPath.ups == 0 => Some(relPath)
case _ => None
}
os.list(workspaceSourcePath)
.filter(
outRelPathOpt match {
case None => _ => true
case Some(outRelPath) => !_.endsWith(outRelPath)
}
)
.foreach(os.copy.into(_, workspacePath))
}

/**
* Remove any ID files to try and force them to exit
*/
def removeServerIdFile(): Unit = {
if (os.exists(workspacePath / out)) {
if (os.exists(os.Path(out, workspacePath))) {
val serverIdFiles = for {
outPath <- os.list.stream(workspacePath / out)
outPath <- os.list.stream(os.Path(out, workspacePath))
if outPath.last.startsWith(millWorker)
} yield outPath / serverId

Expand Down
Loading