[bug#72867,v5] gexp: Make 'local-file' follow symlinks.
Commit Message
Fixes <https://lists.gnu.org/archive/html/guix-devel/2024-08/msg00047.html>
While the issue can be easily fixed (a one line change in 'absolute-dirname')
by changing 'current-source-directory' so that it always follows symlinks,
such a change may break someone else's code. Instead, this patch keeps the
original behavior of 'current-source-directory' macro and adds optional
'follow-symlinks?' argument to it.
;;; Copyright © 2024 Nigko Yerden <nigko.yerden@gmail.com>
* guix/utils.scm (absolute-dirname): Add 'follow-symlinks?' mandatory
argument.
(%guix-source-root-directory): Pass #f to 'absolute-dirname'
'follow-symlinks?' argument.
(current-source-directory): Add 'follow-symlinks?' optional argument.
* guix/gexp.scm (local-file): Pass #t to 'current-source-directory'
'follow-symlinks?' argument.
* tests/gexp.scm ("local-file, load through symlink"): New test.
Change-Id: Ieb30101275deb56b7436df444f9bc21d240fba59
---
Using of 'eval' in test from v4 is wrong. It does not play any role there.
Most importantly it does not prevent spoiling of '%load-path' for the
rest of 'tests/gexp.scm' module. Here is the better version of the test
that uses 'dynamic-wind'.
guix/gexp.scm | 2 +-
guix/utils.scm | 53 ++++++++++++++++++++++++++++----------------------
tests/gexp.scm | 33 +++++++++++++++++++++++++++++++
3 files changed, 64 insertions(+), 24 deletions(-)
base-commit: 4c49cd171e2aa06af05cf52403050b18f100867a
Comments
Hello Nigko, Tobias, Ludo, Attila (putting them in Cc again).
Nigko Yerden <nigko.yerden@gmail.com> writes:
> Using of 'eval' in test from v4 is wrong. It does not play any role there.
> Most importantly it does not prevent spoiling of '%load-path' for the
> rest of 'tests/gexp.scm' module. Here is the better version of the test
> that uses 'dynamic-wind'.
The test is beautiful. It makes clear why each
canonicalize-path is needed. I had not understood the issue entirely
before I read it.
When we follow symlinks, both calling the real "../test-local-file.scm"
and the symlink to it behaves the same.
When not following symlinks, we get different results depending on what
we run. I see no reason to ever want that except displaying info or
debugging.
And except that not following symlinks is faster (fewer stat syscalls).
But Ludovic wrapped absolute-dirname in a memoizing mlambda in commit
87b711d200ad13eaef284bdd1ab77f85618b0498, which reduces the difference.
Regarding the code, if we kept the old code when `follow-symlinks?' is
false (we should not, but if we did), it remains surprising that we do
follow some symlinks.
> + (if follow-symlinks?
> + (dirname (canonicalize-path file))
> + ;; If there are relative names in %LOAD-PATH, FILE can be relative
> + ;; and needs to be canonicalized.
> + (if (string-prefix? "/" file)
> + (dirname file)
> + (canonicalize-path (dirname file))))))))
In the new use-modules part of the test, `file-name'
in `current-source-directory' is "./test-local-file.scm" code, which
means if we look at what happens if we not follow-symlinks, we took the
latter (canonicalize-path (dirname file)) path in
> (if (string-prefix? "/" file)
> (dirname file)
> (canonicalize-path (dirname file))))))))
which does not follow the symlink in the basename and fails.
But it would follow symlinks in the directory part.
Regards,
Florian
@@ -508,7 +508,7 @@ (define-syntax local-file
(string? (syntax->datum #'file))
;; FILE is a literal, so resolve it relative to the source directory.
#'(%local-file file
- (delay (absolute-file-name file (current-source-directory)))
+ (delay (absolute-file-name file (current-source-directory #t)))
rest ...))
((_ (assume-valid-file-name file) rest ...)
;; FILE is not a literal, so resolve it relative to the current
@@ -1110,41 +1110,48 @@ (define (canonical-newline-port port)
(define (%guix-source-root-directory)
"Return the source root directory of the Guix found in %load-path."
- (dirname (absolute-dirname "guix/packages.scm")))
+ (dirname (absolute-dirname "guix/packages.scm" #f)))
(define absolute-dirname
;; Memoize to avoid repeated 'stat' storms from 'search-path'.
- (mlambda (file)
+ (mlambda (file follow-symlinks?)
"Return the absolute name of the directory containing FILE, or #f upon
-failure."
+failure. Follow symlinks if FOLLOW-SYMLINKS? is true."
(match (search-path %load-path file)
(#f #f)
((? string? file)
- ;; If there are relative names in %LOAD-PATH, FILE can be relative and
- ;; needs to be canonicalized.
- (if (string-prefix? "/" file)
- (dirname file)
- (canonicalize-path (dirname file)))))))
+ (if follow-symlinks?
+ (dirname (canonicalize-path file))
+ ;; If there are relative names in %LOAD-PATH, FILE can be relative
+ ;; and needs to be canonicalized.
+ (if (string-prefix? "/" file)
+ (dirname file)
+ (canonicalize-path (dirname file))))))))
(define-syntax current-source-directory
(lambda (s)
"Return the absolute name of the current directory, or #f if it could not
-be determined."
+be determined. Do not follow symlinks if FOLLOW-SYMLINKS? is false (the default)."
+ (define (source-directory follow-symlinks?)
+ (match (assq 'filename (or (syntax-source s) '()))
+ (('filename . (? string? file-name))
+ ;; If %FILE-PORT-NAME-CANONICALIZATION is 'relative, then FILE-NAME
+ ;; can be relative. In that case, we try to find out at run time
+ ;; the absolute file name by looking at %LOAD-PATH; doing this at
+ ;; run time rather than expansion time is necessary to allow files
+ ;; to be moved on the file system.
+ (if (string-prefix? "/" file-name)
+ (dirname (if follow-symlinks?
+ (canonicalize-path file-name)
+ file-name))
+ #`(absolute-dirname #,file-name #,follow-symlinks?)))
+ ((or ('filename . #f) #f)
+ ;; raising an error would upset Geiser users
+ #f)))
(syntax-case s ()
- ((_)
- (match (assq 'filename (or (syntax-source s) '()))
- (('filename . (? string? file-name))
- ;; If %FILE-PORT-NAME-CANONICALIZATION is 'relative, then FILE-NAME
- ;; can be relative. In that case, we try to find out at run time
- ;; the absolute file name by looking at %LOAD-PATH; doing this at
- ;; run time rather than expansion time is necessary to allow files
- ;; to be moved on the file system.
- (if (string-prefix? "/" file-name)
- (dirname file-name)
- #`(absolute-dirname #,file-name)))
- ((or ('filename . #f) #f)
- ;; raising an error would upset Geiser users
- #f))))))
+ ((_) (source-directory #f))
+ ((_ follow-symlinks?)
+ (source-directory (syntax->datum #'follow-symlinks?))))))
;;;
@@ -292,6 +292,39 @@ (define %extension-package
(equal? (scandir (string-append dir "/tests"))
'("." ".." "gexp.scm"))))))
+(test-assert "local-file, load through symlink"
+ ;; See <https://issues.guix.gnu.org/72867>.
+ (call-with-temporary-directory
+ (lambda (tmp-dir)
+ (chdir tmp-dir)
+ ;; create content file
+ (call-with-output-file "content"
+ (lambda (port) (display "Hi!" port)))
+ ;; Create module that call 'local-file'
+ ;; with the content file and returns its
+ ;; absolute file-name. An error is raised
+ ;; if the content file can't be found.
+ (call-with-output-file "test-local-file.scm"
+ (lambda (port) (display "\
+(define-module (test-local-file)
+ #:use-module (guix gexp))
+(define file (local-file \"content\" \"test-file\"))
+(local-file-absolute-file-name file)" port)))
+ (mkdir "dir")
+ (chdir "dir")
+ (symlink "../test-local-file.scm" "test-local-file.scm")
+ ;; 'local-file' in turn calls 'current-source-directory'
+ ;; which has an 'if' branching condition depending on whether
+ ;; 'file-name' is absolute or relative path. To test both
+ ;; of these branches we execute 'test-local-file.scm' symlink
+ ;; first as a module (corresponds to relative path):
+ (dynamic-wind
+ (lambda () (set! %load-path (cons "." %load-path)))
+ (lambda () (use-modules (test-local-file)))
+ (lambda () (set! %load-path (cdr %load-path))))
+ ;; and then as a regular code (corresponds to absolute path):
+ (load (string-append tmp-dir "/dir/test-local-file.scm")))))
+
(test-assert "one plain file"
(let* ((file (plain-file "hi" "Hello, world!"))
(exp (gexp (display (ungexp file))))