From 890225131eb2b5f85fafe82305fead4485d96912 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 23 Jan 2024 10:09:11 +0100 Subject: [PATCH] Mask colons in segment paths (#2965) Again a Windows thing, but the evaluator paths should not contain colons. We mask them with `$colon`. Fix https://github.com/com-lihaoyi/mill/pull/2923 Pull request: https://github.com/com-lihaoyi/mill/pull/2965 --- main/eval/src/mill/eval/EvaluatorPaths.scala | 23 ++++++++++++++++--- .../src/mill/eval/EvaluatorPathsTests.scala | 9 +++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/main/eval/src/mill/eval/EvaluatorPaths.scala b/main/eval/src/mill/eval/EvaluatorPaths.scala index d92c968c0b6..7b761d62dac 100644 --- a/main/eval/src/mill/eval/EvaluatorPaths.scala +++ b/main/eval/src/mill/eval/EvaluatorPaths.scala @@ -3,6 +3,8 @@ package mill.eval import mill.api.internal import mill.define.{NamedTask, Segment, Segments} +import java.util.regex.Matcher + case class EvaluatorPaths private (dest: os.Path, meta: os.Path, log: os.Path) { // scalafix:off; we want to hide the generic copy method private def copy(dest: os.Path = dest, meta: os.Path = meta, log: os.Path = log): EvaluatorPaths = @@ -47,10 +49,25 @@ object EvaluatorPaths { // case-insensitive match on reserved names private val ReservedWinNames = raw"^([cC][oO][nN]|[pP][rR][nN]|[aA][uU][xX]|[nN][uU][lL]|[cC][oO][mM][0-9¹²³]|[lL][pP][tT][0-9¹²³])($$|[.].*$$)".r - def sanitizePathSegment(segment: String): os.PathChunk = { - segment match { + // Colons are not supported on Windows + private val Colon = "[:]".r + // Dollar sign `$` is our masking-character + private val Dollar = "[$]".r + + private val steps: Seq[String => String] = Seq( + // Step 1: mask all existing dollar signs, so we can use the dollar as masking character + s => Dollar.replaceAllIn(s, Matcher.quoteReplacement("$$")), + // Step 2: mask reserved Windows names, like CON1 or LPT1 + _ match { case ReservedWinNames(keyword, rest) => s"${keyword}~${rest}" case s => s - } + }, + // Step 3: Replace colon (:) with $colon + s => Colon.replaceAllIn(s, Matcher.quoteReplacement("$colon")) + ) + + def sanitizePathSegment(segment: String): os.PathChunk = { + // sanitize and implicitly convert to PathChunk + steps.foldLeft(segment) { (segment, f) => f(segment) } } } diff --git a/main/eval/test/src/mill/eval/EvaluatorPathsTests.scala b/main/eval/test/src/mill/eval/EvaluatorPathsTests.scala index 7f905b93973..603a851c62d 100644 --- a/main/eval/test/src/mill/eval/EvaluatorPathsTests.scala +++ b/main/eval/test/src/mill/eval/EvaluatorPathsTests.scala @@ -8,8 +8,15 @@ object EvaluatorPathsTests extends TestSuite { "sanitizedPathSegment" - { "WindowsReservedNames" - { val replace = Seq( + // reserved file names under Windows "com1.json" -> "com1~.json", - "LPT¹" -> "LPT¹~" + "LPT¹" -> "LPT¹~", + // a unsupported character under Windows + "a:b" -> "a$colonb", + // do not collide with the applied `$`-masking character + "a$colonb" -> "a$$colonb", + // replace not just the first $ + "a$$b" -> "a$$$$b" ) val noReplace = Seq( "con10.json"