|
|
BAKEFILE
NAME
DESCRIPTION
VARIABLES
RULES
MACROS
COMMAND FUNCTIONS
PREREQUISITE DETECTION
SUBORDINATE INSTANCES
UTILITIES
COMMON TRICKS
CONFIGURATION SCRIPTS
SEE ALSO
AUTHOR
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) assigments, 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
|
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.
|
|
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
neccessary.
|
|
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
|
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)
|
|
|
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’.
|
|
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 depedencies and of newer targets. The full list
is: |
|
@
|
|
target name
|
|
|
*
|
|
target basename (without extension)
|
|
|
&
|
|
dependencies
|
|
|
#
|
|
number of dependencies
|
|
|
<
|
|
newer dependencies
|
|
|
=
|
|
number of newer dependencies
|
|
|
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
|
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.
|
|
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)
|
|
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.
|
|
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.
|
|
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.
|
|
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’.
|
|
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()
|
|
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 neccessary 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
|
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.
|
|
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>
|
<previous next>
|