Introduction
Introduction Statistics Contact Development Disclaimer Help
tlibraries.lisp - clic - Clic is an command line interactive client for gopher …
git clone git://bitreich.org/clic/ git://hg6vgqziawt5s4dj.onion/clic/
Log
Files
Refs
Tags
LICENSE
---
tlibraries.lisp (18805B)
---
1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
2 ;;;
3 ;;; libraries.lisp --- Finding and loading foreign libraries.
4 ;;;
5 ;;; Copyright (C) 2005-2006, James Bielman <[email protected]>
6 ;;; Copyright (C) 2006-2007, Luis Oliveira <[email protected]>
7 ;;;
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:
15 ;;;
16 ;;; The above copyright notice and this permission notice shall be
17 ;;; included in all copies or substantial portions of the Software.
18 ;;;
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.
27 ;;;
28
29 (in-package #:cffi)
30
31 ;;;# Finding Foreign Libraries
32 ;;;
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.
37 ;;;
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
42 ;;; pathnames.
43 ;;;
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.
47
48 (defun explode-path-environment-variable (name)
49 (mapcar #'uiop:ensure-directory-pathname
50 (split-if (lambda (c) (eql #\: c))
51 (uiop:getenv name)
52 :elide)))
53
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))
57 #p"/usr/local/lib/"
58 #p"/usr/lib/")))
59
60 (defvar *foreign-library-directories*
61 (if (featurep :darwin)
62 '((explode-path-environment-variable "LD_LIBRARY_PATH")
63 (explode-path-environment-variable "DYLD_LIBRARY_PATH")
64 (uiop:getcwd)
65 (darwin-fallback-library-path))
66 '())
67 "List onto which user-defined library paths can be pushed.")
68
69 (defun fallback-darwin-framework-directories ()
70 (or (explode-path-environment-variable "DYLD_FALLBACK_FRAMEWORK_PATH")
71 (list (uiop:getcwd)
72 (merge-pathnames #p"Library/Frameworks/" (user-homedir-pathn…
73 #p"/Library/Frameworks/"
74 #p"/System/Library/Frameworks/")))
75
76 (defvar *darwin-framework-directories*
77 '((explode-path-environment-variable "DYLD_FRAMEWORK_PATH")
78 (fallback-darwin-framework-directories))
79 "List of directories where Frameworks are searched for.")
80
81 (defun mini-eval (form)
82 "Simple EVAL-like function to evaluate the elements of
83 *FOREIGN-LIBRARY-DIRECTORIES* and *DARWIN-FRAMEWORK-DIRECTORIES*."
84 (typecase form
85 (cons (apply (car form) (mapcar #'mini-eval (cdr form))))
86 (symbol (symbol-value form))
87 (t form)))
88
89 (defun parse-directories (list)
90 (mappend (compose #'ensure-list #'mini-eval) list))
91
92 (defun find-file (path directories)
93 "Searches for PATH in a list of DIRECTORIES and returns the first it f…
94 (some (lambda (directory) (probe-file (merge-pathnames path directory)…
95 directories))
96
97 (defun find-darwin-framework (framework-name)
98 "Searches for FRAMEWORK-NAME in *DARWIN-FRAMEWORK-DIRECTORIES*."
99 (dolist (directory (parse-directories *darwin-framework-directories*))
100 (let ((path (make-pathname
101 :name framework-name
102 :directory
103 (append (pathname-directory directory)
104 (list (format nil "~A.framework" framework-name…
105 (when (probe-file path)
106 (return-from find-darwin-framework path)))))
107
108 ;;;# Defining Foreign Libraries
109 ;;;
110 ;;; Foreign libraries can be defined using the
111 ;;; DEFINE-FOREIGN-LIBRARY macro. Example usage:
112 ;;;
113 ;;; (define-foreign-library opengl
114 ;;; (:darwin (:framework "OpenGL"))
115 ;;; (:unix (:or "libGL.so" "libGL.so.1"
116 ;;; #p"/myhome/mylibGL.so"))
117 ;;; (:windows "opengl32.dll")
118 ;;; ;; an hypothetical example of a particular platform
119 ;;; ((:and :some-system :some-cpu) "libGL-support.lib")
120 ;;; ;; if no other clauses apply, this one will and a type will be
121 ;;; ;; automagically appended to the name passed to :default
122 ;;; (t (:default "libGL")))
123 ;;;
124 ;;; This information is stored in the *FOREIGN-LIBRARIES* hashtable
125 ;;; and when the library is loaded through LOAD-FOREIGN-LIBRARY (or
126 ;;; USE-FOREIGN-LIBRARY) the first clause matched by FEATUREP is
127 ;;; processed.
128
129 (defvar *foreign-libraries* (make-hash-table :test 'eq)
130 "Hashtable of defined libraries.")
131
132 (defclass foreign-library ()
133 ((name :initform nil :initarg :name :accessor foreign-library-name)
134 (type :initform :system :initarg :type)
135 (spec :initarg :spec)
136 (options :initform nil :initarg :options)
137 (handle :initform nil :initarg :handle :accessor foreign-library-hand…
138 (pathname :initform nil)))
139
140 (defmethod print-object ((library foreign-library) stream)
141 (with-slots (name pathname) library
142 (print-unreadable-object (library stream :type t)
143 (when name
144 (format stream "~A" name))
145 (when pathname
146 (format stream " ~S" (file-namestring pathname))))))
147
148 (define-condition foreign-library-undefined-error (error)
149 ((name :initarg :name :reader fl-name))
150 (:report (lambda (c s)
151 (format s "Undefined foreign library: ~S"
152 (fl-name c)))))
153
154 (defun get-foreign-library (lib)
155 "Look up a library by NAME, signalling an error if not found."
156 (if (typep lib 'foreign-library)
157 lib
158 (or (gethash lib *foreign-libraries*)
159 (error 'foreign-library-undefined-error :name lib))))
160
161 (defun (setf get-foreign-library) (value name)
162 (setf (gethash name *foreign-libraries*) value))
163
164 (defun foreign-library-type (lib)
165 (slot-value (get-foreign-library lib) 'type))
166
167 (defun foreign-library-pathname (lib)
168 (slot-value (get-foreign-library lib) 'pathname))
169
170 (defun %foreign-library-spec (lib)
171 (assoc-if (lambda (feature)
172 (or (eq feature t)
173 (featurep feature)))
174 (slot-value lib 'spec)))
175
176 (defun foreign-library-spec (lib)
177 (second (%foreign-library-spec lib)))
178
179 (defun foreign-library-options (lib)
180 (append (cddr (%foreign-library-spec lib))
181 (slot-value lib 'options)))
182
183 (defun foreign-library-search-path (lib)
184 (loop for (opt val) on (foreign-library-options lib) by #'cddr
185 when (eql opt :search-path)
186 append (ensure-list val) into search-path
187 finally (return (mapcar #'pathname search-path))))
188
189 (defun foreign-library-loaded-p (lib)
190 (not (null (foreign-library-handle (get-foreign-library lib)))))
191
192 (defun list-foreign-libraries (&key (loaded-only t) type)
193 "Return a list of defined foreign libraries.
194 If LOADED-ONLY is non-null only loaded libraries are returned.
195 TYPE restricts the output to a specific library type: if NIL
196 all libraries are returned."
197 (let ((libs (hash-table-values *foreign-libraries*)))
198 (remove-if (lambda (lib)
199 (or (and type
200 (not (eql type (foreign-library-type lib))))
201 (and loaded-only
202 (not (foreign-library-loaded-p lib)))))
203 libs)))
204
205 ;; :CONVENTION, :CALLING-CONVENTION and :CCONV are coalesced,
206 ;; the former taking priority
207 ;; options with NULL values are removed
208 (defun clean-spec-up (spec)
209 (mapcar (lambda (x)
210 (list* (first x) (second x)
211 (let* ((opts (cddr x))
212 (cconv (getf opts :cconv))
213 (calling-convention (getf opts :calling-conven…
214 (convention (getf opts :convention))
215 (search-path (getf opts :search-path)))
216 (remf opts :cconv) (remf opts :calling-convention)
217 (when cconv
218 (warn-obsolete-argument :cconv :convention))
219 (when calling-convention
220 (warn-obsolete-argument :calling-convention
221 :convention))
222 (setf (getf opts :convention)
223 (or convention calling-convention cconv))
224 (setf (getf opts :search-path)
225 (mapcar #'pathname (ensure-list search-path)))
226 (loop for (opt val) on opts by #'cddr
227 when val append (list opt val) into new-opts
228 finally (return new-opts)))))
229 spec))
230
231 (defmethod initialize-instance :after
232 ((lib foreign-library) &key search-path
233 (cconv :cdecl cconv-p)
234 (calling-convention cconv calling-convention-p)
235 (convention calling-convention))
236 (with-slots (type options spec) lib
237 (check-type type (member :system :test :grovel-wrapper))
238 (setf spec (clean-spec-up spec))
239 (let ((all-options
240 (apply #'append options (mapcar #'cddr spec))))
241 (assert (subsetp (loop for (key . nil) on all-options by #'cddr
242 collect key)
243 '(:convention :search-path)))
244 (when cconv-p
245 (warn-obsolete-argument :cconv :convention))
246 (when calling-convention-p
247 (warn-obsolete-argument :calling-convention :convention))
248 (flet ((set-option (key value)
249 (when value (setf (getf options key) value))))
250 (set-option :convention convention)
251 (set-option :search-path
252 (mapcar #'pathname (ensure-list search-path)))))))
253
254 (defun register-foreign-library (name spec &rest options)
255 (let ((old-handle
256 (when-let ((old-lib (gethash name *foreign-libraries*)))
257 (foreign-library-handle old-lib))))
258 (setf (get-foreign-library name)
259 (apply #'make-instance 'foreign-library
260 :name name
261 :spec spec
262 :handle old-handle
263 options))
264 name))
265
266 (defmacro define-foreign-library (name-and-options &body pairs)
267 "Defines a foreign library NAME that can be posteriorly used with
268 the USE-FOREIGN-LIBRARY macro."
269 (destructuring-bind (name . options)
270 (ensure-list name-and-options)
271 (check-type name symbol)
272 `(register-foreign-library ',name ',pairs ,@options)))
273
274 ;;;# LOAD-FOREIGN-LIBRARY-ERROR condition
275 ;;;
276 ;;; The various helper functions that load foreign libraries can
277 ;;; signal this error when something goes wrong. We ignore the host's
278 ;;; error. We should probably reuse its error message.
279
280 (define-condition load-foreign-library-error (simple-error)
281 ())
282
283 (defun read-new-value ()
284 (format *query-io* "~&Enter a new value (unevaluated): ")
285 (force-output *query-io*)
286 (read *query-io*))
287
288 (defun fl-error (control &rest arguments)
289 (error 'load-foreign-library-error
290 :format-control control
291 :format-arguments arguments))
292
293 ;;;# Loading Foreign Libraries
294
295 (defun load-darwin-framework (name framework-name)
296 "Tries to find and load a darwin framework in one of the directories
297 in *DARWIN-FRAMEWORK-DIRECTORIES*. If unable to find FRAMEWORK-NAME,
298 it signals a LOAD-FOREIGN-LIBRARY-ERROR."
299 (let ((framework (find-darwin-framework framework-name)))
300 (if framework
301 (load-foreign-library-path name (native-namestring framework))
302 (fl-error "Unable to find framework ~A" framework-name))))
303
304 (defun report-simple-error (name error)
305 (fl-error "Unable to load foreign library (~A).~% ~A"
306 name
307 (format nil "~?" (simple-condition-format-control error)
308 (simple-condition-format-arguments error))))
309
310 ;;; FIXME: haven't double checked whether all Lisps signal a
311 ;;; SIMPLE-ERROR on %load-foreign-library failure. In any case they
312 ;;; should be throwing a more specific error.
313 (defun load-foreign-library-path (name path &optional search-path)
314 "Tries to load PATH using %LOAD-FOREIGN-LIBRARY which should try and
315 find it using the OS's usual methods. If that fails we try to find it
316 ourselves."
317 (handler-case
318 (values (%load-foreign-library name path)
319 (pathname path))
320 (simple-error (error)
321 (let ((dirs (parse-directories *foreign-library-directories*)))
322 (if-let (file (find-file path (append search-path dirs)))
323 (handler-case
324 (values (%load-foreign-library name (native-namestring fil…
325 file)
326 (simple-error (error)
327 (report-simple-error name error)))
328 (report-simple-error name error))))))
329
330 (defun try-foreign-library-alternatives (name library-list &optional sea…
331 "Goes through a list of alternatives and only signals an error when
332 none of alternatives were successfully loaded."
333 (dolist (lib library-list)
334 (multiple-value-bind (handle pathname)
335 (ignore-errors (load-foreign-library-helper name lib search-path…
336 (when handle
337 (return-from try-foreign-library-alternatives
338 (values handle pathname)))))
339 ;; Perhaps we should show the error messages we got for each
340 ;; alternative if we can figure out a nice way to do that.
341 (fl-error "Unable to load any of the alternatives:~% ~S" library-lis…
342
343 (defparameter *cffi-feature-suffix-map*
344 '((:windows . ".dll")
345 (:darwin . ".dylib")
346 (:unix . ".so")
347 (t . ".so"))
348 "Mapping of OS feature keywords to shared library suffixes.")
349
350 (defun default-library-suffix ()
351 "Return a string to use as default library suffix based on the
352 operating system. This is used to implement the :DEFAULT option.
353 This will need to be extended as we test on more OSes."
354 (or (cdr (assoc-if #'featurep *cffi-feature-suffix-map*))
355 (fl-error "Unable to determine the default library suffix on this …
356
357 (defun load-foreign-library-helper (name thing &optional search-path)
358 (etypecase thing
359 ((or pathname string)
360 (load-foreign-library-path name (filter-pathname thing) search-path…
361 (cons
362 (ecase (first thing)
363 (:framework (load-darwin-framework name (second thing)))
364 (:default
365 (unless (stringp (second thing))
366 (fl-error "Argument to :DEFAULT must be a string."))
367 (let ((library-path
368 (concatenate 'string
369 (second thing)
370 (default-library-suffix))))
371 (load-foreign-library-path name library-path search-path)))
372 (:or (try-foreign-library-alternatives name (rest thing) search-p…
373
374 (defun %do-load-foreign-library (library search-path)
375 (flet ((%do-load (lib name spec)
376 (when (foreign-library-spec lib)
377 (with-slots (handle pathname) lib
378 (setf (values handle pathname)
379 (load-foreign-library-helper
380 name spec (foreign-library-search-path lib)))))
381 lib))
382 (etypecase library
383 (symbol
384 (let* ((lib (get-foreign-library library))
385 (spec (foreign-library-spec lib)))
386 (%do-load lib library spec)))
387 ((or string list)
388 (let* ((lib-name (gensym
389 (format nil "~:@(~A~)-"
390 (if (listp library)
391 (first library)
392 (file-namestring library)))))
393 (lib (make-instance 'foreign-library
394 :type :system
395 :name lib-name
396 :spec `((t ,library))
397 :search-path search-path)))
398 ;; first try to load the anonymous library
399 ;; and register it only if that worked
400 (%do-load lib lib-name library)
401 (setf (get-foreign-library lib-name) lib))))))
402
403 (defun filter-pathname (thing)
404 (typecase thing
405 (pathname (namestring thing))
406 (t thing)))
407
408 (defun load-foreign-library (library &key search-path)
409 "Loads a foreign LIBRARY which can be a symbol denoting a library defi…
410 through DEFINE-FOREIGN-LIBRARY; a pathname or string in which case we tr…
411 load it directly first then search for it in *FOREIGN-LIBRARY-DIRECTORIE…
412 or finally list: either (:or lib1 lib2) or (:framework <framework-name>)…
413 (let ((library (filter-pathname library)))
414 (restart-case
415 (progn
416 ;; dlopen/dlclose does reference counting, but the CFFI-SYS
417 ;; API has no infrastructure to track that. Therefore if we
418 ;; want to avoid increasing the internal dlopen reference
419 ;; counter, and thus thwarting dlclose, then we need to try
420 ;; to call CLOSE-FOREIGN-LIBRARY and ignore any signaled
421 ;; errors.
422 (ignore-some-conditions (foreign-library-undefined-error)
423 (close-foreign-library library))
424 (%do-load-foreign-library library search-path))
425 ;; Offer these restarts that will retry the call to
426 ;; %LOAD-FOREIGN-LIBRARY.
427 (retry ()
428 :report "Try loading the foreign library again."
429 (load-foreign-library library :search-path search-path))
430 (use-value (new-library)
431 :report "Use another library instead."
432 :interactive read-new-value
433 (load-foreign-library new-library :search-path search-path)))))
434
435 (defmacro use-foreign-library (name)
436 `(load-foreign-library ',name))
437
438 ;;;# Closing Foreign Libraries
439
440 (defun close-foreign-library (library)
441 "Closes a foreign library."
442 (let* ((library (filter-pathname library))
443 (lib (get-foreign-library library))
444 (handle (foreign-library-handle lib)))
445 (when handle
446 (%close-foreign-library handle)
447 (setf (foreign-library-handle lib) nil)
448 t)))
449
450 (defun reload-foreign-libraries (&key (test #'foreign-library-loaded-p))
451 "(Re)load all currently loaded foreign libraries."
452 (let ((libs (list-foreign-libraries)))
453 (loop for l in libs
454 for name = (foreign-library-name l)
455 when (funcall test name)
456 do (load-foreign-library name))
457 libs))
You are viewing proxied material from bitreich.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.