;;;; Objective-CL, an Objective-C bridge for Common Lisp.
;;;; Copyright (C) 2007 Matthias Andreas Benkard.
;;;;
;;;; This program is free software: you can redistribute it and/or
;;;; modify it under the terms of the GNU General Public License as
;;;; published by the Free Software Foundation, either version 3 of the
;;;; License, or (at your option) any later version.
;;;;
;;;; This program is distributed in the hope that it will be useful, but
;;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;;;; General Public License for more details.
;;;;
;;;; You should have received a copy of the GNU General Public License
;;;; along with this program. If not, see
;;;; .
(defpackage objcl-asdf
(:use #:cl #:asdf)
(:export #:objc-source-file
#:*objc-obj-dir*))
(in-package #:objcl-asdf)
(defvar *objc-obj-dir*)
(defvar *stuff-copied-p* nil)
(defclass objc-source-file (source-file) ())
(defclass objcl-c-source-file (objc-source-file) ()
(:documentation "An Objective-CL C source file"))
(defmethod source-file-type ((c objc-source-file) (s module)) "m")
(defmethod source-file-type ((c objcl-c-source-file) (s module)) "c")
(defmethod perform ((o compile-op) (c objc-source-file))
(unless (or (operation-done-p o c)
(null (output-files o c)))
(zerop
;; Run `make' at the top level of the directory tree.
(let (#+allegro (asdf::*verbose-out* (if (find-package '#:swank)
nil
asdf::*verbose-out*)))
(run-shell-command "make -C '~A'"
(namestring
(make-pathname
:directory
(pathname-directory
(merge-pathnames
(make-pathname :directory '(:relative ".." ".."))
(first
(output-files
o
(find "libobjcl"
(module-components
(first
(module-components
(find-system "objective-cl-libobjcl"))))
:key #'component-name
:test #'string=))))))))))))
(defmethod output-files :around ((o compile-op) (c objc-source-file))
;; If this doesn't get called at all, we're kind of screwed.
(let ((files (call-next-method)))
(when (string= (component-name c) "libobjcl")
(setq *objc-obj-dir*
(merge-pathnames #p"../"
(directory-namestring (first files)))))
files))
(defmethod output-files ((o compile-op) (c objc-source-file))
(let ((relative-source-file
(enough-namestring (component-pathname c)
(merge-pathnames
(make-pathname :directory '(:relative "Objective-C"))
(component-pathname
(find-system "objective-cl-libobjcl"))))))
(list (merge-pathnames
(make-pathname :type "o")
(merge-pathnames
relative-source-file
(merge-pathnames
(make-pathname :directory '(:relative "Objective-C" "obj"))
(component-pathname (find-system "objective-cl-libobjcl"))))))))
(defmethod operation-done-p ((o compile-op) (c objc-source-file))
(and (every #'probe-file (output-files o c))
(> (loop for file in (output-files o c)
minimizing (file-write-date file))
(file-write-date (component-pathname c)))))
(defmethod perform ((o load-op) (c objc-source-file))
nil)
(defsystem "objective-cl-libobjcl"
:description "A portable Objective C bridge."
:version asdf::*objcl-version*
:author "Matthias Benkard "
:licence "GNU Lesser General Public License, version 3 or higher"
:depends-on ()
:components ((:module "Objective-C"
:components
((:objc-source-file "libobjcl")
(:objc-source-file "NSObject-ObjectiveCLWrapperLink")
(:module "PyObjC"
:components
((:objc-source-file "libffi_support")
(:objc-source-file "objc_support")
(:objc-source-file "objc-runtime-apple")
(:objc-source-file "objc-runtime-compat")
(:objc-source-file "objc-runtime-gnu")))
(:module "JIGS"
:components
((:objc-source-file "ObjcRuntimeUtilities2")
(:objcl-c-source-file "ObjcRuntimeUtilities"))))))
:serial t)
(defun sanitise-dir-name (pathname)
;; "/bla/stuff///" -> "/bla/stuff"
(loop with dir-name = (namestring pathname)
while (char= #\/ (elt dir-name (1- (length dir-name))))
do (setq dir-name (subseq dir-name 0 (1- (length dir-name))))
finally (return-from sanitise-dir-name dir-name)))
(defmethod perform :before (o (c objc-source-file))
;; Copy the Objective-C sources to the target directory.
(let ((output-files
(output-files (make-instance 'compile-op)
(find "libobjcl"
(module-components
(first
(module-components
(find-system "objective-cl-libobjcl"))))
:key #'component-name
:test #'string=))))
(unless (or *stuff-copied-p* (null output-files))
(setq *stuff-copied-p* t)
(let* ((source-dir (component-pathname (find-system "objective-cl-libobjcl")))
(output-dir
(merge-pathnames #p"../../"
(directory-namestring (first output-files))))
(output-parent-dir (merge-pathnames #p"../" output-dir))
#+allegro (asdf::*verbose-out* (if (find-package '#:swank)
nil
asdf::*verbose-out*)))
;; First try using cp to copy the files over to the compilation
;; directory. If that fails, do it manually, file by file.
(unless (and (not (position #\' (namestring source-dir))) ; a safety measure
(not (position #\' (namestring output-parent-dir)))
;; If the directories' inode numbers are the same,
;; we obviously don't need to copy anything.
;;
;; But first, do some sanity checks about the
;; environment.
(ignore-errors
(ensure-directories-exist output-parent-dir)
(or (and (zerop (run-shell-command "ls -d -i '~A'"
source-dir))
(zerop (run-shell-command "ls -d -i '~A'"
output-parent-dir))
(zerop (run-shell-command "echo"))
(zerop (run-shell-command "echo | awk '{ print $1 }'"))
(zerop
(run-shell-command
"test ~
\"x$(ls -d -i '~A' | awk '{ print $1 }')\" ~
= ~
\"x$(ls -d -i '~A' | awk '{ print $1 }')\""
source-dir
output-parent-dir)))
(zerop (run-shell-command "cp -R -P -f -p '~A' '~A/'"
(sanitise-dir-name source-dir)
output-parent-dir))))
(progn
(ensure-directories-exist output-dir)
(probe-file (merge-pathnames "GNUmakefile" output-dir))))
;; We couldn't use cp. Copy the files manually.
(let ((sources
(mapcar #'(lambda (x)
(enough-namestring x source-dir))
(mapcan #'(lambda (x)
(directory (merge-pathnames x source-dir)))
'(#p"**/*.c" #p"**/*.m" #p"**/*.h"
#p"**/GNUmakefile.*"
#p"**/*.make" #p"**/GNUmakefile"
#p"**/*.in" #p"**/configure" #p"**/configure.ac"
#p"libffi/**/*" #p"libffi/**/*.*")))))
(dolist (relative-source-file sources)
(let ((output-file (merge-pathnames relative-source-file output-dir))
(source-file (merge-pathnames relative-source-file source-dir)))
(ensure-directories-exist output-file)
(unless (and (probe-file output-file)
(= (file-write-date source-file)
(file-write-date output-file)))
(ignore-errors
;; FIXME: We need to skip directories, so that
;; IGNORE-ERRORS can go away.
(with-open-file (in source-file
:element-type '(unsigned-byte 8))
(with-open-file (out output-file
:direction :output
:if-exists :supersede
:element-type '(unsigned-byte 8))
(loop for byte = (read-byte in nil nil)
while byte
do (write-byte byte out))))))))))))))