Skip to content

Commit e1bc941

Browse files
committed
Moved new API to MappedRoots object
1 parent 57ede10 commit e1bc941

File tree

15 files changed

+163
-148
lines changed

15 files changed

+163
-148
lines changed

core/api/src/mill/api/JsonFormatters.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ trait JsonFormatters {
2323

2424
implicit val pathReadWrite: RW[os.Path] = upickle.readwriter[String]
2525
.bimap[os.Path](
26-
p => PathRef.encodeKnownRootsInPath(p),
27-
s => os.Path(PathRef.decodeKnownRootsInPath(s))
26+
p => MappedRoots.encodeKnownRootsInPath(p),
27+
s => os.Path(MappedRoots.decodeKnownRootsInPath(s))
2828
)
2929

3030
implicit val relPathRW: RW[os.RelPath] = upickle.readwriter[String]
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package mill.api
2+
3+
import mill.constants.PathVars
4+
5+
import scala.util.DynamicVariable
6+
7+
trait MappedRoots {
8+
9+
private type MappedRoots = Seq[(key: String, path: os.Path)]
10+
11+
private val rootMapping: DynamicVariable[MappedRoots] = DynamicVariable(Seq())
12+
13+
def get: MappedRoots = rootMapping.value
14+
15+
def toMap: Map[String, os.Path] = get.map(m => (m.key, m.path)).toMap
16+
17+
def withMillDefaults[T](
18+
outPath: os.Path,
19+
workspacePath: os.Path = BuildCtx.workspaceRoot,
20+
homePath: os.Path = os.home
21+
)(thunk: => T): T = withMapping(
22+
Seq(
23+
("MILL_OUT", outPath),
24+
("WORKSPACE", workspacePath),
25+
// TODO: add coursier here
26+
("HOME", homePath)
27+
)
28+
)(thunk)
29+
30+
def withMapping[T](mapping: MappedRoots)(thunk: => T): T = withMapping(_ => mapping)(thunk)
31+
32+
def withMapping[T](mapping: MappedRoots => MappedRoots)(thunk: => T): T = {
33+
val newMapping = mapping(rootMapping.value)
34+
var seenKeys = Set[String]()
35+
var seenPaths = Set[os.Path]()
36+
newMapping.foreach { case m =>
37+
require(!m.key.startsWith("$"), "Key must not start with a `$`.")
38+
require(m.key != PathVars.ROOT, s"Invalid key, '${PathVars.ROOT}' is a reserved key name.")
39+
require(
40+
!seenKeys.contains(m.key),
41+
s"Key must be unique, but '${m.key}' was given multiple times."
42+
)
43+
require(
44+
!seenPaths.contains(m.path),
45+
s"Paths must be unique, but '${m.path}' was given multiple times."
46+
)
47+
seenKeys += m.key
48+
seenPaths += m.path
49+
}
50+
rootMapping.withValue(newMapping)(thunk)
51+
}
52+
53+
54+
def encodeKnownRootsInPath(p: os.Path): String = {
55+
MappedRoots.get.collectFirst {
56+
case rep if p.startsWith(rep.path) =>
57+
s"$$${rep.key}${
58+
if (p != rep.path) {
59+
s"/${p.subRelativeTo(rep.path).toString()}"
60+
} else ""
61+
}"
62+
}.getOrElse(p.toString)
63+
}
64+
65+
def decodeKnownRootsInPath(encoded: String): String = {
66+
if (encoded.startsWith("$")) {
67+
val offset = 1 // "$".length
68+
MappedRoots.get.collectFirst {
69+
case mapping if encoded.startsWith(mapping.key, offset) =>
70+
s"${mapping.path.toString}${encoded.substring(mapping.key.length + offset)}"
71+
}.getOrElse(encoded)
72+
} else {
73+
encoded
74+
}
75+
}
76+
77+
}
78+
79+
object MappedRoots extends MappedRoots

core/api/src/mill/api/PathRef.scala

Lines changed: 12 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package mill.api
22

33
import mill.api.DummyOutputStream
44
import mill.api.daemon.internal.PathRefApi
5-
import mill.constants.PathVars
65
import upickle.ReadWriter as RW
76

87
import java.nio.file as jnio
@@ -14,9 +13,9 @@ import scala.util.DynamicVariable
1413
import scala.util.hashing.MurmurHash3
1514

1615
/**
17-
* A wrapper around `os.Path` that calculates it's hashcode based
18-
* on the contents of the filesystem underneath it. Used to ensure filesystem
19-
* changes can bust caches which are keyed off hashcodes.
16+
* A wrapper around `os.Path` that calculates a `sig` (which ends up in the [[hashCode]])
17+
* based on the contents of the filesystem underneath it.
18+
* Used to ensure filesystem changes can bust caches which are keyed off hashcodes.
2019
*/
2120
case class PathRef private[mill] (
2221
path: os.Path,
@@ -26,11 +25,15 @@ case class PathRef private[mill] (
2625
) extends PathRefApi {
2726
private[mill] def javaPath = path.toNIO
2827

29-
private[mill] val mappedPath: String = PathRef.encodeKnownRootsInPath(path)
28+
/**
29+
* The path with common mapped path roots replaced, to make it relocatable.
30+
* See [[MappedRoots]].
31+
*/
32+
private val mappedPath: String = MappedRoots.encodeKnownRootsInPath(path)
3033

3134
/**
3235
* Apply the current contextual path mapping to this PathRef.
33-
* Updates [[mappedPath]] but does not recalculate the sig`.
36+
* Updates [[mappedPath]] but does not recalculate the [[sig]].
3437
*/
3538
def remap: PathRef = PathRef(path, quick, sig, revalidate)
3639

@@ -216,88 +219,18 @@ object PathRef {
216219
}
217220
}
218221

219-
private[api] type MappedRoots = Seq[(key: String, path: os.Path)]
220-
221-
object mappedRoots {
222-
private[PathRef] val rootMapping: DynamicVariable[MappedRoots] = DynamicVariable(Seq())
223-
224-
def get: MappedRoots = rootMapping.value
225-
226-
def toMap: Map[String, os.Path] = get.map(m => (m.key, m.path)).toMap
227-
228-
def withMillDefaults[T](
229-
outPath: os.Path,
230-
workspacePath: os.Path = BuildCtx.workspaceRoot,
231-
homePath: os.Path = os.home
232-
)(thunk: => T): T = withMapping(
233-
Seq(
234-
("MILL_OUT", outPath),
235-
("WORKSPACE", workspacePath),
236-
// TODO: add coursier here
237-
("HOME", homePath)
238-
)
239-
)(thunk)
240-
241-
def withMapping[T](mapping: MappedRoots)(thunk: => T): T = withMapping(_ => mapping)(thunk)
242-
243-
def withMapping[T](mapping: MappedRoots => MappedRoots)(thunk: => T): T = {
244-
val newMapping = mapping(rootMapping.value)
245-
var seenKeys = Set[String]()
246-
var seenPaths = Set[os.Path]()
247-
newMapping.foreach { case m =>
248-
require(!m.key.startsWith("$"), "Key must not start with a `$`.")
249-
require(m.key != PathVars.ROOT, s"Invalid key, '${PathVars.ROOT}' is a reserved key name.")
250-
require(
251-
!seenKeys.contains(m.key),
252-
s"Key must be unique, but '${m.key}' was given multiple times."
253-
)
254-
require(
255-
!seenPaths.contains(m.path),
256-
s"Paths must be unique, but '${m.path}' was given multiple times."
257-
)
258-
seenKeys += m.key
259-
seenPaths += m.path
260-
}
261-
rootMapping.withValue(newMapping)(thunk)
262-
}
263-
}
264-
265-
private[api] def encodeKnownRootsInPath(p: os.Path): String = {
266-
// TODO: Do we need to check for '$' and mask it ?
267-
mappedRoots.get.collectFirst {
268-
case rep if p.startsWith(rep.path) =>
269-
s"$$${rep.key}${
270-
if (p != rep.path) {
271-
s"/${p.subRelativeTo(rep.path).toString()}"
272-
} else ""
273-
}"
274-
}.getOrElse(p.toString)
275-
}
276-
277-
private[api] def decodeKnownRootsInPath(encoded: String): String = {
278-
if (encoded.startsWith("$")) {
279-
val offset = 1 // "$".length
280-
mappedRoots.get.collectFirst {
281-
case mapping if encoded.startsWith(mapping.key, offset) =>
282-
s"${mapping.path.toString}${encoded.substring(mapping.key.length + offset)}"
283-
}.getOrElse(encoded)
284-
} else {
285-
encoded
286-
}
287-
}
288-
289222
/**
290223
* Default JSON formatter for [[PathRef]].
291224
*/
292225
implicit def jsonFormatter: RW[PathRef] = upickle.readwriter[String].bimap[PathRef](
293226
p => {
294227
storeSerializedPaths(p)
295-
p.toStringPrefix + encodeKnownRootsInPath(p.path)
228+
p.toStringPrefix + MappedRoots.encodeKnownRootsInPath(p.path)
296229
},
297230
{
298231
case s"$prefix:$valid0:$hex:$pathVal" if prefix == "ref" || prefix == "qref" =>
299232

300-
val path = os.Path(decodeKnownRootsInPath(pathVal))
233+
val path = os.Path(MappedRoots.decodeKnownRootsInPath(pathVal))
301234
val quick = prefix match {
302235
case "qref" => true
303236
case "ref" => false
@@ -316,7 +249,7 @@ object PathRef {
316249
pr
317250
case s =>
318251
mill.api.BuildCtx.withFilesystemCheckerDisabled(
319-
PathRef(os.Path(decodeKnownRootsInPath(s), currentOverrideModulePath.value))
252+
PathRef(os.Path(MappedRoots.decodeKnownRootsInPath(s), currentOverrideModulePath.value))
320253
)
321254
}
322255
)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package mill.api
2+
3+
import utest.*
4+
5+
import java.nio.file.Files
6+
import mill.api.{MappedRoots => MR}
7+
8+
object MappedRootsTests extends TestSuite {
9+
val tests: Tests = Tests {
10+
test("encode") {
11+
withTmpDir { tmpDir =>
12+
val workspaceDir = tmpDir / "workspace"
13+
val outDir = workspaceDir / "out"
14+
MR.withMillDefaults(outPath = outDir, workspacePath = workspaceDir) {
15+
16+
def check(path: os.Path, encContains: Seq[String], containsNot: Seq[String]) = {
17+
val enc = MR.encodeKnownRootsInPath(path)
18+
val dec = MR.decodeKnownRootsInPath(enc)
19+
assert(path.toString == dec)
20+
encContains.foreach(s => assert(enc.containsSlice(s)))
21+
containsNot.foreach(s => assert(!enc.containsSlice(s)))
22+
23+
path -> enc
24+
}
25+
26+
val file1 = tmpDir / "file1"
27+
val file2 = workspaceDir / "file2"
28+
val file3 = outDir / "file3"
29+
30+
Seq(
31+
"mapping" -> MR.get,
32+
check(file1, Seq(file1.toString), Seq("$WORKSPACE", "$MILL_OUT")),
33+
check(file2, Seq("$WORKSPACE/file2"), Seq("$MILL_OUT")),
34+
check(file3, Seq("$MILL_OUT/file3"), Seq("$WORKSPACE"))
35+
)
36+
}
37+
}
38+
}
39+
}
40+
41+
private def withTmpDir[T](body: os.Path => T): T = {
42+
val tmpDir = os.Path(Files.createTempDirectory(""))
43+
val res = body(tmpDir)
44+
os.remove.all(tmpDir)
45+
res
46+
}
47+
48+
}

core/api/test/src/mill/api/PathRefTests.scala

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ object PathRefTests extends TestSuite {
1919
val sig2 = PathRef(file, quick).sig
2020
assert(sig1 != sig2)
2121
}
22+
2223
test("qref") - check(quick = true)
2324
test("ref") - check(quick = false)
2425
}
@@ -82,6 +83,7 @@ object PathRefTests extends TestSuite {
8283
val sig2 = PathRef(tmpDir, quick).sig
8384
assert(sig1 == sig2)
8485
}
86+
8587
test("qref") - check(quick = true)
8688
test("ref") - check(quick = false)
8789
}
@@ -111,35 +113,6 @@ object PathRefTests extends TestSuite {
111113
test("qref") - check(quick = true)
112114
test("ref") - check(quick = false)
113115
}
114-
115-
test("encode") {
116-
withTmpDir { tmpDir =>
117-
val workspaceDir = tmpDir / "workspace"
118-
val outDir = workspaceDir / "out"
119-
PathRef.mappedRoots.withMillDefaults(outPath = outDir, workspacePath = workspaceDir) {
120-
def check(path: os.Path, contains: Seq[String], containsNot: Seq[String]) = {
121-
val enc = PathRef.encodeKnownRootsInPath(path)
122-
val dec = PathRef.decodeKnownRootsInPath(enc)
123-
assert(path.toString == dec)
124-
contains.foreach(s => enc.containsSlice(s))
125-
containsNot.foreach(s => !enc.containsSlice(s))
126-
127-
path -> enc
128-
}
129-
130-
val file1 = tmpDir / "file1"
131-
val file2 = workspaceDir / "file2"
132-
val file3 = outDir / "file3"
133-
134-
Seq(
135-
"mapping" -> PathRef.mappedRoots.get,
136-
check(file1, Seq("ref:v0:", file1.toString), Seq("$WORKSPACE", "$MILL_OUT")),
137-
check(file2, Seq("ref:v0:", "$WORKSPACE/file2"), Seq("$MILL_OUT")),
138-
check(file3, Seq("ref:v0:", "$MILL_OUT/file3"), Seq("$WORKSPACE"))
139-
)
140-
}
141-
}
142-
}
143116
}
144117

145118
private def withTmpDir[T](body: os.Path => T): T = {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ trait GroupExecution {
8383
executionContext: mill.api.TaskCtx.Fork.Api,
8484
exclusive: Boolean,
8585
upstreamPathRefs: Seq[PathRef]
86-
): GroupExecution.Results = PathRef.mappedRoots.withMillDefaults(outPath) {
86+
): GroupExecution.Results = MappedRoots.withMillDefaults(outPath) {
8787

8888
val inputsHash = {
8989
val externalInputsHash = MurmurHash3.orderedHash(

integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import mill.api.{BuildCtx, PathRef}
1+
import mill.api.{BuildCtx, MappedRoots, PathRef}
22
import mill.testkit.UtestIntegrationTestSuite
33
import utest.*
44

55
object BuildClasspathContentsTests extends UtestIntegrationTestSuite {
66

77
val tests: Tests = Tests {
88
test("test") - integrationTest { tester =>
9-
PathRef.mappedRoots.withMapping(Seq(
9+
MappedRoots.withMapping(Seq(
1010
"HOME" -> os.home,
1111
"WORKSPACE" -> tester.workspacePath
1212
)) {

integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package mill.integration
22

3-
import mill.api.PathRef
3+
import mill.api.MappedRoots
44
import mill.testkit.{IntegrationTester, UtestIntegrationTestSuite}
55
import mill.constants.OutFiles.*
66
import mill.daemon.RunnerState
@@ -50,7 +50,7 @@ trait MultiLevelBuildTests extends UtestIntegrationTestSuite {
5050
val path =
5151
tester.workspacePath / "out" / Seq.fill(depth)(millBuild) / millRunnerState
5252
if (os.exists(path))
53-
PathRef.mappedRoots.withMillDefaults(
53+
MappedRoots.withMillDefaults(
5454
outPath = tester.workspacePath / "out",
5555
workspacePath = tester.workspacePath
5656
) {

0 commit comments

Comments
 (0)