**tclmake 2022, v. 2.2** [SEH] 20220304 -- Version 2.2: fix for bug in operation of "--update" command line option. [SEH] 20220118 -- Released version 2.1 with newly-configured ability to evaluate Tcl code in make variables. [AM] discovered that Tcl code placed in make variables would be evaluated, due to inadvisable use of [eval] commands in the old sections of the code. Though useful, those eval commands presented potential problems in makefile parsing, so I replaced the eval's with expand operators, but kept the ability to execute Tcl code by starting a make variable definition line with the keyword MAKE_EVAL. See examples below. [SEH] 20220110 -- I've done some refurbishing of the original tclmake code released as part of the [https://ptolemy.berkeley.edu/%|%Ptolemy] project at Berkeley, and added some new features. The new code can be found at: [https://github.com/tcllab/tclmake] 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: # Value of MAKE_INIT var is treated as a Tcl script and executed after file # is parsed but before goal updating: MAKE_INIT = make_init # Make variables defined: SDX = lib/sdx.kit RUNTIME = bin/basekit # Procs can be defined and called within rule recipe script: proc make_init {} { set ::env(PATH) $::env(PATH):[file norm ~/bin] } # An option rule: --test-wrap : # Automatic variables like "$!" inserted into Tcl script by macro # substitution before execution, as GNU Make does: 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 # Proc MAKE_UPDATE drives whether a target is updated based on given # conditional, independent of file mtimes of prerequisites: MAKE_UPDATE $target [list $version ne $vfsversion] # A GNU make-style pattern rules: %.kit : %.vfs --test-wrap @puts "Wrapping $@:" # Make vars substituted into Tcl script. If a var is undefined, an empty string is used: exec basekit $(SDX) wrap "$@" -vfs "$<" $(WRAP_OPTIONS) %.exe : %.kit if {"$(RUNTIME)" eq ""} { error "Make variable RUNTIME must be defined to make starpack." } @puts "Wrapping $@:" set vfs [file root "$@"].vfs exec basekit $(SDX) wrap "$@" -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 [http://ptolemy.eecs.berkeley.edu/%7Ejohnr/archives/code/tclmake/%|%Tclmake] ---- [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 https://github.com/WarrenWeckesser/dde_solver/ 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 # keyword "MAKE_EVAL" at start of make var definition causes var value to # have Tcl command and variable substitution done on it and result stored # in var: MAKE_EVAL SOURCES = [glob examples/*.f90] MAKE_EVAL EXES = [string map {.f90 .exe} {$(SOURCES)}] # Simple rule. First target in file becomes default goal: all: $(EXES) @puts "prereqs:$^" # Simple rule without recipe adds prerequisites to targets: $(EXES): dde_solver_m.o # Pattern rule: %.exe : %.f90 exec gfortran -o $@ $< dde_solver_m.o # This target is added as a dependency to all files in $(EXES) var: 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. (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] 20220118 -- N.B. I rewrote AM's example above to conform to how tclmake 2.1 evaluates Tcl code in make variables. 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 $@ $< dde_solver_m.o dde_solver_m.o : dde_solver_m.f90 exec gfortran -c $< ====== <> Application | Dev. Tools | make