Thanks very much for the ASDF example code. I used your technique of defining a custom operation to teach ASDF how to transform protocol buffer files into Lisp code, which can then be compiled and loaded. Usage looks like this:
(defsystem foobar ... :components ( ;; Protocol buffer file foo.proto, compiled into proto.lisp. (:proto-file "foo") ;; Protocol buffer google-protobuf/src/google/protobuf/unittest.lisp, ;; compiled into unittest.lisp. The protobuf file depends on another ;; and the protobuf search path for the compiler is also specified. (:proto-file "unittest" :proto-pathname "google-protobuf/src/google/protobuf/unittest" :depends-on ("unittest_import") :proto-search-path ("google-protobuf/src/")) ))
I had to use one ASDF method that is not exported, ASDF::COMPONENT-PARENT-PATHNAME. Perhaps it should be exported.
I also had to override a method, ASDF::COMPONENT-SELF-DEPENDENCIES. I don't understand self dependencies, so I may have made a mistake here, but without the method definition below, ASDF loads both the .lisp and .fasl files of each PROTO-FILE component. The method has a comment explaining why this happens
Anyway, the code is attached below. Thanks again.
bob
====================
;;; Teach ASDF how to convert protocol buffer definition files into Lisp.
(defparameter *protoc* #p"google-protobuf/src/protoc" "Pathname of the protocol buffer compiler.")
(defclass proto-file (cl-source-file) ((relative-proto-pathname :initarg :proto-pathname :initform nil :reader proto-pathname :documentation "Relative pathname that specifies a protobuf .proto file") (search-path :initform () :initarg :proto-search-path :reader search-path :documentation "List containing directories in which the protocol buffer compiler should search for imported protobuf files.")) (:documentation "A protocol buffer definition file."))
(defclass proto-to-lisp (operation) () (:documentation "An ASDF operation that compiles a file containing protocol buffer definitions into a Lisp source code."))
(defmethod component-depends-on ((operation compile-op) (component proto-file)) "Compiling a protocol buffer file depends on compiling dependencies and converting the protobuf file into Lisp source code." (append `((compile-op "package" "base" "protocol-buffer" "varint" "wire-format") (proto-to-lisp ,(component-name component))) (call-next-method)))
(defmethod component-depends-on ((operation load-op) (component proto-file)) "Loading a protocol buffer file depends on loading dependencies and converting the protobuf file into Lisp source code." (append `((load-op "package" "base" "protocol-buffer" "varint" "wire-format") (proto-to-lisp ,(component-name component))) (call-next-method)))
(defun proto-input (proto-file) "Return the pathname of the protocol buffer definition file that must be translated into Lisp source code for this PROTO-FILE component." (if (proto-pathname proto-file) ;; Path of the protobuf file was specified with :PROTO-PATHNAME. (merge-pathnames (make-pathname :type "proto") (merge-pathnames (pathname (proto-pathname proto-file)) (asdf::component-parent-pathname proto-file))) ;; No :PROTO-PATHNAME was specified, to the path of the protobuf ;; defaults to that of the Lisp file, but with a ".proto" suffix. (let ((lisp-pathname (component-pathname proto-file))) (merge-pathnames (make-pathname :type "proto") lisp-pathname))))
(defmethod input-files ((operation proto-to-lisp) (component proto-file)) (list *protoc* (proto-input component)))
(defmethod output-files ((operation proto-to-lisp) (component proto-file)) (list (component-pathname component)))
(defmethod perform ((operation proto-to-lisp) (component proto-file)) (let* ((source-file (proto-input component)) (output-file (component-pathname component)) (search-path (cons (directory-namestring source-file) (search-path component))) (status (run-shell-command "~A --proto_path=~{~A~^:~} --lisp_out=~A ~A" (namestring *protoc*) search-path (directory-namestring output-file) (namestring source-file)))) (unless (zerop status) (error 'compile-failed :component component :operation operation))))
(defmethod asdf::component-self-dependencies ((op load-op) (c proto-file)) "Remove PROTO-TO-LISP operations from self dependencies. Otherwise, the .lisp output files of PROTO-TO-LISP are considered to be input files for LOAD-OP, which means ASDF loads both the .lisp file and the .fasl file." (remove-if (lambda (x) (eq (car x) 'proto-to-lisp)) (call-next-method)))