c2ffi.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
       ---
       c2ffi.lisp (9422B)
       ---
            1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
            2 ;;;
            3 ;;; c2ffi.lisp --- c2ffi related code
            4 ;;;
            5 ;;; Copyright (C) 2013, Ryan Pavlik <rpavlik@gmail.com>
            6 ;;; Copyright (C) 2015, Attila Lendvai <attila@lendvai.name>
            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/c2ffi)
           30 
           31 ;;; NOTE: Most of this has been taken over from cl-autowrap.
           32 
           33 ;;; Note this is rather untested and not very extensive at the moment;
           34 ;;; it should probably work on linux/win/osx though. Patches welcome.
           35 
           36 (defun local-cpu ()
           37   #+x86-64 "x86_64"
           38   #+(and (not (or x86-64 freebsd)) x86) "i686"
           39   #+(and (not x86-64) x86 freebsd) "i386"
           40   #+arm "arm")
           41 
           42 (defun local-vendor ()
           43   #+(or linux windows) "-pc"
           44   #+darwin "-apple"
           45   #+(not (or linux windows darwin)) "-unknown")
           46 
           47 (defun local-os ()
           48   #+linux "-linux"
           49   #+windows "-windows-msvc"
           50   #+darwin "-darwin9"
           51   #+freebsd "-freebsd")
           52 
           53 (defun local-environment ()
           54   #+linux "-gnu"
           55   #-linux "")
           56 
           57 (defun local-arch ()
           58   (strcat (local-cpu) (local-vendor) (local-os) (local-environment)))
           59 
           60 (defparameter *known-archs*
           61   '("i686-pc-linux-gnu"
           62     "x86_64-pc-linux-gnu"
           63     "i686-pc-windows-msvc"
           64     "x86_64-pc-windows-msvc"
           65     "i686-apple-darwin9"
           66     "x86_64-apple-darwin9"
           67     "i386-unknown-freebsd"
           68     "x86_64-unknown-freebsd"))
           69 
           70 (defvar *c2ffi-executable* "c2ffi")
           71 
           72 (defvar *trace-c2ffi* nil)
           73 
           74 (defun c2ffi-executable-available? ()
           75   ;; This is a hack to determine if c2ffi exists; it assumes if it
           76   ;; doesn't exist, we will get a return code other than 0.
           77   (zerop (nth-value 2 (uiop:run-program `(,*c2ffi-executable* "-h")
           78                                         :ignore-error-status t))))
           79 
           80 (defun run-program* (program args &key (output (if *trace-c2ffi* *standard-output* nil))
           81                                     (error-output (if *trace-c2ffi* *error-output* nil))
           82                                     ignore-error-status)
           83   (when *trace-c2ffi*
           84     (format *debug-io* "~&; Invoking: ~A~{ ~A~}~%" program args))
           85   (zerop (nth-value 2 (uiop:run-program (list* program args) :output output
           86                                         :error-output error-output
           87                                         :ignore-error-status ignore-error-status))))
           88 
           89 (defun generate-spec-with-c2ffi (input-header-file output-spec-path
           90                                  &key arch sys-include-paths ignore-error-status)
           91   "Run c2ffi on `INPUT-HEADER-FILE`, outputting to `OUTPUT-FILE` and
           92 `MACRO-OUTPUT-FILE`, optionally specifying a target triple `ARCH`."
           93   (uiop:with-temporary-file (:pathname tmp-macro-file
           94                              :keep *trace-c2ffi*)
           95     nil ; workaround for an UIOP bug; delme eventually (attila, 2016-01-27).
           96     :close-stream
           97     (let* ((arch (when arch (list "--arch" arch)))
           98            (sys-include-paths (loop
           99                                 :for dir :in sys-include-paths
          100                                 :append (list "--sys-include" dir))))
          101       ;; Invoke c2ffi to first emit C #define's into TMP-MACRO-FILE. We ask c2ffi
          102       ;; to first generate a file of C global variables that are assigned the
          103       ;; value of the corresponding #define's, so that in the second pass below
          104       ;; the C compiler evaluates for us their right hand side and thus we can
          105       ;; get hold of their value. This is a kludge and eventually we could/should
          106       ;; support generating cffi-grovel files, and in grovel mode not rely
          107       ;; on this kludge anymore.
          108       (when (run-program* *c2ffi-executable* (list* (namestring input-header-file)
          109                                                     "--driver" "null"
          110                                                     "--macro-file" (namestring tmp-macro-file)
          111                                                     (append arch sys-include-paths))
          112                           :output *standard-output*
          113                           :ignore-error-status ignore-error-status)
          114         ;; Write a tmp header file that #include's the original input file and
          115         ;; the above generated macros file which will form the input for our
          116         ;; final, second pass.
          117         (uiop:with-temporary-file (:stream tmp-include-file-stream
          118                                    :pathname tmp-include-file
          119                                    :keep *trace-c2ffi*)
          120           (format tmp-include-file-stream "#include \"~A\"~%" input-header-file)
          121           (format tmp-include-file-stream "#include \"~A\"~%" tmp-macro-file)
          122           :close-stream
          123           ;; Invoke c2ffi again to generate the final output.
          124           (run-program* *c2ffi-executable* (list* (namestring tmp-include-file)
          125                                                   "--output" (namestring output-spec-path)
          126                                                   (append arch sys-include-paths))
          127                         :output *standard-output*
          128                         :ignore-error-status ignore-error-status))))))
          129 
          130 (defun spec-path (base-name &key version (arch (local-arch)))
          131   (check-type base-name pathname)
          132   (make-pathname :defaults base-name
          133                  :name (strcat (pathname-name base-name)
          134                                (if version
          135                                    (strcat "-" version)
          136                                    "")
          137                                "."
          138                                arch)
          139                  :type "spec"))
          140 
          141 (defun find-local-spec (base-name &optional (errorp t))
          142   (let* ((spec-path (spec-path base-name))
          143          (probed (probe-file spec-path)))
          144     (if probed
          145         spec-path
          146         (when errorp
          147           (error "c2ffi spec file not found for base name ~S" base-name)))))
          148 
          149 (defun ensure-spec-file-is-up-to-date (header-file-path
          150                                        &key exclude-archs sys-include-paths version)
          151   (let ((spec-path (find-local-spec header-file-path nil)))
          152     (flet ((regenerate-spec-file ()
          153              (let ((local-arch (local-arch)))
          154                (unless (c2ffi-executable-available?)
          155                  (error "No spec found for ~S on arch '~A' and c2ffi not found"
          156                         header-file-path local-arch))
          157                (generate-spec-with-c2ffi header-file-path
          158                                          (spec-path header-file-path
          159                                                     :arch local-arch
          160                                                     :version version)
          161                                          :arch local-arch
          162                                          :sys-include-paths sys-include-paths)
          163                ;; Try to run c2ffi for other architectures, but tolerate failure
          164                (dolist (arch *known-archs*)
          165                  (unless (or (string= local-arch arch)
          166                              (member arch exclude-archs :test #'string=))
          167                    (unless (generate-spec-with-c2ffi header-file-path
          168                                                      (spec-path header-file-path
          169                                                                 :arch arch
          170                                                                 :version version)
          171                                                      :arch arch
          172                                                      :sys-include-paths sys-include-paths
          173                                                      :ignore-error-status t)
          174                      (warn "Failed to generate spec for other arch: ~S" arch))))
          175                (find-local-spec header-file-path))))
          176       (if (and spec-path
          177                (uiop:timestamp< (file-write-date header-file-path)
          178                                 (file-write-date spec-path)))
          179           spec-path            ; it's up to date, just return it as is
          180           (restart-case
          181               (regenerate-spec-file)
          182             (touch-old-copy ()
          183               :report (lambda (stream)
          184                         (format stream "Update the modification time of the out-of-date copy ~S" spec-path))
          185               ;; Make it only be visible when the spec file exists (but it's out of date)
          186               :test (lambda (condition)
          187                       (declare (ignore condition))
          188                       (not (null spec-path)))
          189               ;; Update the last modification time. Yes, it's convoluted and wasteful,
          190               ;; but I can't see any other way.
          191               (with-staging-pathname (tmp-file spec-path)
          192                 (copy-file spec-path tmp-file))
          193               ;; The return value of RESTART-CASE
          194               spec-path))))))