man bakefile (Formats) - Bake description file
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
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 neccessary.
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 expansion VAR expansion (alternate form) 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 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
Slicing
Variables may be sliced Python-like. A colon-separated list will be split, and there evaluate: first directory in path last directory in path all directories in path but the first The built-in macros that are lists (dependencies and newer dependencies) may be indexed as well: 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 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
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
AUTHOR
(C) 2004 Bertram Scharpf <software@bertram-scharpf.de>