missing-propagated-inputs.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. ;;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
  2. ;;;
  3. ;;; This module is free software; you can redistribute it and/or modify it
  4. ;;; under the terms of the GNU General Public License as published by
  5. ;;; the Free Software Foundation; either version 3 of the License, or (at
  6. ;;; your option) any later version.
  7. ;;;
  8. ;;; This module is distributed in the hope that it will be useful,
  9. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;;; GNU General Public License for more details.
  12. ;;;
  13. ;;; You should have received a copy of the GNU General Public License
  14. ;;; along with this module. If not, see <http://www.gnu.org/licenses/>.
  15. (define-module (guix missing-propagated-inputs)
  16. #:export (fold-missing-propagated-inputs
  17. print-missing-propagated-inputs))
  18. (use-modules (guix build utils)
  19. (guix derivations)
  20. (guix packages)
  21. (guix store)
  22. (gnu packages)
  23. (srfi srfi-1)
  24. (srfi srfi-13)
  25. (srfi srfi-26)
  26. (ice-9 ftw)
  27. (ice-9 match)
  28. (ice-9 regex)
  29. (ice-9 rdelim))
  30. ;;; Helpers
  31. (define (map* n proc . lists)
  32. "Like `map', but PROC must return a list of N elements; the elements are
  33. gathered in separate lists and a list of those lists returned. If any of LISTS
  34. is empty, a list of N empty lists is returned.
  35. (10 20 30) -> (^x (list (double x) (halve x))) -> ((20 40 60) (5 10 15))"
  36. (let ((results (apply map proc lists)))
  37. (if (null? results)
  38. (make-list n '())
  39. (apply map list results))))
  40. (define (read-line*)
  41. "Like read-line, but merges lines separated by a backslash."
  42. (let ((line (read-line)))
  43. (if (eof-object? line)
  44. line
  45. (let lp ((line line))
  46. (let ((match (string-match "(.*)\\\\$" line)))
  47. (if (not match)
  48. line
  49. (let* ((line (match:substring match 1))
  50. (next-line (read-line)))
  51. ;; Ignore backslash at end of last line.
  52. (if (eof-object? next-line)
  53. line
  54. (lp (string-append line next-line))))))))))
  55. (define (fold-lines init proc)
  56. "Fold over input lines, merging backslash-separated lines."
  57. (let lp ((value init)
  58. (line (read-line*)))
  59. (if (eof-object? line)
  60. value
  61. (lp (proc line value)
  62. (read-line)))))
  63. (define (package-output-paths store package)
  64. "Returns an alist of outputs/paths of PACKAGE."
  65. (map (lambda (entry)
  66. (cons (car entry)
  67. (derivation-output-path (cdr entry))))
  68. (derivation-outputs
  69. (package-derivation store package))))
  70. (define (list-libs-in-directory dir)
  71. "Returns a list of the names of the libraries found in DIR. I.e. for every
  72. file \"libfoo.so[.xyz]\" under DIR, the list contains \"foo\"."
  73. (let* ((libs (scandir dir (cut string-match "^lib.*\\.so[.0-9]*" <>)))
  74. (libnames
  75. (map (lambda (lib)
  76. (let ((match (string-match "^lib(.*)\\.so[.0-9]*" lib)))
  77. (match:substring match 1)))
  78. libs)))
  79. libnames))
  80. ;;; Standard libc libraries
  81. (define (list-standard-libc-libraries)
  82. "Returns a list of library names that are components of libc."
  83. (with-store store
  84. (let* ((glibc (car (find-best-packages-by-name "glibc" #f)))
  85. (glibc-dir (assoc-ref (package-output-paths store glibc) "out"))
  86. (lib-dir (string-append glibc-dir "/lib")))
  87. (list-libs-in-directory lib-dir))))
  88. (define standard-libc-libraries (make-parameter '()))
  89. ;;; .pc parsing
  90. (define (parse-requires string)
  91. "Parses the contents of a pkg-config Requires line."
  92. (let lp ((requires '())
  93. (rest string))
  94. (cond
  95. ;; Some files use commas to separate Requires elements.
  96. ((string-match "[[:space:]]*([^[:space:],]+),?(.*)" rest)
  97. => (lambda (match)
  98. (let* ((part (match:substring match 1))
  99. (rest (match:substring match 2))
  100. (rest-match
  101. (string-match
  102. "[[:space:]]*(=|<=|>=|<|>)[[:space:]]*[^[:space:]]+(.*)"
  103. rest)))
  104. (if rest-match
  105. (lp (cons part requires)
  106. (match:substring rest-match 2))
  107. (lp (cons part requires)
  108. rest)))))
  109. (else
  110. requires))))
  111. (define (parse-libs string)
  112. "Parses the contents of a pkg-config Libs line."
  113. (let lp ((libs '())
  114. (rest string))
  115. (cond
  116. ((string-match "[[:space:]]*([^[:space:]]+)(.*)" rest)
  117. => (lambda (match)
  118. (let ((part (match:substring match 1))
  119. (rest (match:substring match 2)))
  120. (lp (cons part libs)
  121. rest))))
  122. (else
  123. libs))))
  124. (define (parse-pc-file)
  125. "Parses a pkg-config file from the current input port, returning a list of two
  126. lists: the Requires and Libs elements."
  127. (define (apply-variables variables line)
  128. (cond
  129. ((string-match "(.*)\\$\\{([a-zA-Z0-9_]+)\\}(.*)" line)
  130. => (lambda (match)
  131. (let ((pre (match:substring match 1))
  132. (var (match:substring match 2))
  133. (post (match:substring match 3)))
  134. (apply-variables
  135. variables
  136. (string-append pre (or (assoc-ref variables var)
  137. ;; Prevent infinite recursion.
  138. (string-append "%{" var "}"))
  139. post)))))
  140. (else line)))
  141. (define (handle-hash-comment line)
  142. (cond
  143. ((string-match "(.*) #.*" line)
  144. => (lambda (match)
  145. (match:substring match 1)))
  146. (else line)))
  147. (match-let
  148. (((variables requires libs)
  149. (fold-lines
  150. (list '() '() '())
  151. (lambda (line state)
  152. (match-let (((variables requires libs) state))
  153. (let ((line (handle-hash-comment
  154. (apply-variables variables line))))
  155. (cond
  156. ((string-match "^([a-zA-Z0-9_]+)=(.*)" line)
  157. => (lambda (match)
  158. (let ((var (match:substring match 1))
  159. (val (match:substring match 2)))
  160. (list (alist-cons var val variables) requires libs))))
  161. ((string-match "^Requires(\\.private)?:(.*)" line)
  162. => (lambda (match)
  163. (let ((requires* (parse-requires
  164. (match:substring match 2))))
  165. (list variables (append requires requires*) libs))))
  166. ((string-match "^Libs:(.*)" line)
  167. => (lambda (match)
  168. (let ((libs* (parse-libs (match:substring match 1))))
  169. (list variables requires (append libs libs*)))))
  170. (else
  171. (list variables requires libs)))))))))
  172. (list requires libs)))
  173. (define (requires-and-libs-for-path path)
  174. "Parses all *.pc files under PATH and returns a list of two lists: all
  175. Requires and Libs elements found."
  176. (let lp ((files (find-files path "\\.pc$"))
  177. (requires '())
  178. (libs '()))
  179. (if (null? files)
  180. (list requires libs)
  181. (match-let (((requires* libs*)
  182. (with-input-from-file (car files) parse-pc-file)))
  183. (lp (cdr files)
  184. (append requires requires*)
  185. (append libs libs*))))))
  186. (define (missing-packages input-dirs requires)
  187. "Filters pkg-config package names REQUIRES to those not satisfied by .pc files
  188. in the INPUT-DIRS directories, which are Guix store items."
  189. ;; XXX Only consider .pc files in $prefix/lib/pkgconfig, or all?
  190. (let* ((pc-dirs (filter file-exists?
  191. (map (lambda (dir)
  192. (string-append dir "/lib/pkgconfig"))
  193. input-dirs)))
  194. (pc-files (apply append (map (cut find-files <> "\\.pc$") pc-dirs)))
  195. (basenames (map basename pc-files))
  196. (pkgnames (map (lambda (name)
  197. (let ((match (string-match "(.*)\\.pc$" name)))
  198. (match:substring match 1)))
  199. basenames)))
  200. (delete-duplicates
  201. (lset-difference string=? requires pkgnames))))
  202. (define (missing-libraries input-dirs libs)
  203. "Returns a list of library names which the linker argument list LIBS wants to
  204. link against but would not find given -L flags in the argument list and lib/
  205. subdirectories of INPUT-DIRS."
  206. (let* ((link-flags (filter (cut string-prefix? "-l" <>) libs))
  207. (link-libs (map (lambda (flag)
  208. (let ((match (string-match "-l(.*)" flag)))
  209. (match:substring match 1)))
  210. link-flags))
  211. (lib-dir-flags (filter (cut string-prefix? "-L" <>) libs))
  212. (lib-dirs (filter
  213. (cut access? <> (logior R_OK X_OK))
  214. (append
  215. (map (lambda (flag)
  216. (let ((match (string-match "-L(.*)" flag)))
  217. (match:substring match 1)))
  218. lib-dir-flags)
  219. (map (cut string-append <> "/lib") input-dirs))))
  220. (found-libs (append
  221. (apply append (map list-libs-in-directory lib-dirs))
  222. (standard-libc-libraries))))
  223. (delete-duplicates
  224. (lset-difference string=? link-libs found-libs))))
  225. ;;; Package path iteration
  226. (define (fold-package-outputs store proc init)
  227. "Folds over packages/outputs, calling PROC with the package, output name,
  228. output path, and the fold accumulation value."
  229. (fold-packages
  230. (lambda (package acc)
  231. (let ((outputs (package-output-paths store package)))
  232. (fold (lambda (output acc)
  233. (let ((name (car output))
  234. (path (cdr output)))
  235. (if (file-exists? path)
  236. (proc package name path acc)
  237. acc)))
  238. acc
  239. outputs)))
  240. init))
  241. ;;; Main
  242. (define (fold-missing-propagated-inputs proc init)
  243. "Folds over packages that seem to have missing propagated inputs, calling PROC
  244. with the package, the name of its output for which inputs seem to be missing,
  245. the list of missing pkg-config packages, the list of missing dynamic libraries,
  246. and the fold accumulation value. The detection is heuristic; false negatives
  247. and positives are both likely. Detection works via packages's outputs in the
  248. store, so missing outputs will limit the detection."
  249. (parameterize ((standard-libc-libraries (list-standard-libc-libraries)))
  250. (with-store store
  251. (fold-package-outputs
  252. store
  253. (lambda (package output path acc)
  254. (let* ((inputs (package-propagated-inputs package))
  255. (input-dirs
  256. (map
  257. (match-lambda
  258. ((name package out)
  259. (assoc-ref (package-output-paths store package) out))
  260. ((name package)
  261. (assoc-ref (package-output-paths store package) "out")))
  262. inputs))
  263. (input-dirs (cons path input-dirs)))
  264. (when (every file-exists? input-dirs)
  265. (match-let (((requires libs) (requires-and-libs-for-path path)))
  266. (let ((missing-pc-pkgs (missing-packages input-dirs requires))
  267. (missing-libs (missing-libraries input-dirs libs)))
  268. (unless (and (null? missing-pc-pkgs) (null? missing-libs))
  269. (proc package output missing-pc-pkgs missing-libs acc)))))))
  270. init))))
  271. (define (print-missing-propagated-inputs)
  272. (fold-missing-propagated-inputs
  273. (lambda (pkg out pc-pkgs libs acc)
  274. (format #t "~a~a: pkg-config: ~s libs: ~s\n"
  275. (package-name pkg)
  276. (if (string=? "out" out) "" (string-append ":" out))
  277. pc-pkgs
  278. libs))
  279. #f))