Skip to content

Commit f8bfd48

Browse files
authored
Add --no-filesystem-checker flag to globally disable filesystem read/write checks (#5278)
Fixes #5248 I kept all the `os.Checker`s in place and made them check a threadlocal to see whether to perform a check or not, rather than swapping out the `Checker` based on the flag. This is necessary to support cases like `BuildFileCls` where plumbing in the flag is not easy. Updated the `os-checker` integration tests to cover this, and mentioned it in the sandbox example tests
1 parent 5f8efbf commit f8bfd48

File tree

8 files changed

+74
-42
lines changed

8 files changed

+74
-42
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package mill.api
2+
3+
object FilesystemCheckerEnabled extends scala.util.DynamicVariable(true)

core/define/src/mill/define/internal/ResolveChecker.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ package mill.define.internal
33
class ResolveChecker(workspace: os.Path) extends os.Checker {
44
def onRead(path: os.ReadablePath): Unit = {
55
path match {
6-
case path: os.Path =>
6+
case path: os.Path if mill.api.FilesystemCheckerEnabled.value =>
77
sys.error(s"Reading from ${path.relativeTo(workspace)} not allowed during resolution phase")
88
case _ =>
99
}
1010
}
1111

1212
def onWrite(path: os.Path): Unit = {
13-
sys.error(s"Writing to ${path.relativeTo(workspace)} not allowed during resolution phase")
13+
if (mill.api.FilesystemCheckerEnabled.value) {
14+
sys.error(s"Writing to ${path.relativeTo(workspace)} not allowed during resolution phase")
15+
}
1416
}
1517

1618
def withResolveChecker[T](f: () => T): T = {

core/exec/src/mill/exec/GroupExecution.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ private trait GroupExecution {
3535
def getEvaluator: () => EvaluatorApi
3636
def headerData: String
3737
def offline: Boolean
38+
3839
lazy val parsedHeaderData: Map[String, ujson.Value] = {
3940
import org.snakeyaml.engine.v2.api.{Load, LoadSettings}
4041
val loaded = new Load(LoadSettings.builder().build()).loadFromString(headerData)
@@ -536,7 +537,7 @@ private object GroupExecution {
536537
val executionChecker = new os.Checker {
537538
def onRead(path: os.ReadablePath): Unit = path match {
538539
case path: os.Path =>
539-
if (!isCommand && !isInput) {
540+
if (!isCommand && !isInput && mill.api.FilesystemCheckerEnabled.value) {
540541
if (path.startsWith(workspace) && !validReadDests.exists(path.startsWith(_))) {
541542
sys.error(
542543
s"Reading from ${path.relativeTo(workspace)} not allowed during execution of `$terminal`"
@@ -547,7 +548,7 @@ private object GroupExecution {
547548
}
548549

549550
def onWrite(path: os.Path): Unit = {
550-
if (!isCommand) {
551+
if (!isCommand && mill.api.FilesystemCheckerEnabled.value) {
551552
if (path.startsWith(workspace) && !validWriteDests.exists(path.startsWith(_))) {
552553
sys.error(
553554
s"Writing to ${path.relativeTo(workspace)} not allowed during execution of `$terminal`"

example/depth/sandbox/1-task/build.mill

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ def bannedWriteTaskOverridden = Task {
8383
hello
8484
*/
8585

86+
// You can also disable it globally by passing in `--no-filesystem-checker` to Mill:
87+
88+
/** Usage
89+
> ./mill --no-filesystem-checker bannedReadTask
90+
*/
91+
8692
//== `os.pwd` redirection
8793

8894
// === Task `os.pwd` redirection

integration/failure/os-checker/resources/build.mill

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@ def baz = Task { 1 }
1717
if (false) {
1818
os.write(moduleDir / "file.txt", "hello", createFolders = true)
1919
}
20+
21+
object allowed extends Module {
22+
os.write(moduleDir / "file.txt", "hello", createFolders = true)
23+
def allowedTask = Task {}
24+
}

integration/failure/os-checker/src/OsCheckerTests.scala

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ object OsCheckerTests extends UtestIntegrationTestSuite {
1111
val res = tester.eval("foo.bar")
1212

1313
assert(res.isSuccess == false)
14-
assert(res.err.contains(
15-
s"Writing to foo not allowed during resolution phase"
16-
))
14+
assert(res.err.contains(s"Writing to foo not allowed during resolution phase"))
1715

1816
val res2 = tester.eval("qux")
1917

2018
assert(res2.isSuccess == false)
21-
assert(res2.err.contains(
22-
s"Writing to file.txt not allowed during execution of `qux`"
23-
))
19+
assert(res2.err.contains(s"Writing to file.txt not allowed during execution of `qux`"))
20+
21+
val res2allowed = tester.eval(("--no-filesystem-checker", "qux"))
22+
23+
assert(res2allowed.isSuccess)
2424

2525
tester.modifyFile(workspacePath / "build.mill", _.replace("if (false)", "if (true)"))
2626
val res3 = tester.eval("baz")
@@ -35,12 +35,16 @@ object OsCheckerTests extends UtestIntegrationTestSuite {
3535
workspacePath / "build.mill",
3636
_ + "\nprintln(os.read(mill.define.BuildCtx.workspaceRoot / \"build.mill\"))"
3737
)
38+
39+
val res4allowed = tester.eval(("--no-filesystem-checker", "allowed.allowedTask"))
40+
41+
assert(res4allowed.isSuccess)
42+
43+
tester.eval("shutdown")
3844
val res4 = tester.eval("baz")
3945

4046
assert(res4.isSuccess == false)
41-
assert(res4.err.contains(
42-
s"Reading from build.mill not allowed during resolution phase"
43-
))
47+
assert(res4.err.contains(s"Reading from build.mill not allowed during resolution phase"))
4448

4549
}
4650
}

runner/daemon/src/mill/daemon/MillCliConfig.scala

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,22 @@ case class MillCliConfig(
165165
)
166166
noWaitForBuildLock: Flag = Flag(),
167167
@arg(
168-
doc =
169-
"""Try to work offline.
170-
|This tells modules that support it to work offline and avoid any access to the internet.
171-
|This is on a best effort basis.
172-
|There are currently no guarantees that modules don't attempt to fetch remote sources."""
173-
.stripMargin
168+
doc = """
169+
Try to work offline.
170+
This tells modules that support it to work offline and avoid any access to the internet.
171+
This is on a best effort basis.
172+
There are currently no guarantees that modules don't attempt to fetch remote sources.
173+
"""
174174
)
175175
offline: Flag = Flag(),
176+
@arg(
177+
doc = """
178+
Globally disables the checks that prevent you from reading and writing to disallowed
179+
files or folders during evaluation. Useful as an escape hatch in case you desperately
180+
need to do something unusual and you are willing to take the risk
181+
"""
182+
)
183+
noFilesystemChecker: Flag = Flag(),
176184
@arg(
177185
doc = """Runs Mill in tab-completion mode"""
178186
)

runner/daemon/src/mill/daemon/MillMain0.scala

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -246,30 +246,33 @@ object MillMain0 {
246246
// ensure all tasks re-run even though no inputs may have changed
247247
if (enterKeyPressed) os.remove(out / OutFiles.millSelectiveExecution)
248248
mill.define.SystemStreams.withStreams(logger.streams) {
249-
tailManager.withOutErr(logger.streams.out, logger.streams.err) {
250-
251-
new MillBuildBootstrap(
252-
projectRoot = BuildCtx.workspaceRoot,
253-
output = out,
254-
// In BSP server, we want to evaluate as many tasks as possible,
255-
// in order to give as many results as available in BSP responses
256-
keepGoing = bspMode || config.keepGoing.value,
257-
imports = config.imports,
258-
env = env,
259-
ec = ec,
260-
targetsAndParams = targetsAndParams,
261-
prevRunnerState = prevState.getOrElse(stateCache),
262-
logger = logger,
263-
needBuildFile = needBuildFile(config),
264-
requestedMetaLevel = config.metaLevel,
265-
config.allowPositional.value,
266-
systemExit = systemExit,
267-
streams0 = streams,
268-
selectiveExecution = config.watch.value,
269-
offline = config.offline.value
270-
).evaluate()
249+
mill.api.FilesystemCheckerEnabled.withValue(
250+
!config.noFilesystemChecker.value
251+
) {
252+
tailManager.withOutErr(logger.streams.out, logger.streams.err) {
253+
254+
new MillBuildBootstrap(
255+
projectRoot = BuildCtx.workspaceRoot,
256+
output = out,
257+
// In BSP server, we want to evaluate as many tasks as possible,
258+
// in order to give as many results as available in BSP responses
259+
keepGoing = bspMode || config.keepGoing.value,
260+
imports = config.imports,
261+
env = env,
262+
ec = ec,
263+
targetsAndParams = targetsAndParams,
264+
prevRunnerState = prevState.getOrElse(stateCache),
265+
logger = logger,
266+
needBuildFile = needBuildFile(config),
267+
requestedMetaLevel = config.metaLevel,
268+
config.allowPositional.value,
269+
systemExit = systemExit,
270+
streams0 = streams,
271+
selectiveExecution = config.watch.value,
272+
offline = config.offline.value
273+
).evaluate()
274+
}
271275
}
272-
273276
}
274277
}
275278

0 commit comments

Comments
 (0)