libraries.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
       libraries.lisp (18837B)
       ---
            1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
            2 ;;;
            3 ;;; libraries.lisp --- Finding and loading foreign libraries.
            4 ;;;
            5 ;;; Copyright (C) 2005-2006, James Bielman  <jamesjb@jamesjb.com>
            6 ;;; Copyright (C) 2006-2007, Luis Oliveira  <loliveira@common-lisp.net>
            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"/opt/local/lib/"
           58             #p"/usr/local/lib/"
           59             #p"/usr/lib/")))
           60 
           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")
           65         (uiop:getcwd)
           66         (darwin-fallback-library-path))
           67       '())
           68   "List onto which user-defined library paths can be pushed.")
           69 
           70 (defun fallback-darwin-framework-directories ()
           71   (or (explode-path-environment-variable "DYLD_FALLBACK_FRAMEWORK_PATH")
           72       (list (uiop:getcwd)
           73             (merge-pathnames #p"Library/Frameworks/" (user-homedir-pathname))
           74             #p"/Library/Frameworks/"
           75             #p"/System/Library/Frameworks/")))
           76 
           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.")
           81 
           82 (defun mini-eval (form)
           83   "Simple EVAL-like function to evaluate the elements of
           84 *FOREIGN-LIBRARY-DIRECTORIES* and *DARWIN-FRAMEWORK-DIRECTORIES*."
           85   (typecase form
           86     (cons (apply (car form) (mapcar #'mini-eval (cdr form))))
           87     (symbol (symbol-value form))
           88     (t form)))
           89 
           90 (defun parse-directories (list)
           91   (mappend (compose #'ensure-list #'mini-eval) list))
           92 
           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)))
           96         directories))
           97 
           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
          102                  :name framework-name
          103                  :directory
          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)))))
          108 
          109 ;;;# Defining Foreign Libraries
          110 ;;;
          111 ;;; Foreign libraries can be defined using the
          112 ;;; DEFINE-FOREIGN-LIBRARY macro. Example usage:
          113 ;;;
          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")))
          124 ;;;
          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
          128 ;;; processed.
          129 
          130 (defvar *foreign-libraries* (make-hash-table :test 'eq)
          131   "Hashtable of defined libraries.")
          132 
          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)))
          140 
          141 (defmethod print-object ((library foreign-library) stream)
          142   (with-slots (name pathname) library
          143     (print-unreadable-object (library stream :type t)
          144       (when name
          145         (format stream "~A" name))
          146       (when pathname
          147         (format stream " ~S" (file-namestring pathname))))))
          148 
          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"
          153                      (fl-name c)))))
          154 
          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)
          158       lib
          159       (or (gethash lib *foreign-libraries*)
          160           (error 'foreign-library-undefined-error :name lib))))
          161 
          162 (defun (setf get-foreign-library) (value name)
          163   (setf (gethash name *foreign-libraries*) value))
          164 
          165 (defun foreign-library-type (lib)
          166   (slot-value (get-foreign-library lib) 'type))
          167 
          168 (defun foreign-library-pathname (lib)
          169   (slot-value (get-foreign-library lib) 'pathname))
          170 
          171 (defun %foreign-library-spec (lib)
          172   (assoc-if (lambda (feature)
          173               (or (eq feature t)
          174                   (featurep feature)))
          175             (slot-value lib 'spec)))
          176 
          177 (defun foreign-library-spec (lib)
          178   (second (%foreign-library-spec lib)))
          179 
          180 (defun foreign-library-options (lib)
          181   (append (cddr (%foreign-library-spec lib))
          182           (slot-value lib 'options)))
          183 
          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))))
          189 
          190 (defun foreign-library-loaded-p (lib)
          191   (not (null (foreign-library-handle (get-foreign-library lib)))))
          192 
          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)
          200                  (or (and type
          201                           (not (eql type (foreign-library-type lib))))
          202                      (and loaded-only
          203                           (not (foreign-library-loaded-p lib)))))
          204                libs)))
          205 
          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)
          210   (mapcar (lambda (x)
          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)
          218                      (when cconv
          219                        (warn-obsolete-argument :cconv :convention))
          220                      (when calling-convention
          221                        (warn-obsolete-argument :calling-convention
          222                                                :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)))))
          230           spec))
          231 
          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))
          240     (let ((all-options
          241            (apply #'append options (mapcar #'cddr spec))))
          242       (assert (subsetp (loop for (key . nil) on all-options by #'cddr
          243                              collect key)
          244                        '(:convention :search-path)))
          245       (when cconv-p
          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)))))))
          254 
          255 (defun register-foreign-library (name spec &rest options)
          256   (let ((old-handle
          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
          261                  :name name
          262                  :spec spec
          263                  :handle old-handle
          264                  options))
          265     name))
          266 
          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)))
          274 
          275 ;;;# LOAD-FOREIGN-LIBRARY-ERROR condition
          276 ;;;
          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.
          280 
          281 (define-condition load-foreign-library-error (simple-error)
          282   ())
          283 
          284 (defun read-new-value ()
          285   (format *query-io* "~&Enter a new value (unevaluated): ")
          286   (force-output *query-io*)
          287   (read *query-io*))
          288 
          289 (defun fl-error (control &rest arguments)
          290   (error 'load-foreign-library-error
          291          :format-control control
          292          :format-arguments arguments))
          293 
          294 ;;;# Loading Foreign Libraries
          295 
          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)))
          301     (if framework
          302         (load-foreign-library-path name (native-namestring framework))
          303         (fl-error "Unable to find framework ~A" framework-name))))
          304 
          305 (defun report-simple-error (name error)
          306   (fl-error "Unable to load foreign library (~A).~%  ~A"
          307             name
          308             (format nil "~?" (simple-condition-format-control error)
          309                     (simple-condition-format-arguments error))))
          310 
          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
          317 ourselves."
          318   (handler-case
          319       (values (%load-foreign-library name path)
          320               (pathname path))
          321     (simple-error (error)
          322       (let ((dirs (parse-directories *foreign-library-directories*)))
          323         (if-let (file (find-file path (append search-path dirs)))
          324           (handler-case
          325               (values (%load-foreign-library name (native-namestring file))
          326                       file)
          327             (simple-error (error)
          328               (report-simple-error name error)))
          329           (report-simple-error name error))))))
          330 
          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))
          337       (when handle
          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))
          343 
          344 (defparameter *cffi-feature-suffix-map*
          345   '((:windows . ".dll")
          346     (:darwin . ".dylib")
          347     (:unix . ".so")
          348     (t . ".so"))
          349   "Mapping of OS feature keywords to shared library suffixes.")
          350 
          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.")))
          357 
          358 (defun load-foreign-library-helper (name thing &optional search-path)
          359   (etypecase thing
          360     ((or pathname string)
          361      (load-foreign-library-path name (filter-pathname thing) search-path))
          362     (cons
          363      (ecase (first thing)
          364        (:framework (load-darwin-framework name (second thing)))
          365        (:default
          366         (unless (stringp (second thing))
          367           (fl-error "Argument to :DEFAULT must be a string."))
          368         (let ((library-path
          369                (concatenate 'string
          370                             (second thing)
          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))))))
          374 
          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)))))
          382            lib))
          383     (etypecase library
          384       (symbol
          385        (let* ((lib (get-foreign-library library))
          386               (spec (foreign-library-spec lib)))
          387          (%do-load lib library spec)))
          388       ((or string list)
          389        (let* ((lib-name (gensym
          390                          (format nil "~:@(~A~)-"
          391                                  (if (listp library)
          392                                      (first library)
          393                                      (file-namestring library)))))
          394               (lib (make-instance 'foreign-library
          395                                   :type :system
          396                                   :name lib-name
          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))))))
          403 
          404 (defun filter-pathname (thing)
          405   (typecase thing
          406     (pathname (namestring thing))
          407     (t        thing)))
          408 
          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)))
          415     (restart-case
          416         (progn
          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
          422           ;; errors.
          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.
          428       (retry ()
          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)))))
          435 
          436 (defmacro use-foreign-library (name)
          437   `(load-foreign-library ',name))
          438 
          439 ;;;# Closing Foreign Libraries
          440 
          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)))
          446     (when handle
          447       (%close-foreign-library handle)
          448       (setf (foreign-library-handle lib) nil)
          449       t)))
          450 
          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)))
          454     (loop for l in libs
          455           for name = (foreign-library-name l)
          456           when (funcall test name)
          457             do (load-foreign-library name))
          458     libs))