diff --git a/CHANGELOG.md b/CHANGELOG.md index dc762fe448..37a9e710a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,7 @@ Other changes: * (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/MODULE.bazel b/MODULE.bazel index 104ae92b89..2ae3173094 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -74,7 +74,7 @@ use_repo(pip, "rules_python_publish_deps") bazel_dep(name = "stardoc", version = "0.7.2", repo_name = "io_bazel_stardoc") # ===== DEV ONLY DEPS AND SETUP BELOW HERE ===== -bazel_dep(name = "rules_bazel_integration_test", version = "0.26.1", dev_dependency = True) +bazel_dep(name = "rules_bazel_integration_test", version = "0.27.0", dev_dependency = True) bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True) bazel_dep(name = "rules_shell", version = "0.3.0", dev_dependency = True) bazel_dep(name = "rules_multirun", version = "0.9.0", dev_dependency = True) diff --git a/python/runfiles/runfiles.py b/python/runfiles/runfiles.py index 6d47d249b4..ea816c64fd 100644 --- a/python/runfiles/runfiles.py +++ b/python/runfiles/runfiles.py @@ -58,13 +58,24 @@ def _LoadRunfiles(path: str) -> Dict[str, str]: 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 03350f3fff..cf6a70a020 100644 --- a/tests/runfiles/runfiles_test.py +++ b/tests/runfiles/runfiles_test.py @@ -185,10 +185,11 @@ def testFailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined(self) -> None: 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 @@ def testManifestBasedRlocation(self) -> None: 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")