Copyright 2003 George Peter Staplin
You may use this under the same terms as Tcl.
A Little Make Replacement (AKA tickmake)
Make is a common build tool. It basically checks the file mtime of dependencies and compares them to the file mtime of target files (if they exist). If a dependency is newer than the target then the target should be generated. Most make programs are complex. They usually implement a limited-command-language that is difficult to work with. Make also has a lot of historical baggage... With Tcl we have a great command language that is well suited for a tool like this.
Do not be misled by the small amount of code to do this. It really is this simple!
If you have the desire to modify this on a Wiki page please append your changed version or your changes and keep the original intact.
Tested with Tcl 8.4.1 in OpenBSD.
Thanks to Gerald Lester for debugging.
proc file.newer? {f _than_ f2} { if {![file exists $f] || ![file exists $f2]} { return 1 } if {[file mtime $f] > [file mtime $f2]} { return 1 } return 0 } proc is.file? f { return [file isfile $f] } proc is.target? t { return [info exists ::targetAr($t)] } array set ::targetAr {} proc make {t _from_ fromList _using_ body} { set ::targetAr($t) [list $fromList $body] interp alias {} $t {} make.target $t } proc make.target t { if {[is.target? $t]} { foreach {fromList body} [set ::targetAr($t)] break set build 0 foreach f $fromList { if {[is.target? $f]} { set r [make.target $f] if {[is.file? $f] && [file.newer? $f than $t]} { set build 1 } elseif {$r} { set build 1 } } elseif {[is.file? $f]} { if {!$build} { set build [file.newer? $f than $t] } } else { return -code error "$f isn't a target or existing source" } } if {$build} { uplevel #0 $body return 1 } return 0 } else { return -code error "make.target called with an invalid target: $t" } } proc mexec args { set cmd [join $args] puts $cmd catch {eval exec -- $cmd} msg if {[string length $msg]} { puts $msg } }
We will now give examples of usage.
It's quite easy to use, and everything is evaluated in level 0 (global). It passes all of these tests for me. You can play around with the touch command to see that it properly generates targets when needed.
set ::CC gcc set ::CFLAGS "-Wall -W" make test_2.o from test_2.c using { mexec $CC $CFLAGS -c test_2.c } make test_lib.a from [list test_2.o test_3.c] using { mexec $CC $CFLAGS -c test_3.c mexec ar cr test_lib.a test_2.o test_3.o mexec ranlib test_lib.a } make a.exe from [list test_1.c test_lib.a] using { mexec $CC $CFLAGS test_1.c test_lib.a -o a.exe } proc main {} { if {0 == $::argc} { a.exe } elseif {1 == $::argc} { eval [lindex $::argv 0] } else { return -code error "invalid number of arguments: $::argv0 ?target?" } } main
You can also use targets and sources with directory paths like so:
make tdir/a.exe from [list tdir/test_1.c tdir/test_2.c tdir/test_3.c] using { set d tdir exec gcc $d/test_1.c $d/test_2.c $d/test_3.c -o $d/a.exe } tdir/a.exe
EF The code above has served as the basis for my make library http://www.sics.se/~emmanuel/?Code:make . I only have added the possibility to force the execution of some rules using make.force. The code is part of the library.