foreign-library.scm 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. ;;; Dynamically linking foreign libraries via dlopen and dlsym
  2. ;;; Copyright (C) 2021 Free Software Foundation, Inc.
  3. ;;;
  4. ;;; This library is free software: you can redistribute it and/or modify
  5. ;;; it under the terms of the GNU Lesser General Public License as
  6. ;;; published by the Free Software Foundation, either version 3 of the
  7. ;;; License, or (at your option) any later version.
  8. ;;;
  9. ;;; This library is distributed in the hope that it will be useful, but
  10. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;;; Lesser General Public License for more details.
  13. ;;;
  14. ;;; You should have received a copy of the GNU Lesser General Public
  15. ;;; License along with this program. If not, see
  16. ;;; <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;;;
  19. ;;; Implementation of dynamic-link.
  20. ;;;
  21. ;;; Code:
  22. (define-module (system foreign-library)
  23. #:use-module (ice-9 match)
  24. #:use-module (srfi srfi-9)
  25. #:use-module (system foreign)
  26. #:export (guile-extensions-path
  27. ltdl-library-path
  28. guile-system-extensions-path
  29. load-foreign-library
  30. foreign-library?
  31. foreign-library-pointer
  32. foreign-library-function))
  33. (define-record-type <foreign-library>
  34. (make-foreign-library filename handle)
  35. foreign-library?
  36. (filename foreign-library-filename)
  37. (handle foreign-library-handle set-foreign-library-handle!))
  38. (eval-when (expand load eval)
  39. (load-extension (string-append "libguile-" (effective-version))
  40. "scm_init_system_foreign_library"))
  41. (define system-library-extensions
  42. (cond
  43. ((string-contains %host-type "-darwin-")
  44. '(".bundle" ".so" ".dylib"))
  45. ((or (string-contains %host-type "cygwin")
  46. (string-contains %host-type "mingw")
  47. (string-contains %host-type "msys"))
  48. '(".dll"))
  49. (else
  50. '(".so"))))
  51. (define (has-extension? head exts)
  52. (match exts
  53. (() #f)
  54. ((ext . exts)
  55. (or (string-contains head ext)
  56. (has-extension? head exts)))))
  57. (define (file-exists-with-extension head exts)
  58. (if (has-extension? head exts)
  59. (and (file-exists? head) head)
  60. (let lp ((exts exts))
  61. (match exts
  62. (() #f)
  63. ((ext . exts)
  64. (let ((head (string-append head ext)))
  65. (if (file-exists? head)
  66. head
  67. (lp exts))))))))
  68. (define (file-exists-in-path-with-extension basename path exts)
  69. (match path
  70. (() #f)
  71. ((dir . path)
  72. (or (file-exists-with-extension (in-vicinity dir basename) exts)
  73. (file-exists-in-path-with-extension basename path exts)))))
  74. (define path-separator
  75. (case (system-file-name-convention)
  76. ((posix) #\:)
  77. ((windows) #\;)
  78. (else (error "unreachable"))))
  79. (define (parse-path var)
  80. (match (getenv var)
  81. (#f #f)
  82. ;; Ignore e.g. "export GUILE_SYSTEM_EXTENSIONS_PATH=".
  83. ("" '())
  84. (val (string-split val path-separator))))
  85. (define guile-extensions-path
  86. (make-parameter
  87. (or (parse-path "GUILE_EXTENSIONS_PATH") '())))
  88. (define ltdl-library-path
  89. (make-parameter
  90. (or (parse-path "LTDL_LIBRARY_PATH") '())))
  91. (define guile-system-extensions-path
  92. (make-parameter
  93. (or (parse-path "GUILE_SYSTEM_EXTENSIONS_PATH")
  94. (list (assq-ref %guile-build-info 'libdir)
  95. (assq-ref %guile-build-info 'extensiondir)))))
  96. ;; There are a few messy situations here related to libtool.
  97. ;;
  98. ;; Guile used to use libltdl, the dynamic library loader provided by
  99. ;; libtool. This loader used LTDL_LIBRARY_PATH, and for backwards
  100. ;; compatibility we still support that path.
  101. ;;
  102. ;; However, libltdl would not only open ".so" (or ".dll", etc) files,
  103. ;; but also the ".la" files created by libtool. In installed libraries
  104. ;; -- libraries that are in the target directories of "make install" --
  105. ;; .la files are never needed, to the extent that most GNU/Linux
  106. ;; distributions remove them entirely. It is sufficient to just load
  107. ;; the ".so" (or ".dll", etc) files.
  108. ;;
  109. ;; But for uninstalled dynamic libraries, like those in a build tree, it
  110. ;; is a bit of a mess. If you have a project that uses libtool to build
  111. ;; libraries -- which is the case for Guile, and for most projects using
  112. ;; autotools -- and you build foo.so in directory D, libtool will put
  113. ;; foo.la in D, but foo.so goes in D/.libs.
  114. ;;
  115. ;; The nice thing about ltdl was that it could load the .la file, even
  116. ;; from a build tree, preventing the existence of ".libs" from leaking
  117. ;; out to the user.
  118. ;;
  119. ;; We don't use libltdl now, essentially for flexibility and
  120. ;; error-reporting reasons. But, it would be nice to keep this old
  121. ;; use-case working. So as a stopgap solution, we add a ".libs" subdir
  122. ;; to the path for each entry in LTDL_LIBRARY_PATH, in case the .so is
  123. ;; there instead of alongside the .la file.
  124. (define (augment-ltdl-library-path path)
  125. (match path
  126. (() '())
  127. ((dir . path)
  128. (cons* dir (in-vicinity dir ".libs")
  129. (augment-ltdl-library-path path)))))
  130. (define (default-search-path search-ltdl-library-path?)
  131. (append
  132. (guile-extensions-path)
  133. (if search-ltdl-library-path?
  134. (augment-ltdl-library-path (ltdl-library-path))
  135. '())
  136. (guile-system-extensions-path)))
  137. (define* (load-foreign-library #:optional filename #:key
  138. (extensions system-library-extensions)
  139. (search-ltdl-library-path? #t)
  140. (search-path (default-search-path
  141. search-ltdl-library-path?))
  142. (search-system-paths? #t)
  143. (lazy? #t) (global? #f))
  144. (define (error-not-found)
  145. (scm-error 'misc-error "load-foreign-library"
  146. "file: ~S, message: ~S"
  147. (list filename "file not found")
  148. #f))
  149. (define flags
  150. (logior (if lazy? RTLD_LAZY RTLD_NOW)
  151. (if global? RTLD_GLOBAL RTLD_LOCAL)))
  152. (define (dlopen* name) (dlopen name flags))
  153. (make-foreign-library
  154. filename
  155. (cond
  156. ((not filename)
  157. ;; The self-open trick.
  158. (dlopen* #f))
  159. ((or (absolute-file-name? filename)
  160. (string-any file-name-separator? filename))
  161. (cond
  162. ((or (file-exists-with-extension filename extensions)
  163. (and search-ltdl-library-path?
  164. (file-exists-with-extension
  165. (in-vicinity (in-vicinity (dirname filename) ".libs")
  166. (basename filename))
  167. extensions)))
  168. => dlopen*)
  169. (else
  170. (error-not-found))))
  171. ((file-exists-in-path-with-extension filename search-path extensions)
  172. => dlopen*)
  173. (search-system-paths?
  174. (if (or (null? extensions) (has-extension? filename extensions))
  175. (dlopen* filename)
  176. (let lp ((extensions extensions))
  177. (match extensions
  178. ((extension)
  179. ;; Open in tail position to propagate any exception.
  180. (dlopen* (string-append filename extension)))
  181. ((extension . extensions)
  182. ;; If there is more than one extension, unfortunately we
  183. ;; only report the error for the last extension. This is
  184. ;; not great because maybe the library was found with the
  185. ;; first extension, failed to load and had an interesting
  186. ;; error, but then we swallowed that interesting error and
  187. ;; proceeded, eventually throwing a "file not found"
  188. ;; exception. FIXME to use more structured exceptions and
  189. ;; stop if the error that we get is more specific than
  190. ;; just "file not found".
  191. (or (false-if-exception
  192. (dlopen* (string-append filename extension)))
  193. (lp extensions)))))))
  194. (else
  195. (error-not-found)))))
  196. (define (->foreign-library lib)
  197. (if (foreign-library? lib)
  198. lib
  199. (load-foreign-library lib)))
  200. (define* (foreign-library-pointer lib name)
  201. (let ((handle (foreign-library-handle (->foreign-library lib))))
  202. (dlsym handle name)))
  203. (define* (foreign-library-function lib name
  204. #:key
  205. (return-type void)
  206. (arg-types '())
  207. (return-errno? #f))
  208. (let ((pointer (foreign-library-pointer lib name)))
  209. (pointer->procedure return-type pointer arg-types
  210. #:return-errno? return-errno?)))