[Sorry; I accidentally sent the last email before finishing it (C-c C-c
instead of C-c C-e).]

The problem:

Using the excellent cffi-wrappers on platforms that are not Allegro does
not work well with delivery, because the path to the generated shared
object (presumably near the source code) is hardcoded into the image,
and the image will crash very early, before entering user code if this
shared object is not available.


The solution:

Close all shared libraries before delivery. After delivery reopen them,
searching *foreign-library-directories*.


Brief summary of the patch:

(1) The wrapper generation code is changed to use define-foreign-library
and not to explicitly wedge the path into the library name, but to put
the directory where it is generated into *foreign-library-directories*.

(2) cffi:close-all-foreign-libraries closes all open defined foreign
libraries. Call this before delivery.

(3) (cffi:do-reload-all-compile-time-open-libraries) is a macro that
will load all the required libraries again. Put this in your main
function.


What I want:

Feedback and comments; maybe this patch touches too much? Apparently
Stelian started doing something a little similar but half finished? If
it looks viable for the mainline, we can add test cases and
documentation.



Examples:

Here are a couple of example build procedures that add the application's
binary directory to *foreign-library-directories*, so that the .so files
can be put there. (Maybe it would be better to fully set
*foreign-library-directories*.)

I have tested it on AMD64 Linux Lispworks and SBCL.

For example, Lispworks:

(compile 
 (defun main ()
   (setf *debugger-hook* 'mtsnmp::mtsnmp-debugger-hook)
   (pushnew (make-pathname :name nil :type nil :version nil 
                           :defaults (truename (first 
sys:*line-arguments-list*)))
            cffi:*foreign-library-directories* :test 'equalp)
   (handler-case
       (progn
         (cffi:do-reload-all-compile-time-open-libraries)
         (prog1 (apply 'my-application:main sys:*line-arguments-list*)
           (finish-output)))
     (condition (c)
       (ignore-errors
         (format *error-output* "Error: ~A~&" c)
         (finish-output *error-output*))
       (lispworks:quit :status 1)))
   (lispworks:quit :status 0)))

(cffi:close-all-foreign-libraries)

(deliver 'main
 (multiple-value-bind 
       (second minute hour date month year day daylight-p zone)
     (get-decoded-time)
   (format nil "binaries/~4,'0D~2,'0D~2,'0D-~2,'0D~2,'0D-~A-~A-~A" 
           year
           month
           date
           hour
           minute
           (lisp-implementation-type) 
           (lisp-implementation-version) (expt 2 (ceiling (log (log 
most-positive-fixnum 2) 2)))))
   1
   :multiprocessing t
   :keep-eval t
   :keep-conditions :all
   :keep-top-level nil
   :keep-debug-mode nil)




For example, SBCL

(in-package #:cl-user)

(defun main ()
  (unwind-protect
       (handler-case
           (progn
             (pushnew (make-pathname :name nil :type nil :version nil 
                                     :defaults (truename (first *posix-argv*)))
                      cffi:*foreign-library-directories* :test 'equalp)
             (cffi:do-reload-all-compile-time-open-libraries)
             (apply 'my-application:main *posix-argv*)
             (sb-ext:quit :unix-status 0))
         (condition (c)
           (ignore-errors
             (format *error-output* "Error: ~A~&" c))
           (sb-ext:quit :unix-status 1)))
    (sb-ext:quit :unix-status 100)))

(defun make-image ()
  (sb-ext:disable-debugger)

  (cffi:close-all-foreign-libraries)
  (sb-ext:save-lisp-and-die 
   (multiple-value-bind 
         (second minute hour date month year)
       (get-decoded-time)
     (declare (ignore second))
     (format nil "binaries/~4,'0D~2,'0D~2,'0D-~2,'0D~2,'0D-~A-~A-~A" 
             year
             month
             date
             hour
             minute
             (lisp-implementation-type) 
             (lisp-implementation-version) (expt 2 (ceiling (log (log 
most-positive-fixnum 2) 2)))))
   :executable t
   :toplevel 'main
   :purify t))

(make-image)


diff -rN -u old-cffi/grovel/asdf.lisp new-cffi/grovel/asdf.lisp
--- old-cffi/grovel/asdf.lisp	2009-07-17 16:41:06.000000000 +0900
+++ new-cffi/grovel/asdf.lisp	2009-07-17 16:41:06.000000000 +0900
@@ -30,11 +30,21 @@
 
 (in-package #:cffi-grovel)
 
+(defvar *wrapper-shared-library-pathname-defaults* nil)
+
 (defun ensure-pathname (thing)
   (if (typep thing 'logical-pathname)
       (translate-logical-pathname thing)
       (pathname thing)))
 
+(defun add-directory-for-generated-library-to-cffi-library-path (component
+                                                                 &optional (compile-op
+                                                                            (make-instance 'asdf:compile-op )))
+  (let ((output-defaults (or *wrapper-shared-library-pathname-defaults* 
+                             (ensure-pathname (car (asdf:output-files compile-op component))))))
+    (pushnew (make-pathname :name nil :type nil :version nil
+                            :defaults output-defaults) cffi::*foreign-library-directories* :test 'equalp)))
+
 (defclass cc-flags-mixin ()
   ((cc-flags :initform nil :accessor cc-flags-of :initarg :cc-flags)))
 
@@ -59,9 +69,10 @@
                   :output-file output-file)))
 
 (defmethod asdf:perform ((op asdf:load-source-op) (c grovel-file))
-  (load (process-grovel-file
-         (asdf:component-pathname c)
-         (ensure-pathname (car (asdf:output-files op c))))))
+  (let ((output-file (ensure-pathname (car (asdf:output-files op c)))))
+    (load (process-grovel-file
+           (asdf:component-pathname c)
+           output-file))))
 
 ;;;# ASDF component: WRAPPER-FILE
 
@@ -74,11 +85,18 @@
 that are subsequently compiled and/or loaded."))
 
 (defmethod asdf:perform ((op asdf:compile-op) (c wrapper-file))
+  (add-directory-for-generated-library-to-cffi-library-path c)
   (let ((output-file (ensure-pathname (car (asdf:output-files op c)))))
-    (compile-file (process-wrapper-file (asdf:component-pathname c) output-file)
+    (compile-file (process-wrapper-file (asdf:component-pathname c) 
+                                        (or *wrapper-shared-library-pathname-defaults* output-file))
                   :output-file output-file)))
 
 (defmethod asdf:perform ((op asdf:load-source-op) (c wrapper-file))
-  (load (process-wrapper-file
-         (asdf:component-pathname c)
-         (ensure-pathname (car (asdf:output-files op c))))))
+  (add-directory-for-generated-library-to-cffi-library-path c)
+  (let ((output-file (ensure-pathname (car (asdf:output-files op c)))))
+    (load (process-wrapper-file
+           (asdf:component-pathname c)
+           output-file))))
+
+(defmethod asdf:perform :before ((op asdf:load-op) (c wrapper-file))
+  (add-directory-for-generated-library-to-cffi-library-path c))
diff -rN -u old-cffi/grovel/grovel.lisp new-cffi/grovel/grovel.lisp
--- old-cffi/grovel/grovel.lisp	2009-07-17 16:41:06.000000000 +0900
+++ new-cffi/grovel/grovel.lisp	2009-07-17 16:41:06.000000000 +0900
@@ -652,15 +652,25 @@
     (values c-file (nreverse *lisp-forms*))))
 
 (defun generate-bindings-file (lib-file lisp-forms output-defaults)
-  (let ((lisp-file (tmp-lisp-filename output-defaults)))
-    (with-open-file (out lisp-file :direction :output :if-exists :supersede)
-      (format out ";;;; This file was automatically generated by cffi-grovel.~%~
+  (let ((lisp-file (tmp-lisp-filename output-defaults))
+        (library-name (alexandria:symbolicate 'cffi-wrapper-lib- (pathname-name lib-file))))
+    (with-standard-io-syntax
+      (with-open-file (out lisp-file :direction :output :if-exists :supersede)
+        (format out ";;;; This file was automatically generated by cffi-grovel.~%~
                    ;;;; Do not edit by hand.~%")
-      (let ((*package* (find-package '#:cl)))
-        (format out "~%~S~%" `(cffi:load-foreign-library ,lib-file)))
-      (dolist (form lisp-forms)
-        (print form out))
-      (terpri out))
+        (format out "~%~{~S~%~}~%" 
+                `((cffi:define-foreign-library ,library-name
+                    (t (:or 
+                            ,(format nil "~A" (make-pathname 
+                                      :type (pathname-type lib-file)
+                                      :name (pathname-name lib-file)))
+
+                            ,(pathname-name lib-file)
+                            )))
+                  (cffi:load-foreign-library ',library-name)))
+        (dolist (form lisp-forms)
+          (print form out))
+        (terpri out)))
     lisp-file))
 
 (defun lib-filename (defaults)
@@ -669,7 +679,7 @@
 
 ;;; *PACKAGE* is rebound so that the IN-PACKAGE form can set it during
 ;;; *the extent of a given wrapper file.
-(defun process-wrapper-file (input-file &optional (output-defaults input-file))
+(defun process-wrapper-file (input-file output-defaults)
   (with-standard-io-syntax
     (let ((lib-file (lib-filename output-defaults)))
       (multiple-value-bind (c-file lisp-forms)
diff -rN -u old-cffi/src/cffi-sbcl.lisp new-cffi/src/cffi-sbcl.lisp
--- old-cffi/src/cffi-sbcl.lisp	2009-07-17 16:41:06.000000000 +0900
+++ new-cffi/src/cffi-sbcl.lisp	2009-07-17 16:41:06.000000000 +0900
@@ -324,7 +324,7 @@
 (defun %load-foreign-library (name path)
   "Load a foreign library."
   (declare (ignore name))
-  (load-shared-object path))
+  (load-shared-object path :dont-save t))
 
 ;;; SBCL 1.0.21.15 renamed SB-ALIEN::SHARED-OBJECT-FILE but introduced
 ;;; SB-ALIEN:UNLOAD-SHARED-OBJECT which we can use instead.
diff -rN -u old-cffi/src/libraries.lisp new-cffi/src/libraries.lisp
--- old-cffi/src/libraries.lisp	2009-07-17 16:41:06.000000000 +0900
+++ new-cffi/src/libraries.lisp	2009-07-17 16:41:06.000000000 +0900
@@ -104,7 +104,7 @@
 (defclass foreign-library ()
   ((spec :initarg :spec)
    (options :initform nil :initarg :options)
-   (handle :initarg :handle :accessor foreign-library-handle)))
+   (handle :initarg ::handle :accessor foreign-library-handle)))
 
 (defun get-foreign-library (lib)
   "Look up a library by NAME, signalling an error if not found."
@@ -113,6 +113,10 @@
       (or (gethash lib *foreign-libraries*)
           (error "Undefined foreign library: ~S" lib))))
 
+(defun foreign-library-loaded-p (lib)
+  (let ((lib (get-foreign-library lib)))
+    (and (slot-boundp lib 'handle) (foreign-library-handle lib))))
+
 (defun (setf get-foreign-library) (value name)
   (setf (gethash name *foreign-libraries*) value))
 
@@ -211,7 +215,7 @@
       (return-from try-foreign-library-alternatives handle)))
   ;; Perhaps we should show the error messages we got for each
   ;; alternative if we can figure out a nice way to do that.
-  (fl-error "Unable to load any of the alternatives:~%   ~S" library-list))
+  (fl-error "Unable to load the ~A foreign library; the alternatives tried were:~%   ~S" name library-list))
 
 (defparameter *cffi-feature-suffix-map*
   '((:windows . ".dll")
@@ -254,12 +258,16 @@
          (let* ((lib (get-foreign-library library))
                 (spec (foreign-library-spec lib)))
            (when spec
-             (setf (foreign-library-handle lib)
-                   (load-foreign-library-helper library spec))
+             (unless (foreign-library-loaded-p lib) 
+               (setf (foreign-library-handle lib)
+                     (load-foreign-library-helper library spec)))
+             (assert (foreign-library-loaded-p lib))
              lib)))
         (t
-         (make-instance 'foreign-library :spec (list (list library))
-                        :handle (load-foreign-library-helper nil library))))
+         (let ((lib (make-instance 'foreign-library :spec (list (list library))
+                                   :handle (load-foreign-library-helper nil library))))
+           (assert (foreign-library-loaded-p lib))
+           lib)))
     ;; Offer these restarts that will retry the call to
     ;; LOAD-FOREIGN-LIBRARY.
     (retry ()
@@ -282,3 +290,39 @@
       (%close-foreign-library (foreign-library-handle lib))
       (setf (foreign-library-handle lib) nil)
       t)))
+
+(defun list-open-library-names ()
+  (loop for name being the hash-keys of *foreign-libraries* using (hash-value lib)
+        when (foreign-library-loaded-p lib)
+        collect name))
+
+(defun load-foreign-libraries (list)
+  ;; we have no way of checking the dependencies between libraries
+  ;; so load all of them in the list, and if at least one succeeded, keep going
+  (labels ((load-at-least-one (list)
+           (let ((list (remove-if 'foreign-library-loaded-p list)))
+             (when list
+               (let (one-success)
+                 (loop for lib in list do
+
+                       ;; muffle warnings to avoid printing messages
+                       ;; about undefined symbols etc. in the trial
+                       ;; phase
+                       (handler-bind ((warning #'muffle-warning))
+                         (ignore-errors (load-foreign-library lib) 
+                                        (setf one-success t))))
+
+                 (unless one-success
+                   ;; this will probably raise the error again, but if not then fine, we have loaded it
+                   (loop for lib in list do
+                         (load-foreign-library lib))))
+
+               (load-at-least-one list)))))
+    (load-at-least-one list)))
+
+(defun close-all-foreign-libraries ()
+  (mapcar 'close-foreign-library (list-open-library-names)))
+
+(defmacro do-reload-all-compile-time-open-libraries ()
+  `(load-foreign-libraries ',(list-open-library-names)))
+
diff -rN -u old-cffi/src/package.lisp new-cffi/src/package.lisp
--- old-cffi/src/package.lisp	2009-07-17 16:41:06.000000000 +0900
+++ new-cffi/src/package.lisp	2009-07-17 16:41:06.000000000 +0900
@@ -79,6 +79,9 @@
    #:load-foreign-library-error
    #:use-foreign-library
    #:close-foreign-library
+   #:do-reload-all-compile-time-open-libraries
+   #:close-all-foreign-libraries
+   #:list-open-library-names
 
    ;; Callbacks.
    #:callback

_______________________________________________
cffi-devel mailing list
cffi-devel@common-lisp.net
http://common-lisp.net/cgi-bin/mailman/listinfo/cffi-devel

Reply via email to