SNAKE: The unholy offspring of make and Python
Snake is a build utility implemented in Python 2 with features similar to
many versions of make. This implementation uses GNU's version make as its
guiding influence.
Snake's operation is determined by a file called a "snakefile" containing
arbitrary Python code, plus make-like rules. The implementation is as a
preprocessor which turns rules into Python code; the processed buffer is
then interpreted by Python.
Many of the features of GNU make would add little to what Python can
already do. The features borrowed from make are therefore relatively few.
Snake can be used by other Python code as an imported module, and the
build rules set by Python code, or as a command line utility using a
snakefile in a manner quite similar to GNU make and its makefile.
SNAKEFILE
When used from the command line, snake attempts to load a file called
"Snakefile" or "snakefile" in the current directory, unless a different
snakefile is specified as a command line parameter. The snakefile looks
similar to a Python source file, with the addition of zero or more
"rules", as explained below. Snake requires the sys and os modules to
operate, so it implicitly imports them. Therefore it is unneccessary to
import there modules in the snakefile. In addition, the global symbol
"snake" will be available within the code in the snakefile. It is an
instance of the class snake.Snakefile.
The command-line parameters understood by snake are similar to those known
to GNU make. Execute snake with the '-h' flag to see usage information.
RULES
Rules have syntax very similar to that of GNU make. In general, they take the form
targets : prerequisites command ...
The commands are simply arbitrary Python code to be executed to build the
targets from the prerequisites. A rule says that to bring the listed
targets up to date, recursively bring the prerequisites up to date. If any
of the prerequisites required action, or are newer than the target, or the
target doesn't exist, the list commands are performed.
The normal indentation rules of Python determine what is part of the action
for a rule. The first target of rule may not be a word reserved by Python
for compound statement syntax ('if', 'for', 'try', etc.).
SHELL-LIKE SUBSTITUTION
The rule line "targets : prerequisite" is treated specially in that all occurrences of "$name" within the line will replaced by the name variable in the target or prerequisite. For example,
for src in ('a', 'b'): $src.o: $src.c util.c cc.compile(snake.prerequisites)
is equivalent to
a.o: a.c util.c cc.compile(snake.prerequisites) b.o: b.c util.c cc.compile(snake.prerequisites)
Also, '$(expr)' or '${expr}' will be substituted with the value of the given expression, which may not contain parentheses or curly braces, respectively. The string "$$" may be used to represent a single dollar sign in contexts where it would be interpreted as a substitution.
SHELL COMMANDS
Other than rule lines, the rest of the snakefile is treated as Python code,
with one exception. When the first nonblank character of a line is the '!'
character, the remainder of the line (possibly extended by a terminating
'\') is executed as a shell command (using sys.command()).
For example,
CC = 'gcc -c' for src in ('a', 'b'): $src.o: $src.c util.c !$(CC) ${snake.prerequisites.join()}
Notice that $-substitution also operates on shell commands (prior to sending the command to the shell).
PATTERN RULES
Simple pattern rules are supported as well. The appearance of a single '%' character in any target matches any target which can be formed by replacing the '%' with one or more characters. When this is the case, the appearance of a '%' in a prerequisite is replaced by the corresponding stem in the target. For example:
%.o: %.c util.c cc.compile(snake.prerequisites)
subsumes the two rules in the section above (and many more besides).
If more than one pattern rule would match a target, the one first defined
is used. However if an explicit rule (one without a '%') matches the
target, that rule take precedence.
IMPLICIT VARIABLES
GNU make has several implicit variables, which snake does not. Several fields of the globally defined object snake can be used within actions instead:
field make var meaning ------------------ ------- -------------------------- snake.target $@ current target for action snake.prerequisites $+ all prerequisites for target snake.newer $? those of above which were newer snake.stem $* stem for pattern replacement
OBJECT INTERFACE
The Snakefile object exposes several methods which can be used instead of,
or in conjuction with the make-like rules.
For example, the effects of this rule:
%.o: %.c util.c cc.compile(snake.prerequisites)
could be implemented via:
snake.depend('%.o', '%.c', 'util.c') snake.action('%.o', 'cc.compile(snake.prerequisites)')
Function pointers may also be specified as actions.
Outside of a snakefile, one could use this code:
s = Snakefile() s.depend('%.o', '%.c', 'util.c') s.action('%.o', 'cc.compile(snake.prerequisites)') s.make('junk.o')
which would cause the call to cc.compile to take place, if junk.o is out of date. Note that the variable snake is still bound within an action in this context.
MISCELLANEOUS FEATURES
-
The .PHONEY target is supported as in GNU make. See make's documentation
for further explanation.
-
Wildcard characters (*, ? and [...]) are supported in dependency lists, as
in
snake.depend('main.c', '*.h')
- Rules may appear within an action. This enables a target to select a rule or set of rules for later targets. This will probably rarely be useful, however.