aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--guix/build/gremlin.scm41
-rw-r--r--tests/gremlin.scm36
2 files changed, 77 insertions, 0 deletions
diff --git a/guix/build/gremlin.scm b/guix/build/gremlin.scm
index 44604827a9..a2d2169ddc 100644
--- a/guix/build/gremlin.scm
+++ b/guix/build/gremlin.scm
@@ -44,6 +44,7 @@
file-dynamic-info
file-runpath
file-needed
+ file-needed/recursive
missing-runpath-error?
missing-runpath-error-file
@@ -259,6 +260,46 @@ FILE lacks dynamic info."
dynamic info."
(and=> (file-dynamic-info file) elf-dynamic-info-needed))
+(define (file-needed/recursive file)
+ "Return two values: the list of absolute .so file names FILE depends on,
+recursively, and the list of .so file names that could not be found. File
+names are resolved by searching the RUNPATH of the file that NEEDs them.
+
+This is similar to the info returned by the 'ldd' command."
+ (let loop ((files (list file))
+ (result '())
+ (not-found '()))
+ (match files
+ (()
+ (values (reverse result)
+ (reverse (delete-duplicates not-found))))
+ ((file . rest)
+ (match (file-dynamic-info file)
+ (#f
+ (loop rest result not-found))
+ (info
+ (let ((runpath (elf-dynamic-info-runpath info))
+ (needed (elf-dynamic-info-needed info)))
+ (if (and runpath needed)
+ (let* ((runpath (map (cute expand-origin <> (dirname file))
+ runpath))
+ (resolved (map (cut search-path runpath <>)
+ needed))
+ (failed (filter-map (lambda (needed resolved)
+ (and (not resolved)
+ (not (libc-library? needed))
+ needed))
+ needed resolved))
+ (needed (remove (lambda (value)
+ (or (not value)
+ ;; XXX: quadratic
+ (member value result)))
+ resolved)))
+ (loop (append rest needed)
+ (append needed result)
+ (append failed not-found)))
+ (loop rest result not-found)))))))))
+
(define %libc-libraries
;; List of libraries as of glibc 2.21 (there are more but those are
;; typically mean to be LD_PRELOADed and thus do not appear as NEEDED.)
diff --git a/tests/gremlin.scm b/tests/gremlin.scm
index f191adb8b3..9ddac14265 100644
--- a/tests/gremlin.scm
+++ b/tests/gremlin.scm
@@ -27,6 +27,8 @@
#:use-module (srfi srfi-64)
#:use-module (rnrs io ports)
#:use-module (ice-9 popen)
+ #:use-module (ice-9 rdelim)
+ #:use-module (ice-9 regex)
#:use-module (ice-9 match))
(define %guile-executable
@@ -58,6 +60,40 @@
(string-take lib (string-contains lib ".so")))
(elf-dynamic-info-needed dyninfo))))))
+(unless (and %guile-executable (not (getenv "LD_LIBRARY_PATH"))
+ (file-needed %guile-executable)) ;statically linked?
+ (test-skip 1))
+(test-assert "file-needed/recursive"
+ (let* ((needed (file-needed/recursive %guile-executable))
+ (pipe (dynamic-wind
+ (lambda ()
+ ;; Tell ld.so to list loaded objects, like 'ldd' does.
+ (setenv "LD_TRACE_LOADED_OBJECTS" "yup"))
+ (lambda ()
+ (open-pipe* OPEN_READ %guile-executable))
+ (lambda ()
+ (unsetenv "LD_TRACE_LOADED_OBJECTS")))))
+ (define ldd-rx
+ (make-regexp "^[[:blank:]]+([[:graph:]]+ => )?([[:graph:]]+) .*$"))
+
+ (define (read-ldd-output port)
+ ;; Read from PORT output in GNU ldd format.
+ (let loop ((result '()))
+ (match (read-line port)
+ ((? eof-object?)
+ (reverse result))
+ ((= (cut regexp-exec ldd-rx <>) m)
+ (if m
+ (loop (cons (match:substring m 2) result))
+ (loop result))))))
+
+ (define ground-truth
+ (remove (cut string-prefix? "linux-vdso.so" <>)
+ (read-ldd-output pipe)))
+
+ (and (zero? (close-pipe pipe))
+ (lset= string=? (pk 'truth ground-truth) (pk 'needed needed)))))
+
(test-equal "expand-origin"
'("OOO/../lib"
"OOO"