chickadee » csm

csm

csm is a build system for CHICKEN Scheme executables.

Usage

usage: csm [-help] [-n] [-d] [-static] [-r7rs] [-depends FILENAME] [-ignore FILENAME] [-main MODULE] [-D FEATURE] [-program NAME] [-scan] [-clean] [-dd] [-g] [-x] [-csm-extend FILENAME] [-makefile FILENAME] [-max-procs N] [-compile-imports] [DIRECTORY] [OPTION ...]

-help
show informational message.
-n
"dry run", display performed operations but do not execute them.
-d
debug mode - show information about what the tool is doing.
-x
explain - show additional information why certain actions are performed.
-static
build static binaries.
-r7rs
assume all source code is R7RS code.
-depends FILENAME
all source files depend on FILENAME.
-ignore FILENAME
ignore given file or directory when scanning source files.
-program NAME
declare that an executable program with this name should be built.
-main MODULE
designate main module of executable to be built.
-D FEATURE
define given feature, similar to the csc option with the same name.
-scan
only scan source files, do not build.
-clean
clean build artifacts.
-dd
dump dependencies in make(1) format.
-g
generate makefile instead of building executables.
-csm-extend FILENAME
loads FILENAME into csm to allow using read-syntax extensions defined in external files.
-makefile FILENAME
like -g, but generate FILENAME instead of Makefile.
-max-procs N
compile at most N files in parallel (alias: -j), the default is 1.
-compile-imports
compile import libraries after they are generated.
OPTION ...
pass all other options to csc.
DIRECTORY
designates DIRECTORY as root source directory instead of the current directory.

Only one of the options -scan, -clean, -dd, -g or -makefile can be used in the same invocation of csm.

Principle of Operation

csm analyzes all Scheme source files in a directory tree and extracts module and file dependencies. Source files can be organized arbitrarily but should contain at most one module. csm maps modules to source files and builds a dependency graph from import forms found during the file system traversal.

Files having the extensions scm, ss, scheme, sch, r4rs or r5rs are assumed to contain Scheme source code. Files having the extensions sld or r7rs are assumed to contain R7RS source code. Files containing module definitions at toplevel are assumed to be components of one or more executable targets.

The following toplevel forms are processed and used to infer file and module dependencies among the various components of a project:

module, functor and define-library (R7RS) determine nodes in the overall module dependency graph.

import, import-for-syntax, import-syntax-for-syntax and the two supported flavours of module NAME = ... (functor instantiation) determine module dependencies.

include, include-relative, include-ci (R7RS) and include-library-declarations (R7RS) determine file dependencies.

The forms begin, begin0, begin-for-syntax are traversed recursively.

bind-file and bind-file* record external dependencies to C/C++ files.

Note that csm will not detect redefinitions or renamings of these forms, it assumes they have their default semantics.

cond-expand is fully expanded and can be used to conditionalize file or module dependencies.

Files having the extensions c, cpp or cxx are assumed to be native C/C++ modules that should be compiled and linked to a module or program.

After scanning the file tree, all modules and program targets are build if they do not yet exist or if one of their dependencies have been modified later than the target.

By default, all modules are compiled to dynamically loadable shared objects (.sos). Modules that are designated as the main module of a standalone program are compiled as static (.o) files, as are direct dependencies of programs main modules. In fully static mode, all modules are compiled to static object files, with the exception of modules imported "for syntax". All source files containing modules are compiled using csc.

The anticipated use case is for a project to have one or more programs, using any number of secondary modules. Programs are declared using "options" (see below) and can be conveniently compiled to static standalone executables. csm is not intended for eggs, for this the existing declarative egg specification format is sufficient.

Options

In addition to flags given on the csm command line, options for one or more source files can be defined using designated files in the source tree. If the root directory of the source tree or one of its child directories contains a file named all.options then that file should hold S-expressions giving command line options that are to be passed to all compiled source files containing a module definition, in the same directory or in a subdirectory. Program- or source-file specific options can be defined by creating a file named <PROGRAM>.options or <ROOTNAME>.options in the root source directory or in the directory where the source file is located. ROOTNAME should be the basename of the source file excluding the file extension.

The options are read as S-expressions and should be quoted using "..." in case they are ambiguous (like -i or -I). Lists in option files are spliced and (cond-expand ...) is detected and expanded so it is possible to use the #+FEATURE read syntax to conditionalize the options.

Option files are added as dependencies, so should they change, targets depending on those files are rebuilt.

The following additional options are detected and handled specially (all other options are simply passed on to csc):

-ignore FILENAME
ignore the file or directory FILENAME when walking the source tree.
-program NAME
declare program with this name to be a build target.
-main MODULE
declare main module for program(s) (defaults to name of program).
-depends OBJECT
module or program depends on source file, binary object file or module.
-static
the program (or all programs) should be build statically.

As a special case, a clause -depends FILENAME.o declares a dependency on a C/C++ object file if a native module named FILENAME was detected.

Note that -ignore and -depends do not accept wildcard pathnames.

Conventions and Caveats

Examples

Trivial: "hello, word"

The most basic project consisting of a single source file.

% cat hello.scm
(module hello ()
  (import scheme)
  (display "Hello, world!\n"))
% csm
  '/home/felix/.local/bin/csc' '-s' '-J' '-I' '/home/felix/csm/tests/hello' '-C' '-I' '-C' '/home/felix/csm/tests/hello' '/home/felix/csm/tests/hello/hello.scm' '-o' 'hello.so'
% csi -q
#;1> (import hello)
; loading ./hello.import.scm ...
; loading ./hello.so ...
Hello, world!
#;2> ,q

Let's create a standalone executable:

% csm -clean
% csm -program hello
  '/home/felix/.local/bin/csc' '-o' 'hello' '-I' '/home/felix/csm/tests/hello' '-C' '-I' '-C' '/home/felix/csm/tests/hello' '/home/felix/csm/tests/hello/hello.scm'
% hello
Hello, world!

Complex: a window manager

Here we generate an X11 window manager program that uses the foreign function interface to access Xlib. This is a more or less faithful reimplementation of TinyWM. The bind extension is required to build this example.

Scheme files:

% cat wm.scm
(module wm (start)
  (import scheme)
  (import (chicken base))
  (import (wm x11))
  
(define (start)
  (and-let* ((dpy (init_x11)))
    (let ((start (make_button_event))
          (ev (make_event))
          (attr (make_attributes)))
      (subwindow_set start 0)
      (let loop ()
        (XNextEvent dpy ev)
        (cond ((and (= (event_type ev) KeyPress)
                    (not (zero? (event_subwindow ev))))
                (XRaiseWindow dpy (event_subwindow ev)))
              ((and (= (event_type ev) ButtonPress)
                    (not (zero? (event_subwindow ev))))
                (XGetWindowAttributes dpy (event_subwindow ev) attr)
                (button_event_copy start ev))
              ((and (= (event_type ev) MotionNotify)
                    (not (zero? (event_subwindow start))))
                (let ((xdiff (- (event_x_root ev) (event_x_root start)))
                      (ydiff (- (event_y_root ev) (event_y_root start))))
                  (XMoveResizeWindow dpy (event_subwindow start)
                    (+ (attributes_x attr) 
                       (if (= (event_button start) 1) xdiff 0))
                    (+ (attributes_y attr) 
                       (if (= (event_button start) 1) ydiff 0))
                    (max 1 (+ (attributes_width attr)
                              (if (= (event_button start) 3) xdiff 0)))
                    (max 1 (+ (attributes_height attr)
                              (if (= (event_button start) 3) ydiff 0))))))
              ((= (event_type ev) ButtonRelease)
                (subwindow_set start 0)))
        (loop)))))
)
% cat tinywm.scm
(module tinywm ()
  (import scheme)
  (import (chicken base))
  (import wm)
  (start))
% cat wrap.scm
(module (wm x11) *
  (import scheme)
  (import bind)
  (import (chicken foreign))
  (foreign-declare "#include <X11/Xlib.h>")
  (define KeyPress (foreign-value "KeyPress" int))
  (define ButtonPress (foreign-value "ButtonPress" int))
  (define MotionNotify (foreign-value "MotionNotify" int))
  (define ButtonRelease (foreign-value "ButtonRelease" int))
  (bind-file* "x11.h"))

C files:

% cat x11.c
#include <X11/Xlib.h>
#include <stdlib.h>

Display *init_x11(void) {
    Display * dpy;
    if(!(dpy = XOpenDisplay(0x0))) return NULL;
    XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("F1")), Mod1Mask,
            DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync);
    XGrabButton(dpy, 1, Mod1Mask, DefaultRootWindow(dpy), True,
            ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
    XGrabButton(dpy, 3, Mod1Mask, DefaultRootWindow(dpy), True,
            ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
    return dpy;
}

XButtonEvent *make_button_event(void) {return malloc(sizeof(XButtonEvent));}
XEvent *make_event(void) {return malloc(sizeof(XEvent));}
XWindowAttributes *make_attributes(void) {return malloc(sizeof(XWindowAttributes));}
void subwindow_set(XButtonEvent *start, int win) {start->subwindow = win;}
int event_type(XEvent *ev) {return ev->type;}
int event_subwindow(XButtonEvent *ev) {return ev->subwindow;}
void button_event_copy(XButtonEvent *ev1, XButtonEvent *ev2) {*ev1 = *ev2;}
int event_x_root(XEvent *e) {return e->xmotion.x_root;}
int event_y_root(XEvent *e) {return e->xmotion.y_root;}
int attributes_x(XWindowAttributes *attr) {return attr->x;}
int attributes_y(XWindowAttributes *attr) {return attr->y;}
int attributes_width(XWindowAttributes *attr) {return attr->width;}
int attributes_height(XWindowAttributes *attr) {return attr->height;}
int event_button(XButtonEvent *ev) {return ev->button;}
% cat x11.h
int XMoveResizeWindow(Display *dpy, unsigned long win, int x, int y, unsigned int w, unsigned int h);
int XNextEvent(Display *dpy, XEvent *ev);
int XRaiseWindow(Display *dpy, unsigned long win);
int XGetWindowAttributes(Display *dpy, unsigned long win, XWindowAttributes *attr);
Display *init_x11(void);
XButtonEvent *make_button_event(void);
XEvent *make_event(void);
XWindowAttributes *make_attributes(void);
void subwindow_set(XButtonEvent *start, unsigned long win);
int event_type(XEvent *ev);
int event_subwindow(XButtonEvent *ev);
void button_event_copy(XButtonEvent *ev1, XButtonEvent *ev2);
int event_x_root(XEvent *e);
int event_y_root(XEvent *e);
int attributes_x(XWindowAttributes *attr);
int attributes_y(XWindowAttributes *attr);
int attributes_width(XWindowAttributes *attr);
int attributes_height(XWindowAttributes *attr);
int event_button(XButtonEvent *ev);

Options customizing the build:

% cat all.options
-program tinywm -C -I/usr/X11R6/include
% cat tinywm.options
-L -L/usr/X11R6/lib -L -lX11
% cat wrap.options
-depends x11.o

This time we generate a makefile:

% csm -g
% make
/home/felix/.local/bin/csc '-c' '-s' '-I' '/home/felix/csm/tests/wm' '-C' '-I' '-C' '/home/felix/csm/tests/wm' '/home/felix/csm/tests/wm/x11.c' '-o' 'x11.o' '-C' '-I/usr/X11R6/include'
/home/felix/.local/bin/csc '-s' '-J' '-I' '/home/felix/csm/tests/wm' '-C' '-I' '-C' '/home/felix/csm/tests/wm' '/home/felix/csm/tests/wm/wrap.scm' '-o' 'wm.x11.so' 'x11.o' 'x11.o' '-C' '-I/usr/X11R6/include'
/home/felix/.local/bin/csc '-c' '-J' '/home/felix/csm/tests/wm/wm.scm' '-I' '/home/felix/csm/tests/wm' '-C' '-I' '-C' '/home/felix/csm/tests/wm' '-unit' 'wm' '-o' 'wm.o' '-C' '-I/usr/X11R6/include'
/home/felix/.local/bin/csc '-o' 'tinywm' '-I' '/home/felix/csm/tests/wm' '-C' '-I' '-C' '/home/felix/csm/tests/wm' 'wm.o' '-uses' 'wm' '/home/felix/csm/tests/wm/tinywm.scm' '-C' '-I/usr/X11R6/include' '-L' '-L/usr/X11R6/lib' '-L' '-lX11' 

Now let's try it out, using Xephyr(1):

% Xephyr -ac -reset :1 &
% DISPLAY=:1 tinywm &
% DISPLAY=:1 xterm 

An R7RS program

Here we build the example given in the R7RS report:

% cat main.scm
(define (damped-oscillator R L C)
  (lambda (state)
    (let ((Vc (vector-ref state 0))
          (Il (vector-ref state 1)))
       (vector (- 0 (+ (/ Vc (* R C)) (/ Il C)))
               (/ Vc L)))))

(define (main count)
  (let ((the-states
          (integrate-system
            (damped-oscillator 10000 1000 .001)
            '#(1 0)
            .01)))
    (do ((i count (- i 1))
         (states the-states (tail states)))
        ((zero? i))
      (let ((h (head states)))
        (printf "~a ~a\n" (vector-ref h 0) (vector-ref h 1))))))
% cat example.scm
(define-library example
  (import (scheme))
  (import (integrate))
  (import (chicken format))
  (include "main.scm")
  (begin (main 100)))
% cat integrate.scm
(define-library integrate
  (import (scheme base))
  (import (scheme lazy))
  (export integrate-system head tail)
  (include "integrate-implementation.scm"))
% cat integrate-implementation.scm
; The procedure integrate-system integrates the system
; y′k = fk(y1, y2, . . . , yn), k = 1, . . . , n
; of differential equations with the method of Runge-Kutta.
; The parameter system-derivative is a function that
; takes a system state (a vector of values for the state vari-
; ables y1, . . . , yn) and produces a system derivative (the val-
; ues y′1, . . . , y′n). The parameter initial-state provides
; an initial system state, and h is an initial guess for the
; length of the integration step.
; The value returned by integrate-system is an infi-
; nite stream of system states.

(define (integrate-system system-derivative initial-state h)
  (let ((next (runge-kutta-4 system-derivative h)))
    (letrec ((states 
               (cons initial-state
                     (delay (map-streams next states)))))
      states)))

(define (runge-kutta-4 f h)
  (let ((*h (scale-vector h))
        (*2 (scale-vector 2))
        (*1/2 (scale-vector (/ 1 2)))
        (*1/6 (scale-vector (/ 1 6))))
    (lambda (y)
      ;; y is a system state
      (let* ((k0 (*h (f y)))
             (k1 (*h (f (add-vectors y (*1/2 k0)))))
             (k2 (*h (f (add-vectors y (*1/2 k1)))))
             (k3 (*h (f (add-vectors y k2)))))
        (add-vectors y
          (*1/6 (add-vectors k0
                             (*2 k1)
                             (*2 k2)
                             k3)))))))

(define (elementwise f)
  (lambda vectors
    (generate-vector
      (vector-length (car vectors))
      (lambda (i)
        (apply f
               (map (lambda (v) (vector-ref v i))
                    vectors))))))

(define (generate-vector size proc)
  (let ((ans (make-vector size)))
    (letrec ((loop
               (lambda (i)
                 (cond ((= i size) ans)
                       (else
                         (vector-set! ans i (proc i))
                         (loop (+ i 1)))))))
      (loop 0))))

(define add-vectors (elementwise +))

(define (scale-vector s)
  (elementwise (lambda (x) (* x s))))

(define (map-streams f s)
  (cons (f (head s))
        (delay (map-streams f (tail s)))))

(define head car)

(define (tail stream)
  (force (cdr stream)))
% cat all.options
-program example

We recklessly decide to link the final program statically. Note that we could have added the -static to all.options to build statically by default:

% csm -static
  '/home/felix/.local/bin/csc' '-c' '-static' '-J' '/home/felix/csm/tests/rk/integrate.scm' '-I' '/home/felix/csm/tests/rk' '-C' '-I' '-C' '/home/felix/csm/tests/rk' '-X' 'r7rs' '-R' 'r7rs' '-M' '-unit' 'integrate' '-emit-link-file' 'integrate.link' '-o' 'integrate.o'
  '/home/felix/.local/bin/csc' '-o' 'example' '-I' '/home/felix/csm/tests/rk' '-C' '-I' '-C' '/home/felix/csm/tests/rk' '-X' 'r7rs' '-R' 'r7rs' '-static' 'integrate.o' '-uses' 'integrate' '/home/felix/csm/tests/rk/example.scm'
% example
<lots of numbers>

Author

Felix Winkelmann

Repository

This egg is hosted on the CHICKEN Subversion repository:

https://anonymous@code.call-cc.org/svn/chicken-eggs/release/5/csm

If you want to check out the source code repository of this egg and you are not familiar with Subversion, see this page.

Version history

0.5
added support for functors, contributed by "gahr".
0.4
added -max-procs, the logic for parallelizing the build has been contributed by "gahr".
0.3
added -compile-imports
0.2
added -csm-extend option (thanks to Matt Welland for reporting this shortcoming)
0.1
Initial release

License

BSD