union.scm 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2012, 2013, 2014, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2014 Mark H Weaver <mhw@netris.org>
  4. ;;; Copyright © 2017 Huang Ying <huang.ying.caritas@gmail.com>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (guix build union)
  21. #:use-module (ice-9 match)
  22. #:use-module (ice-9 format)
  23. #:use-module (srfi srfi-1)
  24. #:use-module (srfi srfi-26)
  25. #:use-module (rnrs bytevectors)
  26. #:use-module (rnrs io ports)
  27. #:export (union-build
  28. warn-about-collision
  29. relative-file-name
  30. symlink-relative))
  31. ;;; Commentary:
  32. ;;;
  33. ;;; Build a directory that is the union of a set of directories, using
  34. ;;; symbolic links.
  35. ;;;
  36. ;;; Code:
  37. ;; This code can be used with the bootstrap Guile, which is Guile 2.0, so
  38. ;; provide a compatibility layer.
  39. (cond-expand
  40. ((and guile-2 (not guile-2.2))
  41. (define (setvbuf port mode . rest)
  42. (apply (@ (guile) setvbuf) port
  43. (match mode
  44. ('line _IOLBF)
  45. ('block _IOFBF)
  46. ('none _IONBF))
  47. rest)))
  48. (else #f))
  49. (define (files-in-directory dirname)
  50. (let ((dir (opendir dirname)))
  51. (let loop ((files '()))
  52. (match (readdir dir)
  53. ((or "." "..")
  54. (loop files))
  55. ((? eof-object?)
  56. (closedir dir)
  57. (sort files string<?))
  58. (file
  59. (loop (cons file files)))))))
  60. (define (file-is-directory? file)
  61. (match (stat file #f)
  62. (#f #f) ;maybe a dangling symlink
  63. (st (eq? 'directory (stat:type st)))))
  64. (define (file=? file1 file2)
  65. "Return #t if FILE1 and FILE2 are regular files and their contents are
  66. identical, #f otherwise."
  67. (let ((st1 (stat file1 #f))
  68. (st2 (stat file2 #f)))
  69. ;; When deduplication is enabled, identical files share the same inode.
  70. (and st1 st2
  71. (or (= (stat:ino st1) (stat:ino st2))
  72. (and (eq? (stat:type st1) 'regular)
  73. (eq? (stat:type st2) 'regular)
  74. (= (stat:size st1) (stat:size st2))
  75. (call-with-input-file file1
  76. (lambda (port1)
  77. (call-with-input-file file2
  78. (lambda (port2)
  79. (define len 8192)
  80. (define buf1 (make-bytevector len))
  81. (define buf2 (make-bytevector len))
  82. (let loop ()
  83. (let ((n1 (get-bytevector-n! port1 buf1 0 len))
  84. (n2 (get-bytevector-n! port2 buf2 0 len)))
  85. (and (equal? n1 n2)
  86. (or (eof-object? n1)
  87. (loop))))))))))))))
  88. (define %harmless-collisions
  89. ;; This is a list of files that are known to collide, but for which emitting
  90. ;; a warning doesn't make sense. For example, "icon-theme.cache" is
  91. ;; regenerated by a profile hook which shadows the file provided by
  92. ;; individual packages, and "gschemas.compiled" is made available to
  93. ;; applications via 'glib-or-gtk-build-system'.
  94. '("icon-theme.cache" "gschemas.compiled"))
  95. (define (warn-about-collision files)
  96. "Handle the collision among FILES by emitting a warning and choosing the
  97. first one of THEM."
  98. (let ((file (first files)))
  99. (unless (member (basename file) %harmless-collisions)
  100. (format (current-error-port)
  101. "~%warning: collision encountered:~%~{ ~a~%~}"
  102. files)
  103. (format (current-error-port) "warning: choosing ~a~%" file))
  104. file))
  105. (define* (union-build output inputs
  106. #:key (log-port (current-error-port))
  107. (create-all-directories? #f)
  108. (symlink symlink)
  109. (resolve-collision warn-about-collision))
  110. "Build in the OUTPUT directory a symlink tree that is the union of all the
  111. INPUTS, using SYMLINK to create symlinks. As a special case, if
  112. CREATE-ALL-DIRECTORIES?, creates the subdirectories in the output directory to
  113. make sure the caller can modify them later.
  114. When two or more regular files collide, call RESOLVE-COLLISION with the list
  115. of colliding files and use the one that it returns; or, if RESOLVE-COLLISION
  116. returns #f, skip the faulty file altogether."
  117. (define (symlink* input output)
  118. (format log-port "`~a' ~~> `~a'~%" input output)
  119. (symlink input output))
  120. (define (resolve-collisions output dirs files)
  121. (cond ((null? dirs)
  122. ;; The inputs are all files.
  123. (match (resolve-collision files)
  124. (#f #f)
  125. ((? string? file)
  126. (symlink* file output))))
  127. (else
  128. ;; The inputs are a mixture of files and directories
  129. (error "union-build: collision between file and directories"
  130. `((files ,files) (dirs ,dirs))))))
  131. (define (union output inputs)
  132. (match inputs
  133. ((input)
  134. ;; There's only one input, so just make a link unless
  135. ;; create-all-directories?.
  136. (if (and create-all-directories? (file-is-directory? input))
  137. (union-of-directories output inputs)
  138. (symlink* input output)))
  139. (_
  140. (call-with-values (lambda () (partition file-is-directory? inputs))
  141. (match-lambda*
  142. ((dirs ())
  143. ;; All inputs are directories.
  144. (union-of-directories output dirs))
  145. ((() (file (? (cut file=? <> file)) ...))
  146. ;; There are no directories, and all files have the same contents,
  147. ;; so there's no conflict.
  148. (symlink* file output))
  149. ((dirs files)
  150. (resolve-collisions output dirs files)))))))
  151. (define (union-of-directories output dirs)
  152. ;; Create a new directory where we will merge the input directories.
  153. (mkdir output)
  154. ;; Build a hash table mapping each file to a list of input
  155. ;; directories containing that file.
  156. (let ((table (make-hash-table)))
  157. (define (add-to-table! file dir)
  158. (hash-set! table file (cons dir (hash-ref table file '()))))
  159. ;; Populate the table.
  160. (for-each (lambda (dir)
  161. (for-each (cut add-to-table! <> dir)
  162. (files-in-directory dir)))
  163. dirs)
  164. ;; Now iterate over the table and recursively
  165. ;; perform a union for each entry.
  166. (hash-for-each (lambda (file dirs-with-file)
  167. (union (string-append output "/" file)
  168. (map (cut string-append <> "/" file)
  169. (reverse dirs-with-file))))
  170. table)))
  171. (setvbuf (current-output-port) 'line)
  172. (setvbuf (current-error-port) 'line)
  173. (when (file-port? log-port)
  174. (setvbuf log-port 'line))
  175. (union-of-directories output (delete-duplicates inputs)))
  176. ;;;
  177. ;;; Relative symlinks.
  178. ;;;
  179. (define %not-slash
  180. (char-set-complement (char-set #\/)))
  181. (define (relative-file-name reference file)
  182. "Given REFERENCE and FILE, both of which are absolute file names, return the
  183. file name of FILE relative to REFERENCE.
  184. (relative-file-name \"/gnu/store/foo\" \"/gnu/store/bin/bar\")
  185. => \"../bin/bar\"
  186. Note that this is from a purely lexical standpoint; conversely, \"..\" is
  187. *not* resolved lexically on POSIX in the presence of symlinks."
  188. (if (and (string-prefix? "/" file) (string-prefix? "/" reference))
  189. (let loop ((reference (string-tokenize reference %not-slash))
  190. (file (string-tokenize file %not-slash)))
  191. (define (finish)
  192. (string-join (append (make-list (length reference) "..") file)
  193. "/"))
  194. (match reference
  195. (()
  196. (finish))
  197. ((head . tail)
  198. (match file
  199. (()
  200. (finish))
  201. ((head* . tail*)
  202. (if (string=? head head*)
  203. (loop tail tail*)
  204. (finish)))))))
  205. file))
  206. (define (symlink-relative old new)
  207. "Assuming both OLD and NEW are absolute file names, make NEW a symlink to
  208. OLD, but using a relative file name."
  209. (symlink (relative-file-name (dirname new) old)
  210. new))
  211. ;;; union.scm ends here