18.2. Code Generator Example

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;
}