From 9a2070dabc737a907ec3fdc5c1213f21d0a98220 Mon Sep 17 00:00:00 2001
From: Philipp Stephani
Date: Tue, 8 Oct 2024 02:00:22 +0200
Subject: [PATCH] Support space and backslash characters in runfile filenames.
Bazel supports them with https://github.com/bazelbuild/bazel/pull/23331.
Step 2: support them for manifest-based runfiles.
---
elisp/runfiles/runfiles-test.el | 13 +++++++++++--
elisp/runfiles/runfiles.el | 32 +++++++++++++++++++++++++-------
elisp/runfiles/test-manifest | 3 +++
3 files changed, 39 insertions(+), 9 deletions(-)
diff --git a/elisp/runfiles/runfiles-test.el b/elisp/runfiles/runfiles-test.el
index fc1f4326..3f52efdb 100644
--- a/elisp/runfiles/runfiles-test.el
+++ b/elisp/runfiles/runfiles-test.el
@@ -54,8 +54,17 @@
"phst_rules_elisp/elisp/runfiles/test-manifest"))
(runfiles (elisp/runfiles/make :manifest manifest
:directory "/invalid/")))
- (should (equal (elisp/runfiles/rlocation "testäα𝐴🐈'.txt" runfiles)
- "/:/runfiles/testäα𝐴🐈'.txt"))))
+ (pcase-dolist (`(,source ,target)
+ '(("testäα𝐴🐈'.txt"
+ "/:/runfiles/testäα𝐴🐈'.txt")
+ ("target-with-space"
+ "/:/runfiles/with space\\and backslash")
+ ("target-with-newline"
+ "/:/runfiles/with\nnewline\\and backslash")
+ ("source with space,\nnewline,\\and backslash"
+ "/:/runfiles/with space,\nnewline,\\and backslash")))
+ (ert-info (source :prefix "Source: ")
+ (should (equal (elisp/runfiles/rlocation source runfiles) target))))))
(ert-deftest elisp/runfiles/make/empty-file ()
(let* ((manifest (elisp/runfiles/rlocation
diff --git a/elisp/runfiles/runfiles.el b/elisp/runfiles/runfiles.el
index 5e06f162..6e59f28f 100644
--- a/elisp/runfiles/runfiles.el
+++ b/elisp/runfiles/runfiles.el
@@ -501,13 +501,31 @@ Return an object of type ‘elisp/runfiles/runfiles--manifest’."
;; Perform the same parsing as
;; https://github.com/bazelbuild/bazel/blob/6.4.0/tools/cpp/runfiles/runfiles_src.cc#L241.
(while (not (eobp))
- (pcase (buffer-substring-no-properties (point) (line-end-position))
- ((rx bos (let key (+ (not (any ?\n ?\s))))
- ?\s (let value (* nonl)) eos)
- ;; Runfiles are always local, so quote them unconditionally.
- (puthash key (if (string-empty-p value) :empty (concat "/:" value))
- manifest))
- (other (signal 'elisp/runfiles/syntax-error (list filename other))))
+ (let ((line (buffer-substring-no-properties
+ (point) (line-end-position)))
+ (escaped (eql (following-char) ?\s)))
+ (cl-flet* ((syntax-error ()
+ (signal 'elisp/runfiles/syntax-error
+ (list filename line)))
+ (unescape (string &rest other)
+ (let ((pairs `(,@other ("\\n" . "\n") ("\\b" . "\\"))))
+ (replace-regexp-in-string
+ (rx ?\\ (? anychar))
+ (lambda (seq)
+ (or (cdr (assoc seq pairs)) (syntax-error)))
+ string :fixedcase :literal))))
+ (pcase (if escaped (substring-no-properties line 1) line)
+ ((rx bos (let key (+ (not (any ?\n ?\s))))
+ ?\s (let value (* nonl)) eos)
+ (when escaped
+ (cl-callf unescape key '("\\s" . " "))
+ (cl-callf unescape value))
+ (puthash key
+ ;; Runfiles are always local, so quote them
+ ;; unconditionally.
+ (if (string-empty-p value) :empty (concat "/:" value))
+ manifest))
+ (_ (syntax-error)))))
(forward-line)))
(elisp/runfiles/manifest--make filename manifest)))
diff --git a/elisp/runfiles/test-manifest b/elisp/runfiles/test-manifest
index 6915b999..26b1f400 100644
--- a/elisp/runfiles/test-manifest
+++ b/elisp/runfiles/test-manifest
@@ -1,3 +1,6 @@
__init__.py
foo/bar runfiles/foo/bar
testäα𝐴🐈'.txt /runfiles/testäα𝐴🐈'.txt
+target-with-space /runfiles/with space\and backslash
+ target-with-newline /runfiles/with\nnewline\band backslash
+ source\swith\sspace,\nnewline,\band\sbackslash /runfiles/with space,\nnewline,\band backslash