tclmake 2022, v. 2.0

SEH 20220110 -- I've done some refurbishing of the original tclmake code released as part of the Ptolemy project at Berkeley, and added some new features. The new code can be found at: [L1 ]

tclmake is not meant to be a clone of standard make, but it borrows many features and adds a few useful features of its own. Anyone with experience using standard make should find it easy to pick up and use tclmake.

If you've ever struggled with writing a GNU makefile and thought, 'this would go a lot easier if I could write the update logic in Tcl', then tclmake may be the tool for you.

What a tclmake makefile looks like:

    MAKE_INIT = make_init
    SDX = lib/sdx.kit
    RUNTIME = bin/basekit

    proc make_init {} {
            set ::env(PATH) $::env(PATH):[file norm ~/bin]

    --test-wrap :
            set target "$!"
            if {![file exists $target]} return
            set vfs [file root $target].vfs
            lassign [exec basekit $(SDX) version $target] date time version
            lassign [exec basekit $(SDX) version $vfs] vfsdate vfstime vfsversion
            MAKE_UPDATE $target [list $version ne $vfsversion]

    %.kit : %.vfs --test-wrap
            puts "Wrapping [email protected]:"
            exec basekit $(SDX) wrap "[email protected]" -vfs "$<" $(WRAP_OPTIONS)

    %.exe : %.kit
            if {"$(RUNTIME)" eq ""} {
                    error "Make variable RUNTIME must be defined to make starpack."
            puts "Wrapping [email protected]:"
            set vfs [file root "[email protected]"].vfs
            exec basekit $(SDX) wrap "[email protected]" -vfs $vfs -runtime "$(RUNTIME)" $(WRAP_OPTIONS)

Like GNU make, tclmake features simple and pattern rules with targets and prerequisites, make variables and automatic variables with values inserted by macro substitution.

tclmake can be used as a stand-alone command line program, or as a package within another Tcl project.

tclmake allows you to define Tcl procs in the makefile, allowing you to organize complex update logic within your makefile.

You can define a 'MAKE_INIT' make variable to contain a Tcl script. If the variable exists after the makefile has been parsed but before updating of targets begins, this script will be evaluated. The script can for example change directories, load packages or otherwise initialize the environment.

The procedure MAKE_UPDATE is available to be called by any Tcl script in the makefile. It takes two arguments: a target name and a conditional expression. If the conditional evaluates to true, the update script of the specified target is run regardless of whether the target is out of date with respect to its prerequisites. If the expression is false, the target will not be updated even if it is out of date.

The MAKE_UPDATE proc allows you to define any criteria for updating a target, beyond simply comparing file modification times. If MAKE_UPDATE is called in the script of one of the target's prerequisites, when prerequisite updating is done and it's time for the target to be updated, the target's update script will be run or not based on the result of the MAKE_UPDATE conditional.

The example makefile above shows rules for wrapping a starkit and creating a starpack. If you want to know if a wrapped starkit needs to be updated from its unwrapped counterpart, you need to know if any file in the unwrapped starkit has changed. In a standard GNU makefile, you'd have to include all files in the unwrapped starkit as dependencies, and ensure this list of files remains current as your starkit is developed.

Fortunately the sdx package includes a command to create a version stamp for a starkit, which operates identically on a wrapped and an unwrapped starkit. The option rule '--test-wrap' generates version stamps for a wrapped and unwrapped starkit, and the MAKE_UPDATE proc is used to compare the version stamps and mark if the wrapped starkit needs to be regenerated.

Version 1.0 released 1998:

Author: John Reekie

Contact: John Reekie atatat uts dot edu dot au


AM After some initial struggles to get it to work, I managed to create an intriguing little makefile. To give some context:

  • I used a Github package that contains a Fortran module in the main directory and a bunch of examples in the subdirectory examples.
  • Rather than write out the names of the examples in a makefile, I used [glob] to find the names of the individual source files and then the rule definitons that tclmake supports to automatically build all the examples.

The result is this minimalistic makefile in the tclmake style:

# makefile for "DDE_SOLVER" module and its examples

SOURCES = [glob examples/*.f90]
EXES    = [string map {.f90 .exe} $(SOURCES)]

%.exe : %.f90
    exec gfortran -o [email protected] $< dde_solver_m.o

all:        dde_solver_m.o {*}$(EXES)
    puts $(SOURCES)

dde_solver_m.o : dde_solver_m.f90
    exec gfortran -c $<

Note the list expansion in the "all" target. It took a bit of experimentation (as I am a trifle rusty on my make skills), but it works. You could go on with parametrising the stuff (.exe and .o and the name of the compiler), but for a first not-so-trivial makefile this does well enough.

Looking at the output I realise that the Tcl commands at the top (assignment of SOURCES and EXES) are not executed right away, but it does work.

(The build step fails when it tries to build the "program" in q1damodule.f90 as that merely defines another module, not a program perse. Oh well, that shows that a more in-depth analysis is required, but note: it does build 14 of the examples ;))

SEH 20220114 -- A nice example, thanks... I hadn't actually tried putting Tcl code into make variables. As you noticed, make variables are inserted into rules by macro substitution (as GNU make does) and aren't evaluated at definition time. Since tclmake expands wildcards in targets and prerequisites with the glob command, the code in the make variables is executed as a by-product of the wildcard expansion.

You could move the expand operator to the make variable, i.e.:

 EXES    = {*}[string map {.f90 .exe} $(SOURCES)]

if you wanted to segregate Tcl syntax in the make variables instead of letting it leak into the rule itself.

AM's use case is a good fit for tclmake's "reverse pattern match rule" feature. Instead of the usual specifying of targets and matching prerequisites with them, a rule using this feature's syntax will glob a set of prerequisites, construct targets for them, then run the associated Tcl script once for each target/prereq pair. So the makefile above could be rewritten as:

all: dde_solver_m.o all_examples
all_examples : examples/%.exe :: examples/%.f90
   exec gfortran -o [email protected] $< dde_solver_m.o

dde_solver_m.o : dde_solver_m.f90
   exec gfortran -c $<