1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
3 ;;; libraries.lisp --- Finding and loading foreign libraries.
5 ;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
6 ;;; Copyright (C) 2006-2007, Luis Oliveira <loliveira@common-lisp.net>
8 ;;; Permission is hereby granted, free of charge, to any person
9 ;;; obtaining a copy of this software and associated documentation
10 ;;; files (the "Software"), to deal in the Software without
11 ;;; restriction, including without limitation the rights to use, copy,
12 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies
13 ;;; of the Software, and to permit persons to whom the Software is
14 ;;; furnished to do so, subject to the following conditions:
16 ;;; The above copyright notice and this permission notice shall be
17 ;;; included in all copies or substantial portions of the Software.
19 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 ;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 ;;; DEALINGS IN THE SOFTWARE.
31 ;;;# Finding Foreign Libraries
33 ;;; We offer two ways for the user of a CFFI library to define
34 ;;; his/her own library directories: *FOREIGN-LIBRARY-DIRECTORIES*
35 ;;; for regular libraries and *DARWIN-FRAMEWORK-DIRECTORIES* for
36 ;;; Darwin frameworks.
38 ;;; These two special variables behave similarly to
39 ;;; ASDF:*CENTRAL-REGISTRY* as its arguments are evaluated before
40 ;;; being used. We used our MINI-EVAL instead of the full-blown EVAL
41 ;;; and the evaluated form should yield a single pathname or a list of
44 ;;; Only after failing to find a library through the normal ways
45 ;;; (eg: on Linux LD_LIBRARY_PATH, /etc/ld.so.cache, /usr/lib/, /lib)
46 ;;; do we try to find the library ourselves.
48 (defun explode-path-environment-variable (name)
49 (mapcar #'uiop
:ensure-directory-pathname
50 (split-if (lambda (c) (eql #\
: c
))
54 (defun darwin-fallback-library-path ()
55 (or (explode-path-environment-variable "DYLD_FALLBACK_LIBRARY_PATH")
56 (list (merge-pathnames #p
"lib/" (user-homedir-pathname))
61 (defvar *foreign-library-directories
*
62 (if (featurep :darwin
)
63 '((explode-path-environment-variable "LD_LIBRARY_PATH")
64 (explode-path-environment-variable "DYLD_LIBRARY_PATH")
66 (darwin-fallback-library-path))
68 "List onto which user-defined library paths can be pushed.")
70 (defun fallback-darwin-framework-directories ()
71 (or (explode-path-environment-variable "DYLD_FALLBACK_FRAMEWORK_PATH")
73 (merge-pathnames #p
"Library/Frameworks/" (user-homedir-pathname))
74 #p
"/Library/Frameworks/"
75 #p
"/System/Library/Frameworks/")))
77 (defvar *darwin-framework-directories
*
78 '((explode-path-environment-variable "DYLD_FRAMEWORK_PATH")
79 (fallback-darwin-framework-directories))
80 "List of directories where Frameworks are searched for.")
82 (defun mini-eval (form)
83 "Simple EVAL-like function to evaluate the elements of
84 *FOREIGN-LIBRARY-DIRECTORIES* and *DARWIN-FRAMEWORK-DIRECTORIES*."
86 (cons (apply (car form
) (mapcar #'mini-eval
(cdr form
))))
87 (symbol (symbol-value form
))
90 (defun parse-directories (list)
91 (mappend (compose #'ensure-list
#'mini-eval
) list
))
93 (defun find-file (path directories
)
94 "Searches for PATH in a list of DIRECTORIES and returns the first it finds."
95 (some (lambda (directory) (probe-file (merge-pathnames path directory
)))
98 (defun find-darwin-framework (framework-name)
99 "Searches for FRAMEWORK-NAME in *DARWIN-FRAMEWORK-DIRECTORIES*."
100 (dolist (directory (parse-directories *darwin-framework-directories
*))
101 (let ((path (make-pathname
104 (append (pathname-directory directory
)
105 (list (format nil
"~A.framework" framework-name
))))))
106 (when (probe-file path
)
107 (return-from find-darwin-framework path
)))))
109 ;;;# Defining Foreign Libraries
111 ;;; Foreign libraries can be defined using the
112 ;;; DEFINE-FOREIGN-LIBRARY macro. Example usage:
114 ;;; (define-foreign-library opengl
115 ;;; (:darwin (:framework "OpenGL"))
116 ;;; (:unix (:or "libGL.so" "libGL.so.1"
117 ;;; #p"/myhome/mylibGL.so"))
118 ;;; (:windows "opengl32.dll")
119 ;;; ;; an hypothetical example of a particular platform
120 ;;; ((:and :some-system :some-cpu) "libGL-support.lib")
121 ;;; ;; if no other clauses apply, this one will and a type will be
122 ;;; ;; automagically appended to the name passed to :default
123 ;;; (t (:default "libGL")))
125 ;;; This information is stored in the *FOREIGN-LIBRARIES* hashtable
126 ;;; and when the library is loaded through LOAD-FOREIGN-LIBRARY (or
127 ;;; USE-FOREIGN-LIBRARY) the first clause matched by FEATUREP is
130 (defvar *foreign-libraries
* (make-hash-table :test
'eq
)
131 "Hashtable of defined libraries.")
133 (defclass foreign-library
()
134 ((name :initform nil
:initarg
:name
:accessor foreign-library-name
)
135 (type :initform
:system
:initarg
:type
)
136 (spec :initarg
:spec
)
137 (options :initform nil
:initarg
:options
)
138 (handle :initform nil
:initarg
:handle
:accessor foreign-library-handle
)
139 (pathname :initform nil
)))
141 (defmethod print-object ((library foreign-library
) stream
)
142 (with-slots (name pathname
) library
143 (print-unreadable-object (library stream
:type t
)
145 (format stream
"~A" name
))
147 (format stream
" ~S" (file-namestring pathname
))))))
149 (define-condition foreign-library-undefined-error
(error)
150 ((name :initarg
:name
:reader fl-name
))
151 (:report
(lambda (c s
)
152 (format s
"Undefined foreign library: ~S"
155 (defun get-foreign-library (lib)
156 "Look up a library by NAME, signalling an error if not found."
157 (if (typep lib
'foreign-library
)
159 (or (gethash lib
*foreign-libraries
*)
160 (error 'foreign-library-undefined-error
:name lib
))))
162 (defun (setf get-foreign-library
) (value name
)
163 (setf (gethash name
*foreign-libraries
*) value
))
165 (defun foreign-library-type (lib)
166 (slot-value (get-foreign-library lib
) 'type
))
168 (defun foreign-library-pathname (lib)
169 (slot-value (get-foreign-library lib
) 'pathname
))
171 (defun %foreign-library-spec
(lib)
172 (assoc-if (lambda (feature)
175 (slot-value lib
'spec
)))
177 (defun foreign-library-spec (lib)
178 (second (%foreign-library-spec lib
)))
180 (defun foreign-library-options (lib)
181 (append (cddr (%foreign-library-spec lib
))
182 (slot-value lib
'options
)))
184 (defun foreign-library-search-path (lib)
185 (loop for
(opt val
) on
(foreign-library-options lib
) by
#'cddr
186 when
(eql opt
:search-path
)
187 append
(ensure-list val
) into search-path
188 finally
(return (mapcar #'pathname search-path
))))
190 (defun foreign-library-loaded-p (lib)
191 (not (null (foreign-library-handle (get-foreign-library lib
)))))
193 (defun list-foreign-libraries (&key
(loaded-only t
) type
)
194 "Return a list of defined foreign libraries.
195 If LOADED-ONLY is non-null only loaded libraries are returned.
196 TYPE restricts the output to a specific library type: if NIL
197 all libraries are returned."
198 (let ((libs (hash-table-values *foreign-libraries
*)))
199 (remove-if (lambda (lib)
201 (not (eql type
(foreign-library-type lib
))))
203 (not (foreign-library-loaded-p lib
)))))
206 ;; :CONVENTION, :CALLING-CONVENTION and :CCONV are coalesced,
207 ;; the former taking priority
208 ;; options with NULL values are removed
209 (defun clean-spec-up (spec)
211 (list* (first x
) (second x
)
212 (let* ((opts (cddr x
))
213 (cconv (getf opts
:cconv
))
214 (calling-convention (getf opts
:calling-convention
))
215 (convention (getf opts
:convention
))
216 (search-path (getf opts
:search-path
)))
217 (remf opts
:cconv
) (remf opts
:calling-convention
)
219 (warn-obsolete-argument :cconv
:convention
))
220 (when calling-convention
221 (warn-obsolete-argument :calling-convention
223 (setf (getf opts
:convention
)
224 (or convention calling-convention cconv
))
225 (setf (getf opts
:search-path
)
226 (mapcar #'pathname
(ensure-list search-path
)))
227 (loop for
(opt val
) on opts by
#'cddr
228 when val append
(list opt val
) into new-opts
229 finally
(return new-opts
)))))
232 (defmethod initialize-instance :after
233 ((lib foreign-library
) &key search-path
234 (cconv :cdecl cconv-p
)
235 (calling-convention cconv calling-convention-p
)
236 (convention calling-convention
))
237 (with-slots (type options spec
) lib
238 (check-type type
(member :system
:test
:grovel-wrapper
))
239 (setf spec
(clean-spec-up spec
))
241 (apply #'append options
(mapcar #'cddr spec
))))
242 (assert (subsetp (loop for
(key . nil
) on all-options by
#'cddr
244 '(:convention
:search-path
)))
246 (warn-obsolete-argument :cconv
:convention
))
247 (when calling-convention-p
248 (warn-obsolete-argument :calling-convention
:convention
))
249 (flet ((set-option (key value
)
250 (when value
(setf (getf options key
) value
))))
251 (set-option :convention convention
)
252 (set-option :search-path
253 (mapcar #'pathname
(ensure-list search-path
)))))))
255 (defun register-foreign-library (name spec
&rest options
)
257 (when-let ((old-lib (gethash name
*foreign-libraries
*)))
258 (foreign-library-handle old-lib
))))
259 (setf (get-foreign-library name
)
260 (apply #'make-instance
'foreign-library
267 (defmacro define-foreign-library
(name-and-options &body pairs
)
268 "Defines a foreign library NAME that can be posteriorly used with
269 the USE-FOREIGN-LIBRARY macro."
270 (destructuring-bind (name . options
)
271 (ensure-list name-and-options
)
272 (check-type name symbol
)
273 `(register-foreign-library ',name
',pairs
,@options
)))
275 ;;;# LOAD-FOREIGN-LIBRARY-ERROR condition
277 ;;; The various helper functions that load foreign libraries can
278 ;;; signal this error when something goes wrong. We ignore the host's
279 ;;; error. We should probably reuse its error message.
281 (define-condition load-foreign-library-error
(simple-error)
284 (defun read-new-value ()
285 (format *query-io
* "~&Enter a new value (unevaluated): ")
286 (force-output *query-io
*)
289 (defun fl-error (control &rest arguments
)
290 (error 'load-foreign-library-error
291 :format-control control
292 :format-arguments arguments
))
294 ;;;# Loading Foreign Libraries
296 (defun load-darwin-framework (name framework-name
)
297 "Tries to find and load a darwin framework in one of the directories
298 in *DARWIN-FRAMEWORK-DIRECTORIES*. If unable to find FRAMEWORK-NAME,
299 it signals a LOAD-FOREIGN-LIBRARY-ERROR."
300 (let ((framework (find-darwin-framework framework-name
)))
302 (load-foreign-library-path name
(native-namestring framework
))
303 (fl-error "Unable to find framework ~A" framework-name
))))
305 (defun report-simple-error (name error
)
306 (fl-error "Unable to load foreign library (~A).~% ~A"
308 (format nil
"~?" (simple-condition-format-control error
)
309 (simple-condition-format-arguments error
))))
311 ;;; FIXME: haven't double checked whether all Lisps signal a
312 ;;; SIMPLE-ERROR on %load-foreign-library failure. In any case they
313 ;;; should be throwing a more specific error.
314 (defun load-foreign-library-path (name path
&optional search-path
)
315 "Tries to load PATH using %LOAD-FOREIGN-LIBRARY which should try and
316 find it using the OS's usual methods. If that fails we try to find it
319 (values (%load-foreign-library name path
)
321 (simple-error (error)
322 (let ((dirs (parse-directories *foreign-library-directories
*)))
323 (if-let (file (find-file path
(append search-path dirs
)))
325 (values (%load-foreign-library name
(native-namestring file
))
327 (simple-error (error)
328 (report-simple-error name error
)))
329 (report-simple-error name error
))))))
331 (defun try-foreign-library-alternatives (name library-list
&optional search-path
)
332 "Goes through a list of alternatives and only signals an error when
333 none of alternatives were successfully loaded."
334 (dolist (lib library-list
)
335 (multiple-value-bind (handle pathname
)
336 (ignore-errors (load-foreign-library-helper name lib search-path
))
338 (return-from try-foreign-library-alternatives
339 (values handle pathname
)))))
340 ;; Perhaps we should show the error messages we got for each
341 ;; alternative if we can figure out a nice way to do that.
342 (fl-error "Unable to load any of the alternatives:~% ~S" library-list
))
344 (defparameter *cffi-feature-suffix-map
*
345 '((:windows .
".dll")
349 "Mapping of OS feature keywords to shared library suffixes.")
351 (defun default-library-suffix ()
352 "Return a string to use as default library suffix based on the
353 operating system. This is used to implement the :DEFAULT option.
354 This will need to be extended as we test on more OSes."
355 (or (cdr (assoc-if #'featurep
*cffi-feature-suffix-map
*))
356 (fl-error "Unable to determine the default library suffix on this OS.")))
358 (defun load-foreign-library-helper (name thing
&optional search-path
)
360 ((or pathname string
)
361 (load-foreign-library-path name
(filter-pathname thing
) search-path
))
364 (:framework
(load-darwin-framework name
(second thing
)))
366 (unless (stringp (second thing
))
367 (fl-error "Argument to :DEFAULT must be a string."))
371 (default-library-suffix))))
372 (load-foreign-library-path name library-path search-path
)))
373 (:or
(try-foreign-library-alternatives name
(rest thing
) search-path
))))))
375 (defun %do-load-foreign-library
(library search-path
)
376 (flet ((%do-load
(lib name spec
)
377 (when (foreign-library-spec lib
)
378 (with-slots (handle pathname
) lib
379 (setf (values handle pathname
)
380 (load-foreign-library-helper
381 name spec
(foreign-library-search-path lib
)))))
385 (let* ((lib (get-foreign-library library
))
386 (spec (foreign-library-spec lib
)))
387 (%do-load lib library spec
)))
389 (let* ((lib-name (gensym
390 (format nil
"~:@(~A~)-"
393 (file-namestring library
)))))
394 (lib (make-instance 'foreign-library
397 :spec
`((t ,library
))
398 :search-path search-path
)))
399 ;; first try to load the anonymous library
400 ;; and register it only if that worked
401 (%do-load lib lib-name library
)
402 (setf (get-foreign-library lib-name
) lib
))))))
404 (defun filter-pathname (thing)
406 (pathname (namestring thing
))
409 (defun load-foreign-library (library &key search-path
)
410 "Loads a foreign LIBRARY which can be a symbol denoting a library defined
411 through DEFINE-FOREIGN-LIBRARY; a pathname or string in which case we try to
412 load it directly first then search for it in *FOREIGN-LIBRARY-DIRECTORIES*;
413 or finally list: either (:or lib1 lib2) or (:framework <framework-name>)."
414 (let ((library (filter-pathname library
)))
417 ;; dlopen/dlclose does reference counting, but the CFFI-SYS
418 ;; API has no infrastructure to track that. Therefore if we
419 ;; want to avoid increasing the internal dlopen reference
420 ;; counter, and thus thwarting dlclose, then we need to try
421 ;; to call CLOSE-FOREIGN-LIBRARY and ignore any signaled
423 (ignore-some-conditions (foreign-library-undefined-error)
424 (close-foreign-library library
))
425 (%do-load-foreign-library library search-path
))
426 ;; Offer these restarts that will retry the call to
427 ;; %LOAD-FOREIGN-LIBRARY.
429 :report
"Try loading the foreign library again."
430 (load-foreign-library library
:search-path search-path
))
431 (use-value (new-library)
432 :report
"Use another library instead."
433 :interactive read-new-value
434 (load-foreign-library new-library
:search-path search-path
)))))
436 (defmacro use-foreign-library
(name)
437 `(load-foreign-library ',name
))
439 ;;;# Closing Foreign Libraries
441 (defun close-foreign-library (library)
442 "Closes a foreign library."
443 (let* ((library (filter-pathname library
))
444 (lib (get-foreign-library library
))
445 (handle (foreign-library-handle lib
)))
447 (%close-foreign-library handle
)
448 (setf (foreign-library-handle lib
) nil
)
451 (defun reload-foreign-libraries (&key
(test #'foreign-library-loaded-p
))
452 "(Re)load all currently loaded foreign libraries."
453 (let ((libs (list-foreign-libraries)))
455 for name
= (foreign-library-name l
)
456 when
(funcall test name
)
457 do
(load-foreign-library name
))