union.scm 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2012, 2013, 2014, 2016, 2017, 2018, 2019, 2021 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'; "etc/ld.so.cache" is created
  94. ;; for most packages.
  95. '("icon-theme.cache" "gschemas.compiled" "ld.so.cache"))
  96. (define (warn-about-collision files)
  97. "Handle the collision among FILES by emitting a warning and choosing the
  98. first one of THEM."
  99. (let ((file (first files)))
  100. (unless (member (basename file) %harmless-collisions)
  101. (format (current-error-port)
  102. "~%warning: collision encountered:~%~{ ~a~%~}"
  103. files)
  104. (format (current-error-port) "warning: choosing ~a~%" file))
  105. file))
  106. (define* (union-build output inputs
  107. #:key (log-port (current-error-port))
  108. (create-all-directories? #f)
  109. (symlink symlink)
  110. (resolve-collision warn-about-collision))
  111. "Build in the OUTPUT directory a symlink tree that is the union of all the
  112. INPUTS, using SYMLINK to create symlinks. As a special case, if
  113. CREATE-ALL-DIRECTORIES?, creates the subdirectories in the output directory to
  114. make sure the caller can modify them later.
  115. When two or more regular files collide, call RESOLVE-COLLISION with the list
  116. of colliding files and use the one that it returns; or, if RESOLVE-COLLISION
  117. returns #f, skip the faulty file altogether."
  118. (define (symlink* input output)
  119. (format log-port "`~a' ~~> `~a'~%" input output)
  120. (symlink input output))
  121. (define (resolve-collisions output dirs files)
  122. (cond ((null? dirs)
  123. ;; The inputs are all files.
  124. (match (resolve-collision files)
  125. (#f #f)
  126. ((? string? file)
  127. (symlink* file output))))
  128. (else
  129. ;; The inputs are a mixture of files and directories
  130. (error "union-build: collision between file and directories"
  131. `((files ,files) (dirs ,dirs))))))
  132. (define (union output inputs)
  133. (match inputs
  134. ((input)
  135. ;; There's only one input, so just make a link unless
  136. ;; create-all-directories?.
  137. (if (and create-all-directories? (file-is-directory? input))
  138. (union-of-directories output inputs)
  139. (symlink* input output)))
  140. (_
  141. (call-with-values (lambda () (partition file-is-directory? inputs))
  142. (match-lambda*
  143. ((dirs ())
  144. ;; All inputs are directories.
  145. (union-of-directories output dirs))
  146. ((() (file (? (cut file=? <> file)) ...))
  147. ;; There are no directories, and all files have the same contents,
  148. ;; so there's no conflict.
  149. (symlink* file output))
  150. ((dirs files)
  151. (resolve-collisions output dirs files)))))))
  152. (define (union-of-directories output dirs)
  153. ;; Create a new directory where we will merge the input directories.
  154. (mkdir output)
  155. ;; Build a hash table mapping each file to a list of input
  156. ;; directories containing that file.
  157. (let ((table (make-hash-table)))
  158. (define (add-to-table! file dir)
  159. (hash-set! table file (cons dir (hash-ref table file '()))))
  160. ;; Populate the table.
  161. (for-each (lambda (dir)
  162. (for-each (cut add-to-table! <> dir)
  163. (files-in-directory dir)))
  164. dirs)
  165. ;; Now iterate over the table and recursively
  166. ;; perform a union for each entry.
  167. (hash-for-each (lambda (file dirs-with-file)
  168. (union (string-append output "/" file)
  169. (map (cut string-append <> "/" file)
  170. (reverse dirs-with-file))))
  171. table)))
  172. (setvbuf (current-output-port) 'line)
  173. (setvbuf (current-error-port) 'line)
  174. (when (file-port? log-port)
  175. (setvbuf log-port 'line))
  176. (union-of-directories output (delete-duplicates inputs)))
  177. ;;;
  178. ;;; Relative symlinks.
  179. ;;;
  180. (define %not-slash
  181. (char-set-complement (char-set #\/)))
  182. (define (relative-file-name reference file)
  183. "Given REFERENCE and FILE, both of which are absolute file names, return the
  184. file name of FILE relative to REFERENCE.
  185. (relative-file-name \"/gnu/store/foo\" \"/gnu/store/bin/bar\")
  186. => \"../bin/bar\"
  187. Note that this is from a purely lexical standpoint; conversely, \"..\" is
  188. *not* resolved lexically on POSIX in the presence of symlinks."
  189. (if (and (string-prefix? "/" file) (string-prefix? "/" reference))
  190. (let loop ((reference (string-tokenize reference %not-slash))
  191. (file (string-tokenize file %not-slash)))
  192. (define (finish)
  193. (string-join (append (make-list (length reference) "..") file)
  194. "/"))
  195. (match reference
  196. (()
  197. (finish))
  198. ((head . tail)
  199. (match file
  200. (()
  201. (finish))
  202. ((head* . tail*)
  203. (if (string=? head head*)
  204. (loop tail tail*)
  205. (finish)))))))
  206. file))
  207. (define (symlink-relative old new)
  208. "Assuming both OLD and NEW are absolute file names, make NEW a symlink to
  209. OLD, but using a relative file name."
  210. (symlink (relative-file-name (dirname new) old)
  211. new))
  212. ;;; union.scm ends here