2013-01-05 16:08:07 +01:00
|
|
|
|
;;; GNU Guix --- Functional package management for GNU
|
2018-03-31 23:18:29 +02:00
|
|
|
|
;;; Copyright © 2012, 2013, 2014, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
|
2014-03-28 08:54:01 +01:00
|
|
|
|
;;; Copyright © 2014 Mark H Weaver <mhw@netris.org>
|
2017-03-12 12:53:58 +01:00
|
|
|
|
;;; Copyright © 2017 Huang Ying <huang.ying.caritas@gmail.com>
|
2012-10-29 22:39:46 +01:00
|
|
|
|
;;;
|
2013-01-05 16:08:07 +01:00
|
|
|
|
;;; This file is part of GNU Guix.
|
2012-10-29 22:39:46 +01:00
|
|
|
|
;;;
|
2013-01-05 16:08:07 +01:00
|
|
|
|
;;; GNU Guix is free software; you can redistribute it and/or modify it
|
2012-10-29 22:39:46 +01:00
|
|
|
|
;;; under the terms of the GNU General Public License as published by
|
|
|
|
|
;;; the Free Software Foundation; either version 3 of the License, or (at
|
|
|
|
|
;;; your option) any later version.
|
|
|
|
|
;;;
|
2013-01-05 16:08:07 +01:00
|
|
|
|
;;; GNU Guix is distributed in the hope that it will be useful, but
|
2012-10-29 22:39:46 +01:00
|
|
|
|
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;;; GNU General Public License for more details.
|
|
|
|
|
;;;
|
|
|
|
|
;;; You should have received a copy of the GNU General Public License
|
2013-01-05 16:08:07 +01:00
|
|
|
|
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
|
2012-10-29 22:39:46 +01:00
|
|
|
|
|
|
|
|
|
(define-module (guix build union)
|
|
|
|
|
#:use-module (ice-9 match)
|
2013-01-09 22:09:58 +01:00
|
|
|
|
#:use-module (ice-9 format)
|
2012-10-29 22:39:46 +01:00
|
|
|
|
#:use-module (srfi srfi-1)
|
|
|
|
|
#:use-module (srfi srfi-26)
|
2013-10-31 23:31:00 +01:00
|
|
|
|
#:use-module (rnrs bytevectors)
|
|
|
|
|
#:use-module (rnrs io ports)
|
2018-04-08 15:47:11 +02:00
|
|
|
|
#:export (union-build
|
|
|
|
|
|
2018-04-28 17:17:33 +02:00
|
|
|
|
warn-about-collision
|
|
|
|
|
|
2018-04-28 22:20:36 +02:00
|
|
|
|
relative-file-name
|
|
|
|
|
symlink-relative))
|
2012-10-29 22:39:46 +01:00
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
;;;
|
|
|
|
|
;;; Build a directory that is the union of a set of directories, using
|
|
|
|
|
;;; symbolic links.
|
|
|
|
|
;;;
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
2014-03-28 08:54:01 +01:00
|
|
|
|
(define (files-in-directory dirname)
|
|
|
|
|
(let ((dir (opendir dirname)))
|
|
|
|
|
(let loop ((files '()))
|
|
|
|
|
(match (readdir dir)
|
|
|
|
|
((or "." "..")
|
|
|
|
|
(loop files))
|
|
|
|
|
((? eof-object?)
|
|
|
|
|
(closedir dir)
|
|
|
|
|
(sort files string<?))
|
|
|
|
|
(file
|
|
|
|
|
(loop (cons file files)))))))
|
|
|
|
|
|
|
|
|
|
(define (file-is-directory? file)
|
2017-05-18 11:35:45 +02:00
|
|
|
|
(match (stat file #f)
|
|
|
|
|
(#f #f) ;maybe a dangling symlink
|
|
|
|
|
(st (eq? 'directory (stat:type st)))))
|
2013-01-09 22:09:58 +01:00
|
|
|
|
|
2013-10-31 23:31:00 +01:00
|
|
|
|
(define (file=? file1 file2)
|
2014-02-10 00:05:39 +01:00
|
|
|
|
"Return #t if FILE1 and FILE2 are regular files and their contents are
|
|
|
|
|
identical, #f otherwise."
|
2017-05-18 11:35:45 +02:00
|
|
|
|
(let ((st1 (stat file1 #f))
|
|
|
|
|
(st2 (stat file2 #f)))
|
2016-05-23 18:02:04 +02:00
|
|
|
|
;; When deduplication is enabled, identical files share the same inode.
|
2017-05-18 11:35:45 +02:00
|
|
|
|
(and st1 st2
|
|
|
|
|
(or (= (stat:ino st1) (stat:ino st2))
|
|
|
|
|
(and (eq? (stat:type st1) 'regular)
|
|
|
|
|
(eq? (stat:type st2) 'regular)
|
|
|
|
|
(= (stat:size st1) (stat:size st2))
|
|
|
|
|
(call-with-input-file file1
|
|
|
|
|
(lambda (port1)
|
|
|
|
|
(call-with-input-file file2
|
|
|
|
|
(lambda (port2)
|
|
|
|
|
(define len 8192)
|
|
|
|
|
(define buf1 (make-bytevector len))
|
|
|
|
|
(define buf2 (make-bytevector len))
|
|
|
|
|
(let loop ()
|
|
|
|
|
(let ((n1 (get-bytevector-n! port1 buf1 0 len))
|
|
|
|
|
(n2 (get-bytevector-n! port2 buf2 0 len)))
|
|
|
|
|
(and (equal? n1 n2)
|
|
|
|
|
(or (eof-object? n1)
|
|
|
|
|
(loop))))))))))))))
|
2013-10-31 23:31:00 +01:00
|
|
|
|
|
2018-04-08 15:47:11 +02:00
|
|
|
|
(define (warn-about-collision files)
|
|
|
|
|
"Handle the collision among FILES by emitting a warning and choosing the
|
|
|
|
|
first one of THEM."
|
|
|
|
|
(format (current-error-port)
|
|
|
|
|
"~%warning: collision encountered:~%~{ ~a~%~}"
|
|
|
|
|
files)
|
|
|
|
|
(let ((file (first files)))
|
|
|
|
|
(format (current-error-port) "warning: choosing ~a~%" file)
|
|
|
|
|
file))
|
|
|
|
|
|
2014-03-28 08:54:01 +01:00
|
|
|
|
(define* (union-build output inputs
|
2017-03-12 12:53:58 +01:00
|
|
|
|
#:key (log-port (current-error-port))
|
2017-10-19 16:07:34 +02:00
|
|
|
|
(create-all-directories? #f)
|
2018-04-08 15:47:11 +02:00
|
|
|
|
(symlink symlink)
|
|
|
|
|
(resolve-collision warn-about-collision))
|
2017-03-12 12:53:58 +01:00
|
|
|
|
"Build in the OUTPUT directory a symlink tree that is the union of all the
|
2017-10-19 16:07:34 +02:00
|
|
|
|
INPUTS, using SYMLINK to create symlinks. As a special case, if
|
|
|
|
|
CREATE-ALL-DIRECTORIES?, creates the subdirectories in the output directory to
|
2018-04-08 15:47:11 +02:00
|
|
|
|
make sure the caller can modify them later.
|
|
|
|
|
|
|
|
|
|
When two or more regular files collide, call RESOLVE-COLLISION with the list
|
|
|
|
|
of colliding files and use the one that it returns; or, if RESOLVE-COLLISION
|
|
|
|
|
returns #f, skip the faulty file altogether."
|
2014-03-28 08:54:01 +01:00
|
|
|
|
|
|
|
|
|
(define (symlink* input output)
|
|
|
|
|
(format log-port "`~a' ~~> `~a'~%" input output)
|
|
|
|
|
(symlink input output))
|
|
|
|
|
|
|
|
|
|
(define (resolve-collisions output dirs files)
|
|
|
|
|
(cond ((null? dirs)
|
|
|
|
|
;; The inputs are all files.
|
2018-04-08 15:47:11 +02:00
|
|
|
|
(match (resolve-collision files)
|
|
|
|
|
(#f #f)
|
|
|
|
|
((? string? file)
|
|
|
|
|
(symlink* file output))))
|
2014-03-28 08:54:01 +01:00
|
|
|
|
|
|
|
|
|
(else
|
|
|
|
|
;; The inputs are a mixture of files and directories
|
|
|
|
|
(error "union-build: collision between file and directories"
|
|
|
|
|
`((files ,files) (dirs ,dirs))))))
|
|
|
|
|
|
|
|
|
|
(define (union output inputs)
|
|
|
|
|
(match inputs
|
|
|
|
|
((input)
|
2017-03-12 12:53:58 +01:00
|
|
|
|
;; There's only one input, so just make a link unless
|
|
|
|
|
;; create-all-directories?.
|
|
|
|
|
(if (and create-all-directories? (file-is-directory? input))
|
|
|
|
|
(union-of-directories output inputs)
|
|
|
|
|
(symlink* input output)))
|
2014-03-28 08:54:01 +01:00
|
|
|
|
(_
|
|
|
|
|
(call-with-values (lambda () (partition file-is-directory? inputs))
|
|
|
|
|
(match-lambda*
|
|
|
|
|
((dirs ())
|
2014-04-03 23:49:20 +02:00
|
|
|
|
;; All inputs are directories.
|
|
|
|
|
(union-of-directories output dirs))
|
2014-03-28 08:54:01 +01:00
|
|
|
|
|
|
|
|
|
((() (file (? (cut file=? <> file)) ...))
|
|
|
|
|
;; There are no directories, and all files have the same contents,
|
|
|
|
|
;; so there's no conflict.
|
|
|
|
|
(symlink* file output))
|
|
|
|
|
|
|
|
|
|
((dirs files)
|
|
|
|
|
(resolve-collisions output dirs files)))))))
|
2013-01-09 22:09:58 +01:00
|
|
|
|
|
2014-04-03 23:49:20 +02:00
|
|
|
|
(define (union-of-directories output dirs)
|
|
|
|
|
;; Create a new directory where we will merge the input directories.
|
|
|
|
|
(mkdir output)
|
|
|
|
|
|
|
|
|
|
;; Build a hash table mapping each file to a list of input
|
|
|
|
|
;; directories containing that file.
|
|
|
|
|
(let ((table (make-hash-table)))
|
|
|
|
|
|
|
|
|
|
(define (add-to-table! file dir)
|
|
|
|
|
(hash-set! table file (cons dir (hash-ref table file '()))))
|
|
|
|
|
|
|
|
|
|
;; Populate the table.
|
|
|
|
|
(for-each (lambda (dir)
|
|
|
|
|
(for-each (cut add-to-table! <> dir)
|
|
|
|
|
(files-in-directory dir)))
|
|
|
|
|
dirs)
|
|
|
|
|
|
|
|
|
|
;; Now iterate over the table and recursively
|
|
|
|
|
;; perform a union for each entry.
|
|
|
|
|
(hash-for-each (lambda (file dirs-with-file)
|
|
|
|
|
(union (string-append output "/" file)
|
|
|
|
|
(map (cut string-append <> "/" file)
|
|
|
|
|
(reverse dirs-with-file))))
|
|
|
|
|
table)))
|
|
|
|
|
|
2012-10-29 22:39:46 +01:00
|
|
|
|
(setvbuf (current-output-port) _IOLBF)
|
|
|
|
|
(setvbuf (current-error-port) _IOLBF)
|
2013-10-30 21:43:37 +01:00
|
|
|
|
(when (file-port? log-port)
|
|
|
|
|
(setvbuf log-port _IOLBF))
|
2012-10-29 22:39:46 +01:00
|
|
|
|
|
2014-04-03 23:49:20 +02:00
|
|
|
|
(union-of-directories output (delete-duplicates inputs)))
|
2012-10-29 22:39:46 +01:00
|
|
|
|
|
2018-04-28 17:17:33 +02:00
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; Relative symlinks.
|
|
|
|
|
;;;
|
|
|
|
|
|
|
|
|
|
(define %not-slash
|
|
|
|
|
(char-set-complement (char-set #\/)))
|
|
|
|
|
|
|
|
|
|
(define (relative-file-name reference file)
|
|
|
|
|
"Given REFERENCE and FILE, both of which are absolute file names, return the
|
|
|
|
|
file name of FILE relative to REFERENCE.
|
|
|
|
|
|
|
|
|
|
(relative-file-name \"/gnu/store/foo\" \"/gnu/store/bin/bar\")
|
|
|
|
|
=> \"../bin/bar\"
|
|
|
|
|
|
|
|
|
|
Note that this is from a purely lexical standpoint; conversely, \"..\" is
|
|
|
|
|
*not* resolved lexically on POSIX in the presence of symlinks."
|
|
|
|
|
(if (and (string-prefix? "/" file) (string-prefix? "/" reference))
|
|
|
|
|
(let loop ((reference (string-tokenize reference %not-slash))
|
|
|
|
|
(file (string-tokenize file %not-slash)))
|
|
|
|
|
(define (finish)
|
|
|
|
|
(string-join (append (make-list (length reference) "..") file)
|
|
|
|
|
"/"))
|
|
|
|
|
|
|
|
|
|
(match reference
|
|
|
|
|
(()
|
|
|
|
|
(finish))
|
|
|
|
|
((head . tail)
|
|
|
|
|
(match file
|
|
|
|
|
(()
|
|
|
|
|
(finish))
|
|
|
|
|
((head* . tail*)
|
|
|
|
|
(if (string=? head head*)
|
|
|
|
|
(loop tail tail*)
|
|
|
|
|
(finish)))))))
|
|
|
|
|
file))
|
|
|
|
|
|
2018-04-28 22:20:36 +02:00
|
|
|
|
(define (symlink-relative old new)
|
|
|
|
|
"Assuming both OLD and NEW are absolute file names, make NEW a symlink to
|
|
|
|
|
OLD, but using a relative file name."
|
|
|
|
|
(symlink (relative-file-name (dirname new) old)
|
|
|
|
|
new))
|
|
|
|
|
|
2012-10-29 22:39:46 +01:00
|
|
|
|
;;; union.scm ends here
|