The derived-code-generator
build item in
doc/example/general/user/derived/code-generator
contains a trivial code generator. All it does is generate a
source and header file that define a function that returns a
constant, which is read from a file. We have modified
derived-main
to use the code generator.
The code generator itself is implemented in the
derived-codegen
build item, which provides
a rule set called code-generator
. The build
item is making these rules available for build items of type
object-code. The file that implements the rules is
therefore called
rules/object-code/code-generator.mk
.
First, we'll look at the code generator itself. Notice that the
only abuild file contained in the
derived-code-generator
build item
directory at user/derived/code-generator
is
Abuild.conf
. There is no
Abuild.interface
or
Abuild.mk
, though these files could exist if
the build item itself had something that it needed to build.
Before we get to the rules themselves, observe that there is
another file in rules/object-code
: the help
file,
rules/object-code/code-generator-help.txt
,
the contents of which are presented here:
general/user/derived/code-generator/rules/object-code/code-generator-help.txt
You must set these variables: DERIVED_CODEGEN_SRC -- name of a C source file to generate DERIVED_CODEGEN_HDR -- name of a C header file to generate DERIVED_CODEGEN_INFILE -- a file containing a numerical value These rules will generate a source file called DERIVED_CODEGEN_SRC which will contain a function called getNumber(). That function will return the number whose value you have placed in the file named in DERIVED_CODEGEN_INFILE. The file DERIVED_CODEGEN_HDR will contain a prototype for the function. You must include DERIVED_CODEGEN_SRC in the list of sources for one of your build targets in order to have it included in that target.
Whenever you provide a rule implementation for rule
rulename
, you should create a
file in the same directory. This is the file that will be
displayed when the user types abuild --help rules
rule:rulename
-help.txtrulename
.
The most important part of this example is the rule
implementation file, so we'll study it carefully. Here is the
rules/object-code/code-generator.mk
file:
general/user/derived/code-generator/rules/object-code/code-generator.mk
# Make sure that the user has provided values for all variables _UNDEFINED := $(call undefined_vars,\ DERIVED_CODEGEN_HDR \ DERIVED_CODEGEN_HDR \ DERIVED_CODEGEN_INFILE) ifneq ($(words $(_UNDEFINED)),0) $(error The following variables are undefined: $(_UNDEFINED)) endif all:: $(DERIVED_CODEGEN_HDR) $(DERIVED_CODEGEN_SRC) $(DERIVED_CODEGEN_SRC) $(DERIVED_CODEGEN_HDR): $(DERIVED_CODEGEN_INFILE) \ $(abDIR_derived-code-generator)/gen_code @$(PRINT) Generating $(DERIVED_CODEGEN_HDR) \ and $(DERIVED_CODEGEN_SRC) perl $(abDIR_derived-code-generator)/gen_code \ $(DERIVED_CODEGEN_HDR) \ $(DERIVED_CODEGEN_SRC) \ $(SRCDIR)/$(DERIVED_CODEGEN_INFILE)
The first thing that happens is that we have some code that checks
for undefined variables. This isn't strictly necessary, but it
can save your users a lot of trouble if you detect when variables
they are supposed to provide are not there. We use the function
undefined_vars
to do this check. This
function is provided by abuild and appears in the file
make/global.mk
relative to the top of the
abuild installation. If you expect to write a lot of make
rules, it will be in your best interest to familiarize yourself
with the functions offered by this file. Even if we hadn't done
this, abuild invokes GNU Make in a manner that causes it to
warn about any undefined variables. This is useful because
undefined variables are a common cause of makefile errors.
Then we add our source file to the all:: target to make sure it gets built. Note that we use all::, not all:, since there are multiple all:: targets throughout various rules files. Adding our source files to the all:: target is not strictly necessary in this case since, by listing the source file as a source file for one of our targets (in the build item that uses this code generator), it will be built in the proper sequence. It's still good practice to do this though, but remember that in a parallel build, dependencies of the all:: target may not necessarily be built in the order in which they are listed.
Next, we have the actual make rules
that create the automatically generated files. In this case, we
make the output files depend on both the input file and the
code generator. Although abuild is running the rules from the
depending build item's output directory, we don't need to put any
prefix in front of the name of the input file: abuild sets
make's VPATH
variable so that the file can be found. By creating a dependency
on the code generator as well, we can be sure that if the code
generator itself is modified, any code that it generates will
also be updated.
In the commands for generating our files, notice that we don't
need an @
sign before the generation command
to prevent it from echoing since abuild doesn't echo its output
by default. Not putting an @
sign there means
that the user will see the command if he/she runs abuild with
the --verbose
option. So that the user knows
something is happening, we generate a useful output message using
@$(PRINT)
. The use of
@$(PRINT)
ensures that we don't see the actual
echo command even when running with --verbose
(since otherwise, we'd see the echo command
immediately followed by the output of the echo
command), and that we don't see the output at all when we're
running with --silent
. All informational
messages should be generated this way. Note also that we invoke
the code generator with perl rather than
assuming that it is executable (since some version control
systems or file systems make it hard to preserve execute
permissions) and that we specify the path to the code generator
in terms of $(abDIR_derived-code-generator)
.
Also notice that we have to prefix the name of the input file
with $(SRCDIR)
when we pass it to the code
generator since we are running from the abuild output
directory. The VPATH
variable tells
make where to look, but it doesn't
tell our code generator anything! However, the special GNU Make
variables like $<
and $^
will contain the full paths to prerequisites and can often be
used instead.
To use this code generator from
derived-main.src
, all we have to do is
define the required variables in Abuild.mk
,
add derived-code-generator
as a dependency
in Abuild.conf
, and add
code-generator
to the
RULES
variable in
Abuild.mk
. Note that we have modified
main.cpp
to include
auto.h
and to call
getNumber()
, thus testing the use of the code
generator. Since this application effectively contains the only
test suite for the code generator, we declare it as a tester of
the code generator in Abuild.conf
using
traits. Here are the relevant files from
derived-main.src
:
general/user/derived/main/src/Abuild.conf
name: derived-main.src platform-types: native deps: common-lib2 project-lib derived-code-generator world-peace traits: tester -item=derived-main.src -item=derived-code-generator
general/user/derived/main/src/Abuild.mk
TARGETS_bin := main DERIVED_CODEGEN_SRC := auto.c DERIVED_CODEGEN_HDR := auto.h DERIVED_CODEGEN_INFILE := number SRCS_bin_main := main.cpp $(DERIVED_CODEGEN_SRC) RULES := ccxx code-generator
Here is the modified main.cpp
file:
general/user/derived/main/src/main.cpp
#include <ProjectLib.hpp> #include <CommonLib2.hpp> #include <iostream> #include <world_peace.hh> #include "auto.h" int main(int argc, char* argv[]) { std::cout << "This is derived-main." << std::endl; ProjectLib l; l.hello(); CommonLib2 cl2(6); cl2.talkAbout(); cl2.count(); std::cout << "Number is " << getNumber() << "." << std::endl; // We don't have to know or care whether this is the stub // implementation or the real implementation. create_world_peace(); return 0; }