Skip to content

Commit

Permalink
Add docs for how to break out of Mill sandbox folder (#3518)
Browse files Browse the repository at this point in the history
Fixes #3510

I decided to try and standardize on a `MILL_WORKSPACE_ROOT` environment
variable that is present in both build files and tests, to try and
provide a seamless experience to our users.

`mill.api.WorkspaceRoot.workspaceRoot` and `T.workspace` are still there
but perhaps we can get rid of them in 0.13.0 in favor of using
`MILL_WORKSPACE_ROOT` everywhere. After all, accessing the workspace
root should be inconvenient to discourage people from doing it
unnecessarily, so providing short concise helper methods to do so may
even be a net negative
  • Loading branch information
lihaoyi authored Sep 12, 2024
1 parent a02a3c6 commit 719146c
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 15 deletions.
4 changes: 4 additions & 0 deletions docs/modules/ROOT/pages/Mill_Sandboxing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ include::example/depth/sandbox/1-task.adoc[]

include::example/depth/sandbox/2-test.adoc[]

== Breaking Out Of Sandbox Folders

include::example/depth/sandbox/3-breaking.adoc[]

== Limitations

Mill's approach to filesystem sandboxing is designed to avoid accidental interference
Expand Down
4 changes: 2 additions & 2 deletions example/depth/sandbox/2-test/bar/test/src/bar/BarTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public void simple() throws Exception {
String result = Bar.generateHtml("world");
Path path = Paths.get("generated.html");
Files.write(path, result.getBytes());
assertEquals("<p>world</p>", new String(Files.readAllBytes(path)));
assertEquals("<p>world</p>", Files.readString(path));
}
}
}
4 changes: 2 additions & 2 deletions example/depth/sandbox/2-test/foo/test/src/foo/FooTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public void simple() throws Exception {
String result = Foo.generateHtml("hello");
Path path = Paths.get("generated.html");
Files.write(path, result.getBytes());
assertEquals("<h1>hello</h1>", new String(Files.readAllBytes(path)));
assertEquals("<h1>hello</h1>", Files.readString(path));
}
}
}
45 changes: 45 additions & 0 deletions example/depth/sandbox/3-breaking/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Mill's sandboxing approach is best effort: while it tries to guide you into using
// isolated sandbox folders, Mill cannot guarantee it, and in fact provides the
// `MILL_WORKSPACE_ROOT` environment variable to reference the project root folder
// for scenarios where you may need it. This can be useful for a variety of reasons:
//
// * Migrating legacy applications that assume access to the workspace root
// * Scenarios where writing the the original source repository is necessary:
// code auto-formatters, auto-fixers, auto-updaters. etc.
//
// `MILL_WORKSPACE_ROOT` can be used both in tasks:
package build
import mill._, javalib._

def tWorkspaceTask = T { println(os.Path(sys.env("MILL_WORKSPACE_ROOT"))) }

/** Usage
> ./mill tWorkspaceTask
*/

// `MILL_WORKSPACE_ROOT` as well as in tests:

object foo extends JavaModule{
object test extends JavaTests with TestModule.Junit4
}

/** See Also: foo/src/foo/Foo.java */
/** See Also: foo/test/src/foo/FooTests.java */

// Test suites can access the workspace root via the `MILL_WORKSPACE_ROOT`
// environment variable:

/** Usage
> ./mill __.test
*/

/** Usage

> find . | grep .html
...
.../out/foo/test/test.dest/sandbox/foo.html

> cat out/foo/test/test.dest/sandbox/foo.html
<h1>foo</h1>

*/
8 changes: 8 additions & 0 deletions example/depth/sandbox/3-breaking/foo/src/foo/Foo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package foo;

public class Foo {
public static String generateHtml(String text) {
return "<h1>" + text + "</h1>";
}
}

23 changes: 23 additions & 0 deletions example/depth/sandbox/3-breaking/foo/test/src/foo/FooTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package foo;

import java.util.stream.Collectors;
import java.nio.file.*;
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class FooTests {
@Test
public void simple() throws Exception {
String workspaceRoot = System.getenv("MILL_WORKSPACE_ROOT");

for(Path subpath: Files.list(Paths.get(workspaceRoot)).collect(Collectors.toList())){
String result = Foo.generateHtml(subpath.getFileName().toString());
Path tmppath = Paths.get(subpath.getFileName() + ".html");
Files.write(tmppath, result.getBytes());
assertEquals(
"<h1>" + subpath.getFileName() + "</h1>",
Files.readString(tmppath)
);
}
}
}
8 changes: 4 additions & 4 deletions example/javalib/module/5-resources/foo/test/src/FooTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public void simple() throws IOException {

// Use `MILL_TEST_RESOURCE_FOLDER` to read `test-file-b.txt` from filesystem
Path testFileResourceDir = Paths.get(System.getenv("MILL_TEST_RESOURCE_FOLDER"));
String testFileResourceText = new String(
Files.readAllBytes(testFileResourceDir.resolve("test-file-b.txt"))
String testFileResourceText = Files.readString(
testFileResourceDir.resolve("test-file-b.txt")
);
assertEquals("Test Hello World Resource File B", testFileResourceText);

Expand All @@ -44,8 +44,8 @@ public void simple() throws IOException {

// Use the `OTHER_FILES_FOLDER` configured in your build to access the
// files in `foo/test/other-files/`.
String otherFileText = new String(
Files.readAllBytes(Paths.get(System.getenv("OTHER_FILES_FOLDER"), "other-file.txt"))
String otherFileText = Files.readString(
Paths.get(System.getenv("OTHER_FILES_FOLDER"), "other-file.txt")
);
assertEquals("Other Hello World File", otherFileText);
}
Expand Down
6 changes: 5 additions & 1 deletion main/client/src/mill/main/client/EnvVars.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class EnvVars {
*/
public static final String MILL_TEST_RESOURCE_FOLDER = "MILL_TEST_RESOURCE_FOLDER";


/**
* How long the Mill background server should run before timing out from inactivity
*/
Expand All @@ -24,7 +25,10 @@ public class EnvVars {
// INTERNAL ENVIRONMENT VARIABLES
/**
* Used to pass the Mill workspace root from the client to the server, so
* the server code can access it despite it not being os.pwd
* the server code can access it despite it not being os.pwd.
*
* Also available in test modules for users to find the root folder of the
* mill project on disk. Not intended for common usage, but sometimes necessary.
*/
public static final String MILL_WORKSPACE_ROOT = "MILL_WORKSPACE_ROOT";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ public void run() {
while (true) {
try{
Thread.sleep(50);
String token = new String(
java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(watched))
);
String token = java.nio.file.Files.readString(java.nio.file.Paths.get(watched));
if (!token.equals(expected)) {
new java.io.File(tombstone).createNewFile();
System.exit(0);
Expand Down
6 changes: 4 additions & 2 deletions scalalib/src/mill/scalalib/TestModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,10 @@ trait TestModule
val mainArgs = Seq(testRunnerClasspathArg, argsFile.toString)

os.makeDir(T.dest / "sandbox")
val resourceEnv =
Map(EnvVars.MILL_TEST_RESOURCE_FOLDER -> resources().map(_.path).mkString(";"))
val resourceEnv = Map(
EnvVars.MILL_TEST_RESOURCE_FOLDER -> resources().map(_.path).mkString(";"),
EnvVars.MILL_WORKSPACE_ROOT -> T.workspace.toString
)
Jvm.runSubprocess(
mainClass = "mill.testrunner.entrypoint.TestRunnerMain",
classPath =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,11 @@ trait TestScalaNativeModule extends ScalaNativeModule with TestModule {

val (close, framework) = scalaNativeBridge().getFramework(
nativeLink().toIO,
forkEnv() ++ Map(EnvVars.MILL_TEST_RESOURCE_FOLDER -> resources().map(_.path).mkString(";")),
forkEnv() ++
Map(
EnvVars.MILL_TEST_RESOURCE_FOLDER -> resources().map(_.path).mkString(";"),
EnvVars.MILL_WORKSPACE_ROOT -> T.workspace.toString
),
toWorkerApi(logLevel()),
testFramework()
)
Expand Down

0 comments on commit 719146c

Please sign in to comment.