1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
3 ;;; c2ffi.lisp --- c2ffi related code
5 ;;; Copyright (C) 2013, Ryan Pavlik <rpavlik@gmail.com>
6 ;;; Copyright (C) 2015, Attila Lendvai <attila@lendvai.name>
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:
16 ;;; The above copyright notice and this permission notice shall be
17 ;;; included in all copies or substantial portions of the Software.
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.
29 (in-package #:cffi
/c2ffi
)
31 ;;; NOTE: Most of this has been taken over from cl-autowrap.
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.
38 #+(and (not (or x86-64 freebsd
)) x86
) "i686"
39 #+(and (not x86-64
) x86 freebsd
) "i386"
42 (defun local-vendor ()
43 #+(or linux windows
) "-pc"
45 #+(not (or linux windows darwin
)) "-unknown")
49 #+windows
"-windows-msvc"
53 (defun local-environment ()
58 (strcat (local-cpu) (local-vendor) (local-os) (local-environment)))
60 (defparameter *known-archs
*
63 "i686-pc-windows-msvc"
64 "x86_64-pc-windows-msvc"
66 "x86_64-apple-darwin9"
67 "i386-unknown-freebsd"
68 "x86_64-unknown-freebsd"))
70 (defvar *c2ffi-executable
* "c2ffi")
72 (defvar *trace-c2ffi
* nil
)
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
))))
80 (defun run-program* (program args
&key
(output (if *trace-c2ffi
* *standard-output
* nil
))
81 (error-output (if *trace-c2ffi
* *error-output
* nil
))
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
))))
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
95 nil
; workaround for an UIOP bug; delme eventually (attila, 2016-01-27).
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
)
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
120 (format tmp-include-file-stream
"#include \"~A\"~%" input-header-file
)
121 (format tmp-include-file-stream
"#include \"~A\"~%" tmp-macro-file
)
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
))))))
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
)
141 (defun find-local-spec (base-name &optional
(errorp t
))
142 (let* ((spec-path (spec-path base-name
))
143 (probed (probe-file spec-path
)))
147 (error "c2ffi spec file not found for base name ~S" base-name
)))))
149 (defun ensure-spec-file-exists (header-file-path &key exclude-archs sys-include-paths version
)
152 (find-local-spec header-file-path nil
)
154 (values h-name m-name
)
155 (let ((local-arch (local-arch)))
156 (unless (c2ffi-executable-available?
)
157 (error "No spec found for ~S on arch '~A' and c2ffi not found"
158 header-file-path local-arch
))
159 (generate-spec-with-c2ffi header-file-path
160 (spec-path header-file-path
164 :sys-include-paths sys-include-paths
)
165 ;; also run c2ffi for other architectures, but tolerate failure
166 (dolist (arch *known-archs
*)
167 (unless (or (string= local-arch arch
)
168 (member arch exclude-archs
:test
#'string
=))
169 (unless (generate-spec-with-c2ffi header-file-path
170 (spec-path header-file-path
174 :sys-include-paths sys-include-paths
175 :ignore-error-status t
)
176 (warn "Failed to generate spec for other arch: ~S" arch
))))
177 (find-local-spec header-file-path
)))))