Recipe Examples
These are examples for what can be done with A-A-P recipes.
This is not intended to teach you writing recipes, only a few things are
explained. See the Aap manual for a verbose explanation.
The tutorial is a good place to start learning Aap, it contains plenty of
examples that are explained.
Building and installing Exuberant Ctags
Exuberant ctags (ctags for short) is a nice tool for generating a tags file
from source files in various languages. In many editors the tags file can
be used to jump to where a function is defined, for example.
It is portable to many different systems.
The original ctags distribution uses a dozen makefiles to build the program on
different systems. In this example is shown how most of it can be done with
just one recipe.
The main.aap recipe can be found
here.
You might want to open it in a separate window, so that you can look through
it while reading the comments below.
More info about Exuberant Ctags can be found
here.
Installing the package
Before explaining how the interesting parts of the recipe work, let's see how
it can be executed. The recipe has been made available in the Aap package
system. This means you can do it all with just one command:
aap --install ctags
On many systems this will obtain the needed files and install ctags for you.
However, there are a few situations when this fails:
- One of the files cannot be downloaded. The SourceForge CVS server has
been unreliable. You might want to try again later.
- Not all systems are supported yet. In case you see the building fail and
you have some idea how to fix it, please send us the fix, so that it will work
for others.
- On FreeBSD there already is a ctags command. When chosing to use the
ctags port it will install the program as "exctags" instead of "ctags".
- Lots of text scrolls by and you can't see what happened. Try editing the
file $HOME/.aap/packages/ctags/AAPDIR/log (on non-Unix systems use "aap"
instead of ".aap"). It should contain lots of messages that Aap produced
while attempting to install ctags for you.
Building
The recipe is quite long, because it supports much more than building the
program. The simple instructions for building the program are these:
Sources = args.c asm.c asp.c awk.c beta.c c.c cobol.c eiffel.c entry.c
erlang.c fortran.c get.c html.c jscript.c keyword.c lisp.c
lregex.c lua.c main.c make.c options.c parse.c pascal.c
perl.c php.c python.c read.c rexx.c routines.c ruby.c
scheme.c sh.c slang.c sml.c sort.c sql.c strlist.c tcl.c
verilog.c vim.c yacc.c vstring.c
CtagsProg = ctags$EXESUF
ProgName = $CtagsProg
:program $ProgName : $Sources
|
The $Sources variable is set to the list of source files used for the program.
Note that no quotes or backslashes are used. It is easy to reformat the list
of names without having to worry about special characters. It is also not
necessary to make a list of object files, this is handled automatically by
Aap.
The use of $CtagsProg and $ProgName to inderectly specify the name of the
executable is not really needed here, but they are used in the recipe for
other reasons.
Some variable names are all capitals, while others also use lower case
characters. Why this difference? The all-caps variables are the ones that
have a special meaning to Aap. $EXESUF, for example, is set by Aap to the
suffix of executable programs. On Unix it is empty, on MS-Windows it is
".exe". To avoid trouble with using a variable that has a special meaning for
Aap, lower case letters are used for normal variables.
The Aap command ":program" specifies from which source files the ctags program
is build. Aap then knows enough to support:
aap | | build the
program |
aap install | | install the
program |
aap uninstall | | uninstall the
program |
aap clean | | cleanup most
files |
aap cleanmore | | cleanup all
generated files |
Configuration
The ctags sources already come with a configure script that is used on Unix
(-like) systems to adjust to the specific properties of the system.
When this example was written Aap didn't have a configuration feature yet,
therefore the existing configure script is used. But instead of generating a
Makefile, an Aap recipe is generated.
The configure script uses "Makefile.in" to produce "Makefile". But we want it
to use "config.aap.in" and produce "config.aap". This part of the recipe
takes care of it:
# Filter the configure script created by autoconf to generate config.aap
# instead of Makefile. This means we can use the unmodified configure.in
# distributed with ctags.
configure_aap : configure
:cat configure
| :eval re.sub("Makefile", "config.aap", stdin)
>! configure_aap
:chmod 755 configure_aap
|
The "configure" file is filtered and written as "configure_aap". This shell
script is then executed to do the configuration. The filtering is done in
three steps:
- The ":cat configure" command reads the "configure" file and writes it
through a pipe to the next command.
- The ":eval" command evaluates a Python expression, where "stdin" stands
for the text that is written through the pipe. The Python function "re.sub()"
substitutes the string "Makefile" with "config.aap". This also works to
change "Makefile.in" to "config.aap.in".
- The result is written into configure_aap.
After executing the filtered shell script the generated "config.aap" recipe is
moved into $BDIR, so that another one is generated for other systems (using
the same files over a network). The recipe is then finally included:
:include $BDIR/config.aap
|
The compilation commands must be changed to specify that the generated
configuration header file "$BDIR/config.h" is to be used. These commands take
care of this:
INCLUDE += -I$BDIR
DEFINE += -DHAVE_CONFIG_H
|
$INCLUDE and $DEFINE are standard variables that are used by the compile
actions. Thus besides adding a value to these variables, nothing else needs
to be done to make them used for the compilation.
The configure script is included with the distribution. Thus normally you
don't have to generate it. But someone might need to correct the
configuration method. This is done by editing "configure.in" and running the
"autoconf" program. To be able to avoid running "autoconf" for most people
this code is used:
# Run autoconf when needed, but avoid doing this always, not everybody has
# autoconf installed. Include "mysign" in the distribution, it stores the
# signature of the distributed configure script.
configure {signfile = mysign} : configure.in
@if not program_path("autoconf"):
:print Can't find autoconf, using existing configure script.
@else:
:sys autoconf
|
The clever part is "{signfile = mysign}". This specifies a "signfile"
attribute for the dependency. This means the signature for this dependency
isn't stored in the usual way, but in the specified "mysign" file. This file
is then included in the distribution. When Aap comes to this dependency and
checks the signatures to find out if the command to build "configure" need to
be executed, it will read the distributed signatures from "mysign". If both
"configure" and "configure.in" are still the same as when they were
distributed, the build commands will not be executed.
The build commands include an extra check if the "autoconf" program can be
found. It is not installed on every system. A choice was made to skip
running "autoconf" when it is not available. Another possibility would be to
use ":assertpkg autoconf" to install the autoconf program when needed.
Variants
The recipe can also be used to build a debugging version of ctags.
Aap has the ":variant" command to make this easy:
:variant DEBUG
no
ProgName = $CtagsProg
OPTIMIZE ?= 2
DEBUG = no
UNINSTALL_EXEC += $DCtagsProg # also uninstall dctags
CLEANFILES += $DCtagsProg
yes
ProgName = $DCtagsProg
Sources += debug.c
DEFINE += -DDEBUG
CPPFLAGS = -g
OPTIMIZE ?= 0
UNINSTALL_EXEC += $CtagsProg # also uninstall ctags
CLEANFILES += $CtagsProg
|
The non-debug variant is build by default, since it is the first alternative
below ":variant". To build the debug version use "aap DEBUG=yes".
The resulting object files will be stored in different build directories, so
that you can switch between the two variants without having to recompile all
files.
The standard variables $OPTIMIZE and $DEBUG are used to tell the compile
action what compilation options to use. $OPTIMIZE is a number ranging from
zero (no optimizing) to nine (lots of optimizing). $DEBUG is "yes" for
debugging and "no" for not debugging.
Two extra assignments are used to cause "aap clean" and "aap uninstall" to
also delete the files build for the variant that was not selected.
$UNINSTALL_EXEC is the list of executables that are to be uninstalled.
$CLEANFILES is the list of files to be cleaned up. The files normally
generated for a variant do not need to be specified, these are added
automatically when the ":program" command is used..
Installing etags
Etags is a program like ctags, but it produces a TAGS file with a different
format. This is only useful for backwards compatibility.
Ctags actually works like Etags by either giving the "-e" argument or by
calling the executable "etags". For Unix this is done by making a symbolic
link from "etags" to "tags". This code in the recipe takes care of it:
install-local:
@if osname() == "posix":
:cd $DESTDIR $PREFIX $EXECDIR
:symlink {force} $ProgName $EtagsProg
uninstall-local:
@if osname() == "posix":
:cd $DESTDIR $PREFIX $EXECDIR
:del {force} $EtagsProg
|
The ":cd" command used here has three arguments. These are concatenated,
adding path separators where needed, to form the name of the new directory.
The ":symlink" command works like the Unix command "ln -s". The {force}
attribute is used to overwrite any existing file or link.
The "install-local" and "uninstall-local" targets are used by the default
install dependencies if they exist. This means you can easily add special
commands for installing, like we do here, without having to write the whole
"install" and "uninstall" targets.
Maintainer Functionality
The maintainer of ctags has a few tasks that normal users do not need.
Therefore this has been moved to a separated recipe that is included only when
needed:
@if has_targetarg("tags TAGS tar tarclean revise cvstag"):
:include maintainer.aap {fetch = http://www.a-a-p.org/packages/ctags_maintainer.aap}
|
The "has_targetarg()" function is used to check if one of the targets that
requires the maintainer.aap recipe has been used. When one of these targets
is present then the recipe will be downloaded from the specified URL.
You can find the maintainer.aap recipe
here.
Much of this recipe is listing all files that have to be distributed. This is
not spectacular. More interesting is the method used to avoid having the
version number in more than one place. This means that updating the version
number only needs to be done once. Since the number is also needed in the
recipe, it is obtained with this code:
@r = re.compile('.*PROGRAM_VERSION "(\\d[^"]*)".*', re.DOTALL)
:cat ctags.h | :eval r.match(stdin).group(1) | :assign Version
|
This compact code mixes Python and Aap commands:
- Compile a pattern that matches the line in ctags.h where the version is
defined. A group in parentheses is used around the place where the version
number is matched. The resulting object is assigned to the "r" variable.
- The ":cat" command reads the "ctags.h" file and pipes it to the next
command.
- The ":eval" command uses the compiled pattern object to match with the
whole file contents, found in the "stdin" variable. This results in a string
with just the version number, which is piped to the next command.
- The ":assign" command reads the piped version number and assigns it to the
$Version variable.
The version number is then used in the tar file that contains all the
distributed files:
TarFile = ctags-$(Version).tar.gz
CLEANFILES += $TarFile
tar {virtual} {comment = Create tar file with all distributed files}:
# Make sure we have a tar command.
:assertpkg tar
# Copy the files to a new directory, so that the archive unpacks nicely.
TarDir = ctags-$Version
:del {f}{q}{r} $TarDir
:mkdir $TarDir
:copy {quiet} $DISTFILES $TarDir
:sys tar cfz $TarFile $TarDir
:del {f}{q}{r} $TarDir
|
Most of this is easy to understand. The $DISTFILES variable contains the list
of files to distribute. This is partly filled by the ":program" command in
the main recipe, the compiled source files are added automatically.
The ":assertpkg" command is used to check that the "tar" program is available.
If it isn't then Aap will offer you to install. This uses the Aap package
mechanism.
The following code specifies which files are to be stored in the CVS server
and how it's done.
# The "commit" attribute specifies CVS is used for "aap fetch" and "aap commit".
# The server isn't specified, use the default.
:attr {commit = cvs://} $DISTFILES
# Commit all changes into CVS. Also deletes files!
revise:
:reviseall {logentry = updated for version $Version}
# Tag the current set of files as a specific version.
cvstag:
:tagall {tag = ctags-`string.replace(_no.Version, ".", "_")`}
|
The "commit" attribute is set to use CVS for version control.
This doesn't specify a CVS server to use, which means that the default server
will be used (CVS remembers the one used before). Now the this command can be
used to update the files in CVS:
aap revise
Note that files that are omitted from $DISTFILES will be deleted from CVS,
thus it is important that the list of files is correct. Well, it should be
correct anyway, since the same list is used to create the tar file.
When a version is to be released the "aap cvstag" command can be used to
update the tags on the files in CVS. Normally the "tag" target is used for
this, but that is rather confusing in this context.
top
|