utils: 'current-source-directory' gracefully handles lack of source info.
[jackhill/guix/guix.git] / guix / utils.scm
index f566a99..69f4e78 100644 (file)
@@ -32,8 +32,9 @@
   #:use-module (rnrs bytevectors)
   #:use-module (rnrs io ports)
   #:use-module ((rnrs bytevectors) #:select (bytevector-u8-set!))
+  #:use-module (guix combinators)
   #:use-module ((guix build utils) #:select (dump-port))
-  #:use-module ((guix build syscalls) #:select (errno mkdtemp!))
+  #:use-module ((guix build syscalls) #:select (mkdtemp! fdatasync))
   #:use-module (ice-9 vlist)
   #:use-module (ice-9 format)
   #:autoload   (ice-9 popen)  (open-pipe*)
   #:use-module (ice-9 format)
   #:use-module ((ice-9 iconv) #:select (bytevector->string))
   #:use-module (system foreign)
+  #:re-export (memoize)         ; for backwards compatibility
   #:export (bytevector->base16-string
             base16-string->bytevector
 
-            compile-time-value
-            fcntl-flock
-            memoize
             strip-keyword-arguments
             default-keyword-arguments
             substitute-keyword-arguments
             ensure-keyword-arguments
 
+            current-source-directory
+
             <location>
             location
             location?
@@ -61,6 +62,7 @@
             location-line
             location-column
             source-properties->location
+            location->source-properties
 
             nix-system->gnu-triplet
             gnu-triplet->nix-system
             call-with-temporary-output-file
             call-with-temporary-directory
             with-atomic-file-output
-            fold2
-            fold-tree
-            fold-tree-leaves
-            split
             cache-directory
             readlink*
             edit-expression
             canonical-newline-port))
 
 \f
-;;;
-;;; Compile-time computations.
-;;;
-
-(define-syntax compile-time-value
-  (syntax-rules ()
-    "Evaluate the given expression at compile time.  The expression must
-evaluate to a simple datum."
-    ((_ exp)
-     (let-syntax ((v (lambda (s)
-                       (let ((val exp))
-                         (syntax-case s ()
-                           (_ #`'#,(datum->syntax s val)))))))
-       v))))
-
-\f
 ;;;
 ;;; Base 16.
 ;;;
@@ -360,94 +342,9 @@ This procedure returns #t on success."
 
 \f
 ;;;
-;;; Advisory file locking.
+;;; Keyword arguments.
 ;;;
 
-(define %struct-flock
-  ;; 'struct flock' from <fcntl.h>.
-  (list short                                     ; l_type
-        short                                     ; l_whence
-        size_t                                    ; l_start
-        size_t                                    ; l_len
-        int))                                     ; l_pid
-
-(define F_SETLKW
-  ;; On Linux-based systems, this is usually 7, but not always
-  ;; (exceptions include SPARC.)  On GNU/Hurd, it's 9.
-  (compile-time-value
-   (cond ((string-contains %host-type "sparc") 9) ; sparc-*-linux-gnu
-         ((string-contains %host-type "linux") 7) ; *-linux-gnu
-         (else 9))))                              ; *-gnu*
-
-(define F_SETLK
-  ;; Likewise: GNU/Hurd and SPARC use 8, while the others typically use 6.
-  (compile-time-value
-   (cond ((string-contains %host-type "sparc") 8) ; sparc-*-linux-gnu
-         ((string-contains %host-type "linux") 6) ; *-linux-gnu
-         (else 8))))                              ; *-gnu*
-
-(define F_xxLCK
-  ;; The F_RDLCK, F_WRLCK, and F_UNLCK constants.
-  (compile-time-value
-   (cond ((string-contains %host-type "sparc") #(1 2 3))    ; sparc-*-linux-gnu
-         ((string-contains %host-type "hppa")  #(1 2 3))    ; hppa-*-linux-gnu
-         ((string-contains %host-type "linux") #(0 1 2))    ; *-linux-gnu
-         (else                                 #(1 2 3))))) ; *-gnu*
-
-(define fcntl-flock
-  (let* ((ptr  (dynamic-func "fcntl" (dynamic-link)))
-         (proc (pointer->procedure int ptr `(,int ,int *))))
-    (lambda* (fd-or-port operation #:key (wait? #t))
-      "Perform locking OPERATION on the file beneath FD-OR-PORT.  OPERATION
-must be a symbol, one of 'read-lock, 'write-lock, or 'unlock.  When WAIT? is
-true, block until the lock is acquired; otherwise, thrown an 'flock-error'
-exception if it's already taken."
-      (define (operation->int op)
-        (case op
-          ((read-lock)  (vector-ref F_xxLCK 0))
-          ((write-lock) (vector-ref F_xxLCK 1))
-          ((unlock)     (vector-ref F_xxLCK 2))
-          (else         (error "invalid fcntl-flock operation" op))))
-
-      (define fd
-        (if (port? fd-or-port)
-            (fileno fd-or-port)
-            fd-or-port))
-
-      ;; XXX: 'fcntl' is a vararg function, but here we happily use the
-      ;; standard ABI; crossing fingers.
-      (let ((err (proc fd
-                       (if wait?
-                           F_SETLKW               ; lock & wait
-                           F_SETLK)               ; non-blocking attempt
-                       (make-c-struct %struct-flock
-                                      (list (operation->int operation)
-                                            SEEK_SET
-                                            0 0   ; whole file
-                                            0)))))
-        (or (zero? err)
-
-            ;; Presumably we got EAGAIN or so.
-            (throw 'flock-error (errno)))))))
-
-\f
-;;;
-;;; Miscellaneous.
-;;;
-
-(define (memoize proc)
-  "Return a memoizing version of PROC."
-  (let ((cache (make-hash-table)))
-    (lambda args
-      (let ((results (hash-ref cache args)))
-        (if results
-            (apply values results)
-            (let ((results (call-with-values (lambda ()
-                                               (apply proc args))
-                             list)))
-              (hash-set! cache args results)
-              (apply values results)))))))
-
 (define (strip-keyword-arguments keywords args)
   "Remove all of the keyword arguments listed in KEYWORDS from ARGS."
   (let loop ((args   args)
@@ -533,6 +430,11 @@ For instance:
          (#f
           (loop rest kw/values (cons* value kw result))))))))
 
+\f
+;;;
+;;; System strings.
+;;;
+
 (define* (nix-system->gnu-triplet
           #:optional (system (%current-system)) (vendor "unknown"))
   "Return a guess of the GNU triplet corresponding to Nix system
@@ -725,89 +627,19 @@ output port, and PROC's result is returned."
     (with-throw-handler #t
       (lambda ()
         (let ((result (proc out)))
-          (close out)
+          (fdatasync out)
+          (close-port out)
           (rename-file template file)
           result))
       (lambda (key . args)
-        (false-if-exception (delete-file template))))))
-
-(define fold2
-  (case-lambda
-    ((proc seed1 seed2 lst)
-     "Like `fold', but with a single list and two seeds."
-     (let loop ((result1 seed1)
-                (result2 seed2)
-                (lst     lst))
-       (if (null? lst)
-           (values result1 result2)
-           (call-with-values
-               (lambda () (proc (car lst) result1 result2))
-             (lambda (result1 result2)
-               (loop result1 result2 (cdr lst)))))))
-    ((proc seed1 seed2 lst1 lst2)
-     "Like `fold', but with a two lists and two seeds."
-     (let loop ((result1 seed1)
-                (result2 seed2)
-                (lst1    lst1)
-                (lst2    lst2))
-       (if (or (null? lst1) (null? lst2))
-           (values result1 result2)
-           (call-with-values
-               (lambda () (proc (car lst1) (car lst2) result1 result2))
-             (lambda (result1 result2)
-               (fold2 proc result1 result2 (cdr lst1) (cdr lst2)))))))))
-
-(define (fold-tree proc init children roots)
-  "Call (PROC NODE RESULT) for each node in the tree that is reachable from
-ROOTS, using INIT as the initial value of RESULT.  The order in which nodes
-are traversed is not specified, however, each node is visited only once, based
-on an eq? check.  Children of a node to be visited are generated by
-calling (CHILDREN NODE), the result of which should be a list of nodes that
-are connected to NODE in the tree, or '() or #f if NODE is a leaf node."
-  (let loop ((result init)
-             (seen vlist-null)
-             (lst roots))
-    (match lst
-      (() result)
-      ((head . tail)
-       (if (not (vhash-assq head seen))
-           (loop (proc head result)
-                 (vhash-consq head #t seen)
-                 (match (children head)
-                   ((or () #f) tail)
-                   (children (append tail children))))
-           (loop result seen tail))))))
-
-(define (fold-tree-leaves proc init children roots)
-  "Like fold-tree, but call (PROC NODE RESULT) only for leaf nodes."
-  (fold-tree
-   (lambda (node result)
-     (match (children node)
-       ((or () #f) (proc node result))
-       (else result)))
-   init children roots))
-
-(define (split lst e)
-  "Return two values, a list containing the elements of the list LST that
-appear before the first occurence of the object E and a list containing the
-elements after E."
-  (define (same? x)
-    (equal? e x))
-
-  (let loop ((rest lst)
-             (acc '()))
-    (match rest
-      (()
-       (values lst '()))
-      (((? same?) . tail)
-       (values (reverse acc) tail))
-      ((head . tail)
-       (loop tail (cons head acc))))))
+        (false-if-exception (delete-file template))
+        (close-port out)))))
 
 (define (cache-directory)
   "Return the cache directory for Guix, by default ~/.cache/guix."
   (or (getenv "XDG_CONFIG_HOME")
-      (and=> (getenv "HOME")
+      (and=> (or (getenv "HOME")
+                 (passwd:dir (getpwuid (getuid))))
              (cut string-append <> "/.cache/guix"))))
 
 (define (readlink* file)
@@ -870,6 +702,40 @@ elements after E."
 ;;; Source location.
 ;;;
 
+(define (absolute-dirname file)
+  "Return the absolute name of the directory containing FILE, or #f upon
+failure."
+  (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))))))
+
+(define-syntax current-source-directory
+  (lambda (s)
+    "Return the absolute name of the current directory, or #f if it could not
+be determined."
+    (syntax-case s ()
+      ((_)
+       (match (assq 'filename (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.
+          (cond ((not file-name)
+                 #f)                ;raising an error would upset Geiser users
+                ((string-prefix? "/" file-name)
+                 (dirname file-name))
+                (else
+                 #`(absolute-dirname #,file-name))))
+         (_
+          #f))))))
+
 ;; A source location.
 (define-record-type <location>
   (make-location file line column)
@@ -895,3 +761,10 @@ etc."
     ;; In accordance with the GCS, start line and column numbers at 1.  Note
     ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
     (location file (and line (+ line 1)) col)))
+
+(define (location->source-properties loc)
+  "Return the source property association list based on the info in LOC,
+a location object."
+  `((line     . ,(and=> (location-line loc) 1-))
+    (column   . ,(location-column loc))
+    (filename . ,(location-file loc))))