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 and is made
available through that build item's Rules.mk
file.
First, we'll look at the code generator itself. Notice that the
only abuild files contained in the
derived-code-generator build item
directory at user/derived/code-generator are
Abuild.conf and
Rules.mk. There is no
Abuild.interface or
Abuild.mk, though these files could exist if
they were needed.
The important part of this is Rules.mk, so
we'll study it carefully. Here is the
Rules.mk file:
general/user/derived/code-generator/Rules.mk
# Our rules-help target will be run when the user runs abuild
# rules-help if they include this build item in BUILD_ITEM_RULES.
rules-help::
@echo
@echo "** Help for users of BUILD_ITEM_RULES=derived-code-generator **"
@echo
@echo "You must set these variables:"
@echo " DERIVED_CODEGEN_SRC -- name of a C source file to generate"
@echo " DERIVED_CODEGEN_HDR -- name of a C header file to generate"
@echo " DERIVED_CODEGEN_INFILE -- a file containing a numerical value"
@echo
@echo "These rules will generate a source file called"
@echo "DERIVED_CODEGEN_SRC which will contain a function called"
@echo "getNumber(). That function will return the number whose"
@echo "value you have placed in the file named in"
@echo "DERIVED_CODEGEN_INFILE. The file DERIVED_CODEGEN_HDR will"
@echo "contain a prototype for the function."
@echo
@echo "You must include DERIVED_CODEGEN_SRC in the list of sources"
@echo "for one of your build targets in order to have it included in"
@echo "that target."
@echo
# 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)
clean::
$(RM) $(DERIVED_CODEGEN_SRC) $(DERIVED_CODEGEN_HDR)
The first thing we do here is to define a
rules-help target that prints some help
information about using the rules. By placing the name of this
build item (derived-code-generator) in
your BUILD_ITEM_RULES variable, you will see
this output when you run abuild rules-help.
When writing rules help, please remember that the strings you are
echoing are subject to make's ordinary
variable substitution rules. It's best to actually test by
running abuild rules-help to make sure that it
echoes properly. Alternatively, you can have your
rules-help target display the contents of a
file in your build item.
The next 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,
and that we don't see the output at all when we're running with
--silent. All informational messages should be
generated this way. (For the rules-help
target, we use @echo directly since it doesn't
make sense to suppress that output even when running with
--silent.) 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!) Finally, we add a
clean rule that removes our automatically
generated files. Note that the clean rule is
only run when the user invokes abuild from the output
directory. For this reason, adding a clean
target is optional. For more detail on this, please see Section 29.2, “Starting Abuild in an Output Directory”.
To use this code generator from
derived-main.src, all we have to do is
define the required variables in Abuild.mk,
and add derived-code-generator as a
dependency in Abuild.conf and to the
BUILD_ITEM_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
this: derived-main.src platform-types: native parent-dir: .. 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 BUILD_ITEM_RULES := derived-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;
}