NAME
bakefile - Bake description file
DESCRIPTION
In analogy to Make, a bakefile is the standard description file for
Bake. Bakefiles are plain Python source code, included by an
‘execfile’ function call. There are defined several classes and
functions before a bakefile is entered; those are mainly build rules
and variable (macro) assignments, further some small helpers.
You declare rules, suffix rules or variable assignments by creating an
instance of a class ‘Rule’, ‘Suffix’ or ‘Var’. The object may be
forgotten by your code; Bake will still keep it if once it was
constructed.
The declarations may look completely different as the makefiles your
are used to. But the way they work is as close as possible to that of
Make.
VARIABLES
A variable definition looks like this:
Var( ’CC’, ’gcc-3.3’)
Var( ’CFLAGS’, ’-g -Wall’)
In shell commands and in prerequisite declarations, macros are expanded
when a ‘$’ (dollar sign) occurs. A given command
’${CC} -o myprog myprog.o ${CFLAGS}’
will expand to
’gcc-3.3 -o myprog myprog.o -g -Wall’
Lowest precedence have the environment variables. If a Bake variable
is declared, this overrides the environment. If a variable is assigned
to on the command line, variable assignments in the previous read
bakefile are overridden. The behaviour of environment variables may be
changed using the -e Option on the command line.
If a variable isn’t defined at all, it is assumed to be the empty
string.
RULES
Static rules
To declare a rule, create an instance of class ‘Rule’:
Rule( File,
’myprg’,
’myprg.o mymodule.o’,
’cc -o $@ $&’)
or
Rule( cls = File,
name = ’myprg’,
prereqs = ’myprg.o mymodule.o’,
cmds = ’cc -o $@ $&’)
The first argument, ‘File’ means that a file will be produced and its
time stamp has to be checked. (Phony targets will be described later
in this section.)
The second and the third argument correspond to the rule declaration
line of Make. The second is what comes before the colon and the third
what comes after. The rule name is the name of the target file to be
built. The prerequisites are the files the target is built from.
Here, this is a string with file names separated by spaces; it is also
allowed to give a Python list:
prereqs = [ ’myprg.o’, ’mymodule.o’]
The prerequisites are looked up if they match build rules themselves.
If so, these targets are built first. The dependent targets are built
in the same order as they are specified in the ‘prereqs’ argument.
The last argument is a command or a list of commands to be executed.
When a list is given, every command runs in its own shell. That is,
when you say
cmds = [ ’cd subdir’, ’./do_sth’]
you probably meant one of:
cmds = ’cd subdir ; ./do_sth’
cmds = ’cd subdir\n./do_sth’
cmds = ’’’\
cd subdir
./do_sth’’’
The macros ‘$@’ and ‘$&’ expand to the target name and to the list of
dependencies. See the section MACROS for details.
Commands may be Python functions. Then they are just called. Look at
section COMMAND FUNCTIONS for a description of the parameters to give.
Phony targets
Sometimes not actually a program or document has to be created, but
just a couple of jobs have to be put together. Therefore Phony targets
were designed. When a rule is declared to be a Phony target, no file
of that name is searched; the target and its dependencies are just
built allways. An example:
Rule( Phony, ’all’, ’myprog myprog.1’, None)
Given ‘None’ as command means that nothing will be executed for this
target itself, but both the program ‘myprog’ and its manpage ‘myprog.1’
are checked, and built if necessary.
Suffix rules
Rules cannot only be defined for a singe file, but as well for a whole
class, specified by an extension. So you can say:
Suffix( File, ’.o’, ’.c’, ’${CC} -o $@ -c $<’)
Whenever a file with extension ‘.o’ is to be built and there is no
explicit rule, the extension is replaced by ‘.c’ and the command is
executed with the appropriate macro expansions.
Suffix rules may depend from single files, too. Put an equal sign
("=") in front of the file name.
Suffix( File, ’.dvi’, ’.tex =myfmt.fmt’,
’tex --fmt myfmt $<’)
Of course, this only makes sense if you have a rule or suffix rule to
build ‘myfmt.fmt’.
You can also use suffix rules to find automatically the dependencies of
header files. See the section PREREQUISITE DETECTION for an
explanation.
The initial dots in ‘.o’ and ‘.c’ are optional and may be omitted. If
a single dot (‘.’) is specified for a prerequisite file built from the
target name stripped off its extension, this dot doesn’t appear in the
filename composed. You may even specify a single dot in order to
indicate there is a prerequise at all and it’s still stripped. Give
‘None’ if you don’t want any prerequise to be present. This is
admittedly a litte bit paradox.
MACROS
Expansion
Macro expansion applies to that of Make. Variable names longer than
one character have to be put into parenthesis or into braces.
$(VAR) VAR expansion
${VAR} VAR expansion (alternate form)
$V single character name
$$ $ (dollar sign)
Order
Macros are expanded as late as possible. That is, if you define in
your bakefile
Var( ’PREFIX’, ’/usr/local’)
Var( ’MANDIR’, ’${PREFIX}/man’)
the macro ‘${MANDIR}’ normally will expand to ‘/usr/local/man’. As
soon as you write
$ bake PREFIX=/usr/share install
the target ‘install’ will be built with ‘${MANDIR}’ expanding to
‘/usr/share/man’, as ‘MANDIR’ itself actually still means
‘${PREFIX}/man’.
Built-in macros
The ‘$’ Macros apply to those of Make as well, but there are some
additional ones. Especially, ‘&’ means all dependencies, not only the
newer ones. There are as well macros yielding the number of
dependencies and of newer targets. The full list is:
@ target name
* target basename (without extension)
& dependencies
# number of dependencies
< newer dependencies
= number of newer dependencies
Slicing
Variables may be sliced Python-like. A colon-separated list will be
split, and there evaluate:
${PATH[0]} first directory in path
${PATH[-1]} last directory in path
${PATH[1:]} all directories in path but the first
The built-in macros that are lists (dependencies and newer
dependencies) may be indexed as well:
${&[0]} the first prerequisite
If you find a case where it makes sense to index newer dependencies,
please let me know.
COMMAND FUNCTIONS
Simple build functions
Commands may be specified as Python functions. Then, they are called
directly.
def buildhp( name, macros):
import myhomepage
myhomepage.buildit()
Rule( File, ’index.html’, None, buildhp)
The function handed over has to take two arguments. The first is the
name of the target to be built. The second is a function returning the
macro expansions. For example, if you want to find the newer
dependencies, you have to call
newer = macros( ’<’)
See the UTILITIES section for further examples. You will find there
some handy functions you probably now might think of.
Calling shell commands
If your function will call shell commands itself, you should use the
same interface as is used internally. Then macro expansion will take
place and the no-act option as well as the verbositiy levels will be
redeemed. You should hand over the macro expansion function you
retrieved from Bake.
def compressit( name, macros):
Command( ’gzip -${SPEED} $@’, macros).system()
Rule( File, ’myprog.1.gz’, None, compressit)
If the command is returning with an exit code other than 0, an
exception is raised. You may catch and examine it.
try:
Command( ’diff -q $@ $@.prev’, macros).system()
except ExitCode, e:
if e.code == 1:
print ’Different from previous.’
To process the commands output call its ‘popen’ function.
headerdeps = Command( ’cpp -MM $@’, macros).popen()
(Don’t do that; to resolve C header dependencies, there is provided a
special solution. See the section below.)
PREREQUISITE DETECTION
Like commands may be Python functions, the prerequisites field may be a
function, too. If it is, the dependencies will be the list this
function returns. The function has to take the same arguments as the
build command functions.
def buildhp( name, macros):
import myhomepage
myhomepage.buildit()
def sourceshp( name, macros):
import myhomepage
return myhomepage.findsources()
Rule( File, ’index.html’, sourceshp, buildhp)
C preprocessing
You don’t need to worry about how to process the output of the C
preprocessors ‘-MM’ option. Bake defines a class that does this for
you. You just have to specify your include directories (‘cpp’ needs
them) and hand over the objects ‘findheaders’ bound method. As there
is no compile command to execute at this state, the commands argument
is omitted.
cpp = Cpp( ’incdir otherdir’)
Suffix( File, ’c’, cpp.findheaders)
or, shortly,
Suffix(
File,
’.c’,
Cpp( ’${MYLIB}’).findheaders)
As you see, macros are expanded.
SUBORDINATE INSTANCES
You might be temptated to specify a command like
Rule( Phony, ’examples’, ’’,
cmds = ’cd examples ; bake all’)
Although this will work, there is a better way to do it. You don’t
need to load the interpreter twice. There is a class ‘Subbake’ that
preserves the initial state and recovers it after the job is finished.
The command function might look like this:
def buildexamples( name, macros):
Subbake().main( macros, ’-Cexamples’, ’all’)
Rule( Phony, ’examples’, ’’, buildexamples)
You can use macros in the command line. In the following example the
subdirectory is entered and the same target (‘clean’) is built there.
def doexamples( name, macros):
Subbake().main( macros, ’-Cexamples’, "$@")
Rule( Phony, ’clean’, ’’, [ ’rm -fv *.o’, doexamples])
Of course, the string ‘"$@"’ could have been written as ‘name’.
UTILITIES
The C preprocessor header files detection mechanism is described in the
PREREQUISITE DETECTION section. Here now are some other handy tools
for bakefile design. If you want to propose or contribute another one,
please write me.
Changing the Directory
If you temporarily want to change to another working directory and if
you want to be sure to get back in any case, use the ‘ChDir’ class. It
use is very simple.
def builddocs( name, macros):
docdir = ChDir( ’doc’, macros)
# do something in doc
When the function is left, the ‘docdir’ instance is destroyed. Then,
it will automatically change to the directory where you were before the
object was instantiated.
The ‘macros’ parameter may be omitted.
Extensions
If you’re building filenames by adding or splitting off extensions, you
may use the functions that Bake uses internally.
path, name, ext = Ext.split( ’mydir/myfile.py’)
full = Ext.join( ’path’, ’to’, ’prog’, ’c’)
In the join function, the last two arguments are supposed to be name
and extension, the preceding ones are used to build a path.
Line joining
Joining lines may be done with a very simple function call. Just type
cmds = joinlines( [ ’rm -f *~’, ’rm -f \\#*#’])
to build one multiline string. This function claims to be understood
as the opposite of strings ‘splitlines’.
Archiving
Bake provides a way to convieniently archive your today work. It packs
all contents of a directory including all subdirectories into a tar or
into a zip archive.
def archive( name, macros):
a = Archive()
a.targzall()
a.zipall()
Rule( Phony, ’archive’, ’wipe’, archive)
This builds both a tar and a zip archive in the parent diectory of the
current. It is given the same name as the current directory, added the
extensions ‘.tar.gz’ and ‘.zip’ respectively. For example, if the
directory you’re in is ‘/home/user/myproj-1.7’, your tar archive file
will be ‘/home/user/myproj-1.7.tar.gz’.
Of course, you may specify some options. By default, the file names in
the archive are prefixed with the directories name. A file ‘main.c’
will appear as ‘myproj-1.7/main.c’. You may turn off this by
specifying ‘prefix = False’.
def archive( name, macros):
a = Archive( prefix = False, fakeroot = True)
a.targzall( to = ’~/save’)
In this example, further the files in the tar archive will have user
and group owner ‘root’. The archive won’t be saved in the parent
directory but in the users preferred archiving directory ‘save’ that
will be created if it doesn’t exist. If ‘to’ is not an absolute path,
it will be meant relatively to the current parent directory.
The archiver may be told to give the tar or zip file another name in
two ways.
a = Archive( ’savedwork’)
a = Archive( name = ’savedwork’)
or
a = Archive()
a.name += time.strftime( ’-%Y-%m-%d’)
In the second example, the name of the archive that will be built may
be ‘myproj-1.7-2004-09-23.tar.gz’.
You may create both a tar and a zip archive at one time calling the
‘bothall’ function.
def archive( name, macros):
a = Archive()
a.bothall()
Installing
Instead of writing long installation routines you may use Bake’s
installer feature. Just define a class that returns what its files to
install are.
class MyProjInst( Installer):
def execfiles( self):
return { ’${BINDIR}’: ’myproj’,
’${MANDIR}/man1’: ’myproj.1’}
def conffiles( self):
return { ’/etc’: ’myprojrc’}
mi = MyProjInst()
mi.stdrules()
The ‘stdrules’ function defines five rules as if you would have written
Rule( Phony, ’check’, self.prereqs, self.check)
Rule( Phony, ’install’, self.prereqs, self.install)
Rule( Phony, ’remove’, self.prereqs, self.remove)
Rule( Phony, ’purge’, self.prereqs, self.purge)
Rule( Phony, ’package’, self.prereqs, self.package)
The difference between ‘remove’ and ‘purge’ is that in the latter case
the configuration files are deleted, too. The ‘install’ target doesn’t
overwrite existing config files but creates ones with an extension
‘.bake-new’ instead.
The ‘package’ target doesn’t install any file at all; it builds a
directory named ‘package’ (the target name) containing the files that
are necessary for building a package. You don’t need to be root, all
users and groups with id >= 500 are changed to root.
Further, you may specify installation scripts to be executed before or
after installation/removal.
class MyProjInst( Installer):
# ...
def postinst( self):
return ’python compileall.py ${MODDIR}’
This way, you may specify the functions ‘preinst’, ‘postinst’, ‘prerm’
and ‘postrm’ resulting in a script being called at the appropriate
time. If you create a package, the scripts are written to the package
directory and they are given the same names as the functions.
COMMON TRICKS
Password entering
As dependent targets are built in the order they’re specified, here the
password will be requested before the homepage is built.
site = ’ftp.nny.xx’
user = ’fry’
passwd = None
def getpasswd( name, macros):
import getpass
global passwd
passwd = getpass.getpass(
’Password for %s on %s:’ % (user, site))
def upload( name, macros):
import ftplib
f = ftplib.FTP( site)
f.login( user, passwd)
# ...
f.quit()
Rule( Phony, ’passwd’, ’’, getpasswd)
Rule( Phony, ’upload’, ’passwd index.html’, upload)
This makes it convenient to start an upload procedure. You may first
enter the password instead of waiting until the homepage is built and
then entering it so that the process can continue with uploading the
files.
Caution with cleanup
Be sure you do not build new files during a cleanup. For example, in
the following code the order in that the commands are specified makes a
slight difference.
def removedeps( name, macros):
import genhp
genhp.removeall()
Rule( Phony, ’clean’, [
removedeps, ’rm -f *.pyc’])
As the function ‘removedeps’ imports a module, it will produce a file
‘genhp.pyc’. This is removed by the second command. If the commands
were given in reverse order, the temporary file ‘genhp.pyc’ would
survive the procedure.
CONFIGURATION SCRIPTS
Bake doesn’t need to be preceded by configuration scripts. As
bakefiles are Python code, you may specify detection functions. You
are encouraged to put them at the end of the bakefile, but you don’t
need to do so.
Var( ’CC’, find_cc())
Here, ‘find_cc’ should be a function previously defined, returning a
string containing the systems preferred C compiler.
A more complex implementation reflecting the operating system your
project is compiled on would be:
def createosh( name, macros):
f = open( ’osdef.h’, ’w’)
if sys.platform == ’win32’:
print >>f, ’#define THANK_YOU_FREDDIE’
Rule( File, ’osdef.h’, ’’, createosh)
A header file will only be created if it doesn’t exist. As it is part
of a rule and it will be built on any request of ‘osdef.h’, it should
be mentioned within a ‘clean’ or ‘wipe’ target.
SEE ALSO
bake(1)
Python Documentation: http://www.python.org/doc/
AUTHOR
(C) 2004 Bertram Scharpf <software@bertram-scharpf.de>