feat(runfiles): add support for spaces and newlines in runfiles paths (#2456)
Bazel 7.4.0 introduced support for all characters in runfile source and
target paths: https://github.com/bazelbuild/bazel/pull/23912
This is a backwards-compatible change, based on a similar change in
rules_go: https://github.com/bazel-contrib/rules_go/pull/4136
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a20057e..cc66afa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -112,6 +112,7 @@
* (providers) Added {obj}`py_runtime_info.site_init_template` and
{obj}`PyRuntimeInfo.site_init_template` for specifying the template to use to
initialize the interpreter via venv startup hooks.
+* (runfiles) (Bazel 7.4+) Added support for spaces and newlines in runfiles paths
{#v0-0-0-removed}
### Removed
diff --git a/python/runfiles/runfiles.py b/python/runfiles/runfiles.py
index 6d47d24..ea816c6 100644
--- a/python/runfiles/runfiles.py
+++ b/python/runfiles/runfiles.py
@@ -58,13 +58,24 @@
result = {}
with open(path, "r") as f:
for line in f:
- line = line.strip()
- if line:
- tokens = line.split(" ", 1)
- if len(tokens) == 1:
- result[line] = line
- else:
- result[tokens[0]] = tokens[1]
+ line = line.rstrip("\n")
+ if line.startswith(" "):
+ # In lines that start with a space, spaces, newlines, and backslashes are escaped as \s, \n, and \b in
+ # link and newlines and backslashes are escaped in target.
+ escaped_link, escaped_target = line[1:].split(" ", maxsplit=1)
+ link = (
+ escaped_link.replace(r"\s", " ")
+ .replace(r"\n", "\n")
+ .replace(r"\b", "\\")
+ )
+ target = escaped_target.replace(r"\n", "\n").replace(r"\b", "\\")
+ else:
+ link, target = line.split(" ", maxsplit=1)
+
+ if target:
+ result[link] = target
+ else:
+ result[link] = link
return result
def _GetRunfilesDir(self) -> str:
diff --git a/tests/runfiles/runfiles_test.py b/tests/runfiles/runfiles_test.py
index 03350f3..cf6a70a 100644
--- a/tests/runfiles/runfiles_test.py
+++ b/tests/runfiles/runfiles_test.py
@@ -185,10 +185,11 @@
def testManifestBasedRlocation(self) -> None:
with _MockFile(
contents=[
- "Foo/runfile1",
+ "Foo/runfile1 ", # A trailing whitespace is always present in single entry lines.
"Foo/runfile2 C:/Actual Path\\runfile2",
"Foo/Bar/runfile3 D:\\the path\\run file 3.txt",
"Foo/Bar/Dir E:\\Actual Path\\Directory",
+ " Foo\\sBar\\bDir\\nNewline/runfile5 F:\\bActual Path\\bwith\\nnewline/runfile5",
]
) as mf:
r = runfiles.CreateManifestBased(mf.Path())
@@ -205,6 +206,10 @@
r.Rlocation("Foo/Bar/Dir/Deeply/Nested/runfile4"),
"E:\\Actual Path\\Directory/Deeply/Nested/runfile4",
)
+ self.assertEqual(
+ r.Rlocation("Foo Bar\\Dir\nNewline/runfile5"),
+ "F:\\Actual Path\\with\nnewline/runfile5",
+ )
self.assertIsNone(r.Rlocation("unknown"))
if RunfilesTest.IsWindows():
self.assertEqual(r.Rlocation("c:/foo"), "c:/foo")