For existing projects, which already have an established Makefile system this may not be practical or desired. So for our first example we will provide a minimal shim to allow make to invoke practcl.
The example here is a stylized version of the build system for odielib. Bits have been edited for clarity, and may vary slightly from the production code.
# Minimal make.tcl with a single target
set CWD [pwd]
set ::project(builddir) $::CWD
set ::project(srcdir) [file dirname [file normalize [info script]]]
set ::project(sandbox) [file dirname $::project(srcdir)]
if {[file exists [file join $CWD .. tclconfig practcl.tcl]]} {
source [file join $CWD .. tclconfig practcl.tcl]
} else {
source [file join $SRCPATH tclconfig practcl.tcl]
}
array set ::project [::practcl::config.tcl $CWD]
::practcl::library create LIBRARY [array get ::project]
LIBRARY source [file join $::project(srcdir) library.ini]
LIBRARY implement $::project(builddir)
set fout [open pkgIndex.tcl w]
puts $fout "
#
# Tcl package index file
#
"
puts $fout [LIBRARY package-ifneeded]
close $fout
This make script has no options, not targets, and only has a single task which will:
- Read our practcl build rules (in the library.ini file)
- generate a PKGNAME.mk file (which is a supplementary list of instructions for the Makefile)
- generate the pkgIndex.tcl file (which will notifies Tcl about the package and how to load it.)
The library.ini contains instructions for the LIBRARY object. For this package it looks like:
set SRCPATH [file normalize [my define get srcdir]]
my add [file join $SRCPATH cmodules btree module.ini]
my add [file join $SRCPATH cmodules odieutil module.ini]
my add [file join $SRCPATH cmodules geometry module.ini]
my define add public-include <tcl.h>
my define add public-include <assert.h>
my define add public-include <stdio.h>
my define add public-include <stdlib.h>
my define add public-include <string.h>
my define add public-include <math.h>
my define add include_dir [my define get builddir]
You will note the "my" in front of many statements. This is because the script is actually invoked within the namespace of a TclOO object. This object is the master controller for the project.
The add method introduces a new subordinate object. In this case we have added three modules, which themselves will be objects. The add method has an auto-dectection algorithm to pair the appropriate class for a new subordinate by file extension. If we want more control we could have specified:
my add class module filename [file join $SRCPATH cmodules btree module.ini]
Modules, in turn, can have subordinates.
The btree module is straightforward:
set here [file dirname [file normalize [info script]]]
my add [file join $here tree.tcl]
The tree.tcl file, in turn defines data structures and functions we want available within our library. That file contains a stream of commands along the lines of:
my c_structure Tree {
/* A complete binary tree is defined by an instance of the following
** structure
*/
int (*xCompare)(const void*, const void*); /* Comparison function */
void *(*xCopy)(const void*); /* Key copy function, or NULL */
void (*xFree)(void*); /* Key delete function */
struct TreeElem *top; /* The top-most node of the tree */
};
And
my c_function {static void TreeClearNode(TreeElem *p, void (*xFree)(void*))} {
/* Delete a single node of the binary tree and all of its children */
if( p==0 ) return;
if( p->left ) TreeClearNode(p->left, xFree);
if( p->right ) TreeClearNode(p->right, xFree);
if( xFree ){
xFree(p->key);
}
Tcl_Free((char *)p);
}
Methods also exist for injecting arbitrary block of C code into specific places in the resulting C file, defining Tcl commands, and building OO classes.
Source files can also be read in from pure C:
my add [file join $here md5.c]
or
my add class csource initfunc Md5_Init filename [file join $here md5.c]