c-toolchain.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
       ---
       c-toolchain.lisp (16281B)
       ---
            1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
            2 ;;;
            3 ;;; c-toolchain.lisp --- Generic support compiling and linking C code.
            4 ;;;
            5 ;;; Copyright (C) 2005-2006, Dan Knap <dankna@accela.net>
            6 ;;; Copyright (C) 2005-2006, Emily Backes <lucca@accela.net>
            7 ;;; Copyright (C) 2007, Stelian Ionescu <sionescu@cddr.org>
            8 ;;; Copyright (C) 2007, Luis Oliveira <loliveira@common-lisp.net>
            9 ;;;
           10 ;;; Permission is hereby granted, free of charge, to any person
           11 ;;; obtaining a copy of this software and associated documentation
           12 ;;; files (the "Software"), to deal in the Software without
           13 ;;; restriction, including without limitation the rights to use, copy,
           14 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies
           15 ;;; of the Software, and to permit persons to whom the Software is
           16 ;;; furnished to do so, subject to the following conditions:
           17 ;;;
           18 ;;; The above copyright notice and this permission notice shall be
           19 ;;; included in all copies or substantial portions of the Software.
           20 ;;;
           21 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           22 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           23 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           24 ;;; NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
           25 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
           26 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
           27 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
           28 ;;; DEALINGS IN THE SOFTWARE.
           29 ;;;
           30 
           31 (in-package #:cffi-toolchain)
           32 
           33 ;;; Utils
           34 
           35 (defun parse-command-flags (flags)
           36   (let ((separators '(#\Space #\Tab #\Newline #\Return)))
           37     (remove-if 'emptyp (split-string flags :separator separators))))
           38 
           39 (defun parse-command-flags-list (strings)
           40   (loop for flags in strings append (parse-command-flags flags)))
           41 
           42 (defun program-argument (x)
           43   (etypecase x
           44     (string x)
           45     (pathname (native-namestring x))))
           46 
           47 (defun invoke (command &rest args)
           48   (when (pathnamep command)
           49     (setf command (native-namestring command))
           50     #+os-unix
           51     (unless (absolute-pathname-p command)
           52       (setf command (strcat "./" command))))
           53   (let ((cmd (cons command (mapcar 'program-argument args))))
           54     (safe-format! *debug-io* "; ~A~%" (escape-command cmd))
           55     (run-program cmd :output :interactive :error-output :interactive)))
           56 
           57 
           58 ;;; C support
           59 
           60 (defparameter *cc* nil "C compiler")
           61 (defparameter *cc-flags* nil "flags for the C compiler")
           62 (defparameter *ld* nil "object linker") ;; NB: can actually be the same as *cc*
           63 (defparameter *ld-exe-flags* nil "flags for linking executables via *ld*")
           64 (defparameter *ld-dll-flags* nil "flags for linking shared library via *ld*")
           65 (defparameter *linkkit-start* nil "flags for the implementation linkkit, start")
           66 (defparameter *linkkit-end* nil "flags for the implementation linkkit, end")
           67 
           68 (defun link-all-library (lib)
           69   ;; Flags to provide to cc to link a whole library into an executable
           70   (when lib
           71     (if (featurep :darwin) ;; actually, LLVM ld vs GNU ld
           72         `("-Wl,-force_load" ,lib)
           73         `("-Wl,--whole-archive" ,lib "-Wl,--no-whole-archive"))))
           74 
           75 (defun normalize-flags (directory flags)
           76   (loop for val in (parse-command-flags flags) collect
           77         (cond
           78           ((find (first-char val) "-+/") val)
           79           ((probe-file* (subpathname directory val)))
           80           (t val))))
           81 
           82 (defun implementation-file (file &optional type)
           83   (subpathname (lisp-implementation-directory) file
           84                :type (bundle-pathname-type type)))
           85 
           86 ;; TODO: on CCL, extract data from
           87 ;; (pathname (strcat "ccl:lisp-kernel/" (ccl::kernel-build-directory) "/Makefile")) ?
           88 
           89 #+clisp
           90 (progn
           91   (defparameter *clisp-toolchain-parameters*
           92     '(("CC" *cc*)
           93       ("CFLAGS" *cc-flags* t)
           94       ("CLFLAGS" *cc-exe-flags* t)
           95       ("LIBS" *linkkit-start* t)
           96       ("X_LIBS" *linkkit-end* t)))
           97   (defun clisp-toolchain-parameters (&optional linkset)
           98     (nest
           99      (let* ((linkset (ensure-pathname
          100                       (or linkset "base")
          101                       :defaults (lisp-implementation-directory)
          102                       :ensure-absolute t
          103                       :ensure-directory t
          104                       :want-existing t))
          105             (makevars (subpathname linkset "makevars"))))
          106      (with-input-file (params makevars :if-does-not-exist nil))
          107      (when params)
          108      (loop for l = (read-line params nil nil) while l
          109            finally (appendf *linkkit-start* (normalize-flags linkset "modules.o")) do)
          110      (if-let (p (position #\= l)))
          111      (let ((var (subseq l 0 p))
          112            ;; strip the start and end quote characters
          113            (val (subseq l (+ p 2) (- (length l) 1)))))
          114      (if-let (param (cdr (assoc var *clisp-toolchain-parameters* :test 'equal))))
          115      (destructuring-bind (sym &optional normalizep) param
          116        (setf (symbol-value sym)
          117              (if normalizep (normalize-flags linkset val) val))))
          118     (setf *ld* *cc*
          119           *ld-exe-flags* `(,@*cc-flags* #-darwin "-Wl,--export-dynamic")
          120           *ld-dll-flags* (list* #+darwin "-dynamiclib" ;; -bundle ?
          121                                 #-darwin "-shared"
          122                                 *cc-flags*))))
          123 
          124 ;; TODO: for CMUCL, see whatever uses its linker.sh,
          125 ;; and teach it to accept additional objects / libraries
          126 ;; as it links a runtime plus a core into an executable
          127 
          128 #+ecl
          129 (defun ecl-toolchain-parameters ()
          130   (setf *cc* c:*cc*
          131         *cc-flags* `(,@(parse-command-flags c::*cc-flags*)
          132                      ,@(parse-command-flags c:*user-cc-flags*))
          133         ;; For the below, we just use c::build-FOO
          134         *ld* *cc*
          135         *ld-exe-flags* *cc-flags*
          136         *ld-dll-flags* *cc-flags*
          137         *linkkit-start* nil
          138         *linkkit-end* nil))
          139 
          140 #+mkcl
          141 (defun mkcl-toolchain-parameters ()
          142   (setf *cc* compiler::*cc*
          143         *cc-flags* (parse-command-flags compiler::*cc-flags*)
          144         ;; For the below, we just use compiler::build-FOO
          145         *ld* *cc*
          146         *ld-exe-flags* *cc-flags*
          147         *ld-dll-flags* *cc-flags*
          148         *linkkit-start* nil
          149         *linkkit-end* nil))
          150 
          151 #+sbcl
          152 (progn
          153   (defparameter *sbcl-toolchain-parameters*
          154     '(("CC" *cc*)
          155       ("CFLAGS" *cc-flags* t)
          156       ("LINKFLAGS" *ld-exe-flags* t)
          157       ("USE_LIBSBCL" *linkkit-start* t)
          158       ("LIBS" *linkkit-end* t)))
          159   (defun sbcl-toolchain-parameters ()
          160     (nest
          161      (let* ((sbcl-home (lisp-implementation-directory))
          162             (sbcl.mk (subpathname sbcl-home "sbcl.mk"))))
          163      (with-input-file (params sbcl.mk :if-does-not-exist nil))
          164      (when params)
          165      (loop for l = (read-line params nil nil) while l
          166            finally (appendf *linkkit-end* '("-lm")) do)
          167      (if-let (p (position #\= l)))
          168      (let ((var (subseq l 0 p))
          169            (val (subseq l (1+ p)))))
          170      (if-let (param (cdr (assoc var *sbcl-toolchain-parameters* :test 'equal))))
          171      (destructuring-bind (sym &optional normalizep) param
          172        (setf (symbol-value sym)
          173              (if normalizep (normalize-flags sbcl-home val) val))))
          174     (unless (featurep :sb-linkable-runtime)
          175       (setf *linkkit-start* nil *linkkit-end* nil))
          176     (setf *ld* *cc* ;; !
          177           *ld-dll-flags* (list* #+darwin "-dynamiclib" #-darwin "-shared"
          178                                 *cc-flags*))))
          179 
          180 ;;; Taken from sb-grovel
          181 (defun split-cflags (string)
          182   (remove-if (lambda (flag)
          183                (zerop (length flag)))
          184              (loop
          185                for start = 0 then (if end (1+ end) nil)
          186                for end = (and start (position #\Space string :start start))
          187                while start
          188                collect (subseq string start end))))
          189 
          190 (defun default-toolchain-parameters ()
          191   ;; The values below are legacy guesses from previous versions of CFFI.
          192   ;; It would be nice to clean them up, remove unneeded guesses,
          193   ;; annotate every guess with some comment explaining the context.
          194   ;; TODO: have proper implementation-provided linkkit parameters
          195   ;; for all implementations as above, and delete the below altogether.
          196   (let ((arch-flags
          197          ;; Former *cpu-word-size-flags*
          198          #+arm '("-marm")
          199          #+arm64 '()
          200          #-(or arm arm64)
          201          (ecase (cffi:foreign-type-size :pointer)
          202                 (4 '("-m32"))
          203                 (8 '("-m64")))))
          204     (setf *cc*
          205           (or (getenvp "CC")
          206               #+(or cygwin (not windows)) "cc"
          207               "gcc")
          208           *cc-flags*
          209           (append
          210            arch-flags
          211            ;; For MacPorts
          212            #+darwin (list "-I" "/opt/local/include/")
          213            ;; ECL internal flags
          214            #+ecl (parse-command-flags c::*cc-flags*)
          215            ;; FreeBSD non-base header files
          216            #+freebsd (list "-I" "/usr/local/include/")
          217            (split-cflags (getenv "CFLAGS")))
          218           *ld* *cc*
          219           *ld-exe-flags* `(,@arch-flags #-darwin "-Wl,--export-dynamic")
          220           *ld-dll-flags* (list* #+darwin "-dynamiclib" ;; -bundle ?
          221                                 #-darwin "-shared"
          222                                 *cc-flags*)
          223           *linkkit-start* nil
          224           *linkkit-end* nil)))
          225 
          226 (defun ensure-toolchain-parameters ()
          227   #+clisp (unless *cc* (clisp-toolchain-parameters))
          228   #+ecl (unless *cc* (ecl-toolchain-parameters))
          229   #+mkcl (unless *cc* (mkcl-toolchain-parameters))
          230   #+sbcl (unless *cc* (sbcl-toolchain-parameters))
          231   (unless *cc* (default-toolchain-parameters)))
          232 
          233 ;; Actually initialize toolchain parameters
          234 (ignore-errors (ensure-toolchain-parameters))
          235 
          236 
          237 (defun call-with-temporary-output (output-file fun)
          238   (let ((output-file (ensure-pathname output-file :want-file t :ensure-absolute t :truenamize t)))
          239     (with-temporary-file
          240         (:pathname tmp :direction :output
          241          :prefix (strcat (native-namestring (pathname-directory-pathname output-file))
          242                          (pathname-name output-file) "-tmp")
          243          :suffix ""
          244          :type (pathname-type output-file))
          245       (funcall fun tmp)
          246       (rename-file-overwriting-target tmp output-file))))
          247 
          248 (defmacro with-temporary-output ((output-file-var &optional (output-file-val output-file-var))
          249                                  &body body)
          250   "Create an output file atomically, by executing the BODY while OUTPUT-FILE-VAR
          251 is bound to a temporary file name, then atomically renaming that temporary file to OUTPUT-FILE-VAL."
          252   `(call-with-temporary-output ,output-file-val (lambda (,output-file-var) ,@body)))
          253 
          254 (defun invoke-builder (builder output-file &rest args)
          255   "Invoke the C Compiler with given OUTPUT-FILE and arguments ARGS"
          256   (with-temporary-output (output-file)
          257     (apply 'invoke `(,@builder ,output-file ,@args))))
          258 
          259 (defun cc-compile (output-file inputs)
          260   (apply 'invoke-builder (list *cc* "-o") output-file
          261          "-c" (append *cc-flags* #-windows '("-fPIC") inputs)))
          262 
          263 (defun link-executable (output-file inputs)
          264   (apply 'invoke-builder (list *ld* "-o") output-file
          265          (append *ld-exe-flags* inputs)))
          266 
          267 (defun link-lisp-executable (output-file inputs)
          268   #+ecl
          269   (let ((c::*ld-flags*
          270          (format nil "-Wl,--export-dynamic ~@[ ~A~]"
          271                  c::*ld-flags*)))
          272     (c::build-program output-file :lisp-files inputs))
          273   #+mkcl (compiler::build-program
          274           output-file :lisp-object-files (mapcar 'program-argument inputs)
          275           :on-missing-lisp-object-initializer nil)
          276   #+(and sbcl (not sb-linkable-runtime)) (error "Your SBCL doesn't support :SB-LINKABLE-RUNTIME")
          277   #-(or ecl mkcl)
          278   (link-executable output-file `(,@*linkkit-start* ,@inputs ,@*linkkit-end*)))
          279 
          280 (defun link-static-library (output-file inputs)
          281   #+ecl (c::build-static-library output-file :lisp-files inputs)
          282   #+mkcl (compiler::build-static-library
          283           output-file :lisp-object-files (mapcar 'program-argument inputs)
          284           :on-missing-lisp-object-initializer nil)
          285   #-(or ecl mkcl)
          286   (with-temporary-output (output-file)
          287     (delete-file-if-exists output-file)
          288     #+(or bsd linux windows)
          289     (apply 'invoke
          290            `(;; TODO: make it portable to BSD.
          291              ;; ar D is also on FreeBSD, but not on OpenBSD or Darwin, dunno about NetBSD;
          292              ;; ar T seems to only be on Linux (means something different on Darwin). Sigh.
          293              ;; A MRI script might be more portable... not, only supported by GNU binutils.
          294              ;; I couldn't get libtool to work, and it's not ubiquitous anyway.
          295              ;; ,@`("libtool" "--mode=link" ,*cc* ,@*cc-flags* "-static" "-o" ,output-file)
          296              ;; "Solution": never link .a's into further .a's, only link .o's into .a's,
          297              ;; which implied changes that are now the case in ASDF 3.2.0.
          298              #+darwin ,@`("libtool" "-static" "-o" ,output-file)
          299              #+(:and bsd (:not darwin)) ,@`("ar" "rcs" ,output-file)
          300              #+linux ,@`("ar" "rcsDT" ,output-file)
          301              #+windows ,@`("lib" "-nologo" ,(strcat "-out:" (native-namestring output-file)))
          302              ,@inputs))
          303     #-(or bsd linux windows)
          304     (error "Not implemented on your system")))
          305 
          306 (defun link-shared-library (output-file inputs)
          307   ;; remove the library so we won't possibly be overwriting
          308   ;; the code of any existing process
          309   (delete-file-if-exists output-file)
          310   #+ecl (c::build-shared-library output-file :lisp-files inputs)
          311   #+mkcl (compiler::build-shared-library
          312           output-file :lisp-object-files (mapcar 'program-argument inputs)
          313           :on-missing-lisp-object-initializer nil)
          314   #-(or ecl mkcl)
          315   ;; Don't use a temporary file, because linking is sensitive to the output file name :-/ (or put it in a temporary directory?)
          316   (apply 'invoke *ld* "-o" output-file
          317          (append *ld-dll-flags* inputs)))
          318 
          319 
          320 ;;; Computing file names
          321 
          322 (defun make-c-file-name (output-defaults &optional suffix)
          323   (make-pathname :type "c"
          324                  :name (strcat (pathname-name output-defaults) suffix)
          325                  :defaults output-defaults))
          326 
          327 (defun make-o-file-name (output-defaults &optional suffix)
          328   (make-pathname :type (bundle-pathname-type :object)
          329                  :name (format nil "~A~@[~A~]" (pathname-name output-defaults) suffix)
          330                  :defaults output-defaults))
          331 
          332 (defun make-so-file-name (defaults)
          333   (make-pathname :type (bundle-pathname-type :shared-library)
          334                  :defaults defaults))
          335 
          336 (defun make-exe-file-name (defaults)
          337   (make-pathname :type (bundle-pathname-type :program)
          338                  :defaults defaults))
          339 
          340 
          341 ;;; Implement link-op on image-based platforms.
          342 #-(or clasp ecl mkcl)
          343 (defmethod perform ((o link-op) (c system))
          344   (let* ((inputs (input-files o c))
          345          (output (first (output-files o c)))
          346          (kind (bundle-type o)))
          347     (when output ;; some operations skip any output when there is no input
          348       (ecase kind
          349         (:program (link-executable output inputs))
          350         ((:lib :static-library) (link-static-library output inputs))
          351         ((:dll :shared-library) (link-shared-library output inputs))))))
          352 
          353 (defclass c-file (source-file)
          354   ((cflags :initarg :cflags :initform :default)
          355    (type :initform "c")))
          356 
          357 (defmethod output-files ((o compile-op) (c c-file))
          358   (let* ((i (first (input-files o c)))
          359          (base (format nil "~(~{~a~^__~}~)"
          360                        (mapcar (lambda (x) (substitute-if #\_ (complement #'alphanumericp) x))
          361                                (component-find-path c))))
          362          (path (make-pathname :defaults i :name base)))
          363     (list (make-o-file-name path)
          364           (make-so-file-name path))))
          365 
          366 (defmethod perform ((o compile-op) (c c-file))
          367   (let ((i (first (input-files o c))))
          368     (destructuring-bind (.o .so) (output-files o c)
          369       (cc-compile .o (list i))
          370       (link-shared-library .so (list .o)))))
          371 
          372 (defmethod perform ((o load-op) (c c-file))
          373   (let ((o (second (input-files o c))))
          374     (cffi:load-foreign-library (file-namestring o) :search-path (list (pathname-directory-pathname o)))))
          375 
          376 (setf (find-class 'asdf::c-file) (find-class 'c-file))
          377 
          378 (defclass o-file (source-file)
          379   ((cflags :initarg :cflags :initform :default)
          380    (type :initform (bundle-pathname-type :object)))
          381   (:documentation "class for pre-compile object components"))
          382 
          383 (defmethod output-files ((op compile-op) (c o-file))
          384   (let* ((o (first (input-files op c)))
          385          (so (apply-output-translations (make-so-file-name o))))
          386     (values (list o so) t)))
          387 
          388 (defmethod perform ((o load-op) (c o-file))
          389   (let ((so (second (input-files o c))))
          390     (cffi:load-foreign-library (file-namestring so) :search-path (list (pathname-directory-pathname so)))))
          391 
          392 (setf (find-class 'asdf::o-file) (find-class 'o-file))
          393