Lisp Project Framework Generators

Project Framework Generators

I looked at two project framework generators: quickproject and cl-project.

Quickproject creates a quick and simple project, particularly good for things that are only going to be one or two source code files in size.

Cl-project goes a bit further and automatically created a test package as well and created source and testing directories. Cl-project also creates a tiny asd package to  avoid interning any symbols in the asd file to cl-user package.  (This was explained to me by the author, Eitarow Fukamachi, I did not figure this out on my own.)

Cl-project pushes the package file down into the source directory. As a personal preference, I prefer the package file (the file that provides the defpackage, export and use declarations) up at the top directory level, next to the asd file. But I think that is personal preference and you can easily move it to one place or the other. In creating the test-package, cl-project automatically pulls in cl-test-more as the testing package.

My wish list for the next iteration for either package is (1) a :testing-framework keyword allowing you to specify the testing framework (and, in the case of quickproject, automatically setting up a testing package and both a source and testing directory), (2) automatically adding a docs directory (a reminder to programmers that library users get a lot out of documentation such as tutorials - do not expect them to simply glance at the source code and understand all the wonderfull work you have done), (3) automatically adding an :export entry for completion (the author uses @export annotation of cl-annot, so he does not use :export the way I use it), (4) a :use keyword allowing you to specify what other packages are used, if any and (5) since it creates a gitignore file, can it take a :version-control keyword and and actually create the initial base, either in git, svn, mercury or darcs. Oh, but we don't have the libraries to do that - so Sabra, what are you going to do about that? Stop complaining and start coding.


Author: Zach Beane of quicklisp fame.

Sample Usage:

(quickproject:make-project #p"/Users/sabra/data/lisp/systems/test-project2/" :name "Test-project2" :author "Sabra Crolleton" :license "MIT" :depends-on '("hunchentoot" "cl-who" "cl-ppcre" "regex" "cl-irregsexp"))

results in 4 files: README.txt Test-project2.asd Test-project2.lisp and package.lisp.

After the project has been created, quickproject also adds its pathname to ASDF:*CENTRAL-REGISTRY*, so the project is immediately loadable via <tt>ASDF:LOAD-SYSTEM

Note the fact that I used initial caps in the name for the project and that was passed into the Name, but the defsystem declarations are all lower case. Both quickproject and cl-project handled this the same way.

The asd file reads as:

(asdf:defsystem #:test-project2 :serial t :description "Describe Test-project2 here" :author "Sabra Crolleton" :license "MIT" :depends-on (#:hunchentoot #:cl-who #:cl-ppcre #:regex #:cl-irregsexp) :components ((:file "package") (:file "test-project2")))

The package file reads as:

(defpackage #:test-project2 (:use #:cl))

The lisp file reads as:

(in-package #:test-project2) ;;; "Test-project2" goes here. Hacks and glory await!

As expected, the README.txt file is just a stub.

Redditor Joekarma posted the following:

"I use quickproject. I added the following to my .sbclrc file to take care of the git repo initialization problem. You'll notice it also sets the README format to markdown--this is my preferred format because it works well with Github.

(setf quickproject:*after-make-project-hooks*
  (lambda (pathname &rest args)
    (declare (ignore args))
    (nix:chdir (fad:pathname-as-directory pathname))
    (rename-file "README.txt" "README.markdown")
    (external-program:run "git"
                          (list "init" "."))
    (external-program:run "git"
                          (list "add" "."))
    (external-program:run "git"
                          (list "commit" "-m" "Initial commit")))))

You'll have to quickload some libraries first: '(:osicat :external-program :quickproject :cl-fad)

I also like to set defaults for the quickproject *licence* and *author* variables."


Author: Eitarow Fukamachi, of clack and caveman (web application framework) fame.


(cl-project:make-project #p"/Users/sabra/data/lisp/systems/test-project1" :name "Test-project1" :author "Sabra Crolleton" :license "MIT" :description "Test Project1" :email "" :depends-on '("hunchentoot" "cl-who" "cl-ppcre" "regex" "cl-irregsexp")) writing /Users/sabra/data/lisp/systems/test-project1/.gitignore writing /Users/sabra/data/lisp/systems/test-project1/README.markdown writing /Users/sabra/data/lisp/systems/test-project1/test-project1-test.asd writing /Users/sabra/data/lisp/systems/test-project1/test-project1.asd writing /Users/sabra/data/lisp/systems/test-project1/src/test-project1.lisp writing /Users/sabra/data/lisp/systems/test-project1/t/test-project1.lisp

A few things to note here. First, the system actually creates a .gitignore file which immediately fills itself with various fasl extensions to ignore. Second, the readme is markdown, rather than text. Third, cl-project creates source and test directories and automatically creates a test package as well as your intended package. Fourth, cl-project accepts a description keyword, which quickproject does not.

Unlike quickproject, cl-project doesn't create the link in ~/quickload/local-projects on new generated projects . If you create your projects under ~/quickload/local-projects, quicklisp will be able to quickload it, but if you put it somewhere else, you should create a link to your project.

The README.markdown file is slightly more than a stub, providing a bit more than quickproject:

# Test-Project1 - Test Project1 ## Usage ## Installation ## Author * Sabra Crolleton ## Copyright Copyright (c) 2012 Sabra Crolleton # License Licensed under the MIT License.

A few things to note in the asd file. First, it provides a version and long-description for the defsystem.The asd file pulls the README.markdown file into the long-description of the asd file. Unlike quickproject, the generated defsystem does not provide :serial t.

#| This file is a part of Test-project1 project. Copyright (c) 2012 Sabra Crolleton |# #| Test Project1 Author: Sabra Crolleton |# (in-package :cl-user) (defpackage Test-project1-asd (:use :cl :asdf)) (in-package :Test-project1-asd) (defsystem Test-project1 :version "0.1" :author "Sabra Crolleton" :email " :license "MIT" :depends-on (:hunchentoot :cl-who :cl-ppcre :regex :cl-irregsexp) :components ((:module "src" :components ((:file "Test-project1")))) :description "Test Project1" :long-description #.(with-open-file (stream (merge-pathnames #p"README.markdown" (or *load-pathname* *compile-file-pathname*)) :if-does-not-exist nil :direction :input) (when stream (let ((seq (make-array (file-length stream) :element-type 'character :fill-pointer t))) (setf (fill-pointer seq) (read-sequence seq stream)) seq))) :in-order-to ((test-op (load-op Test-project1-test))))

cl-project also does not provide a separate package.lisp file. The defpackage information is, rather, put into the named lisp file in the source directory. E.g.:

#| This file is a part of Test-project1 project. Copyright (c) 2012 Sabra Crolleton |# (in-package :cl-user) (defpackage Test-project1 (:use :cl)) (in-package :Test-project1) ;; blah blah blah.

Now looking at the test package created, cl-project creates both a test package and a test directory. As expected, the test package depends on and uses your new package, but it also depends on and uses the package cl-test-more. You can, of course, substitute the testing package of your choice. (Why do we have 27 different testing frameworks?)