tlibraries.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://hg6vgqziawt5s4dj.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) 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  <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"/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-pathname))
           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 finds."
           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-handle)
          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-convention))
          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 file))
          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 search-path)
          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-list))
          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 OS.")))
          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-path))))))
          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 defined
          410 through DEFINE-FOREIGN-LIBRARY; a pathname or string in which case we try to
          411 load it directly first then search for it in *FOREIGN-LIBRARY-DIRECTORIES*;
          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))