29.5. Plugin Examples

In this section, we present examples of using abuild's plugin facility. The examples here illustrate all of the capabilities of abuild's plugin system, albeit with simplistic cases. Plugins are a very powerful feature that can be used to do things that you could not otherwise do with abuild. If you are not careful, they can also create situations that violate some of abuild's design principles, so plugins should be used with particular care. You should also be careful not to overuse plugins. Many things you may consider implementing as a plugin would be better implemented as an ordinary build item with rules or hooks. Plugins should be used only for adding capabilities that can't be added without plugins or that should apply broadly and transparently across many items in the build tree.

Abuild enforces that no plugin may have dependencies or be declared as a dependency of another build item. Still, it's good practice to name plugins by placing them in a private namespace. This prevents build trees that may have access to these items (but may not presently declare them as plugins) from declaring them as dependencies. In these examples, we always place our plugins in the plugin namespace by starting their names with plugin. even though we have no actual plugin build item. In order to use the plugins in this tree, we have to declare them as plugins in the root build item's Abuild.conf:

plugin/Abuild.conf

tree-name: plugin
child-dirs: plugins java other outside echo
plugins: plugin.echoer plugin.printer plugin.counter

29.5.1. Plugins with Rules and Interfaces

Here we examine the plugin.counter plugin, which can be found in doc/example/plugin/plugins/counter. This is a trivial plugin that illustrates use of an interface file and also creates a custom rule that can be referenced in the RULES variable of a build item's Abuild.mk file. There's nothing special about the plugin's Abuild.conf file:

plugin/plugins/counter/Abuild.conf

name: plugin.counter

The plugin.interface file declares a new interface variable called TO_COUNT which contains a list of file names:

plugin/plugins/counter/plugin.interface

declare TO_COUNT list filename append

This file gets loaded automatically before any regular build items' Abuild.interface files. The file count.mk in the rules/all directory is the file that a build item may include by placing RULES := count in its Abuild.mk file:

plugin/plugins/counter/rules/all/count.mk

all:: count

# Make sure the user has asked for things to count.
ifeq ($(words $(TO_COUNT)), 0)
$(error plugin.counter: TO_COUNT is empty)
endif

# Use echo `wc` to normalize whitespace
count:
        for i in $(TO_COUNT); do echo `wc -l $$i`; done

If a build item includes count in the value of its RULES variable, then any files listed in TO_COUNT will have their lines counted with wc -l when the user runs abuild with the count target. The intention here is that items that the target build item depends on would add files to TO_COUNT in their Abuild.interface files. Then the build item that actually uses the count rule would display the line counts of all of the files named by its dependencies.

This is admittedly a contrived example, but it illustrates an important point. Here we are adding some functionality that enables a build item to make use of certain information provided by its dependencies through their Abuild.interface files. Although we could certainly add the count target using a normal build item that users would depend on, doing it that way would be somewhat more difficult because each item that wanted to add to TO_COUNT would also have to depend on something that declares the TO_COUNT interface variable. By using a plugin, we cause the plugin's plugin.interface to be automatically loaded by all build items in the build tree. That way, any build item can add to TO_COUNT without having to take any other special actions. This type of facility could be particularly useful for adding support to abuild for other programming languages that require other information to be known from its dependencies.

For an example of a build item that uses this plugin's capabilities, see the build items under doc/example/plugin/other/indep. Here we have the build item indep-a in the a directory that adds a file to TO_COUNT in its Abuild.interface:

plugin/other/indep/a/Abuild.conf

name: indep-a
platform-types: indep

plugin/other/indep/a/Abuild.interface

TO_COUNT = a-file

We also have the build item indep-b (which depends on indep-a) in the b directory that uses the count rule in its RULES variable in its Abuild.mk file:

plugin/other/indep/b/Abuild.conf

name: indep-b
platform-types: indep
deps: indep-a

plugin/other/indep/b/Abuild.mk

RULES := count

Here is the output of running abuild count from the plugin/other/b directory:

count-b.out

abuild: build starting
abuild: indep-b (abuild-indep): count
make: Entering directory `--topdir--/plugin/other/indep/b/abuild-indep'
10 ../../a/a-file
make: Leaving directory `--topdir--/plugin/other/indep/b/abuild-indep'
abuild: build complete

29.5.2. Adding Backend Code

Here we examine the plugin.echoer plugin in the plugins/echoer directory. This plugin supplies automatic build code for both Groovy-based and make-based build items, something that cannot be done using ordinary build item-supplied rules. This very simple plugin causes a message to be printed when running the all target. The contents of the message have a default value but can be influenced by changes to a variable that users can make in their individual build files. All build items in any build tree that includes this plugin in its list of plugins will get this functionality automatically without having to take any explicit action. This would be preferable to declaring this as a dependency for every item and modifying RULES or abuild.rules for every build item.

Here we show the code for both the make and Groovy backends in plugin.mk and plugin.groovy respectively:

plugin/plugins/echoer/plugin.mk

all:: echo ;

echo::
        @$(PRINT) This is a message from the echoer plugin.
        @$(PRINT) The value of ECHO_MESSAGE is $(ECHO_MESSAGE)

plugin/plugins/echoer/plugin.groovy

abuild.addTargetClosure('echo') {
    ant.echo("This is a message from the echoer plugin.")
    ant.echo("The value of echo.message is " + abuild.resolve('echo.message'))
}
abuild.addTargetDependencies('all', 'echo')

Observe that the make version refers to the variable ECHO_MESSAGE and the Groovy version refers to the parameter echo.message. Where do these come from? The answer is that default values are provided by pre-plugin initialization files in preplugin.mk and preplugin.groovy. [58] The pre-plugin initialization code is loaded before your build file (Abuild.mk or Abuild.groovy), while the plugin.mk and plugin.groovy files are loaded after your build file. Here are the files:

plugin/plugins/echoer/preplugin.mk

ECHO_MESSAGE = default message

plugin/plugins/echoer/preplugin.groovy

parameters {
    echo.message = 'default message'
}

Although this example is trivial and doesn't do anything useful, it does illustrate how you can use pre-plugin initialization along with regular plugin code to interact with the user's build files. Although adding specific code without going through the usual rules method should generally be used sparingly, there are other cases in which this type of facility might be useful. Examples could include targets that gather statistics or run static analysis checks that may be required by a certain project, or code that enforces policy.

Although building any item in the plugin tree will illustrate use of the plugin.echoer plugin, the echo directory contains four items specifically designed to illustrate manipulation of the echo message. Under the plugin/echo directory, there are four build items. The item echo-a in the a directory uses the make backend and does not modify the ECHO_MESSAGE variable:

plugin/echo/a/Abuild.mk

RULES := empty

The item echo-b in the b directory uses the make backend and modifies the ECHO_MESSAGE variabel:

plugin/echo/b/Abuild.mk

ECHO_MESSAGE += with modifications
RULES := empty

The item echo-c in the c directory uses the Groovy backend and does not modify the echo.message parameter:

plugin/echo/c/Abuild.groovy

parameters {
    abuild.rules = 'empty'
}

The item echo-d in the d directory uses the Groovy backend and modifies the echo.message parameter:

plugin/echo/d/Abuild.groovy

parameters {
    echo.message = resolve(echo.message) + ' with modifications'
    abuild.rules = 'empty'
}

To see this in action, run abuild -b desc from the plugin/echo directory:

plugin-echo.out

abuild: build starting
abuild: echo-a (abuild-indep): all
make: Entering directory `--topdir--/plugin/echo/a/abuild-indep'
This is a message from the echoer plugin.
The value of ECHO_MESSAGE is default message
make: Leaving directory `--topdir--/plugin/echo/a/abuild-indep'
abuild: echo-b (abuild-indep): all
make: Entering directory `--topdir--/plugin/echo/b/abuild-indep'
This is a message from the echoer plugin.
The value of ECHO_MESSAGE is default message with modifications
make: Leaving directory `--topdir--/plugin/echo/b/abuild-indep'
abuild: echo-c (abuild-indep): all
     [echo] This is a message from the echoer plugin.
     [echo] The value of echo.message is default message
abuild: echo-d (abuild-indep): all
     [echo] This is a message from the echoer plugin.
     [echo] The value of echo.message is default message with modifications
abuild: build complete

29.5.3. Platforms and Platform Type Plugins

In the plugin.printer plugin defined in the plugins/printer directory, we create a new platform type and corresponding platform. This is the mechanism that would be used to add support to abuild for an embedded platform, a cross compiler, or some other special environment. In this example, we stretch the idea of platform types a bit for the purpose of illustrating this capability with a simple example.

Here we define a new platform type called printer. This is done by creating a platform-types file and declaring the platform type in it:

plugin/plugins/printer/platform-types

platform-type printer

In addition to adding the platform type, we also add a platform called zzprinter.any.test-suite.abc. [59] To add this platform, we print its name from the list_platforms command:

plugin/plugins/printer/list_platforms

#!/usr/bin/env perl

require 5.008;
BEGIN { $^W = 1; }
use strict;

print "platform zzprinter.any.test-suite.abc -type printer\n"

In this case, the program is trivial, but in a real implementation, the list_platforms command would probably be checking the environment or path for presence of certain tools before emitting the name of the platform. A list_platforms program should only mention the name of a platform that can actually be built on the build host from which it is run.

The fourth field of any object-code platform is always the name of the compiler, so this implies that we have an abc compiler defined somewhere. This plugin also provides the rules for using the abc compiler in toolchains/abc.mk. Here are the implementation file and help file:

plugin/plugins/printer/toolchains/abc.mk

.LIBPATTERNS = shlib-% lib-%
OBJ := obj
LOBJ := obj
define libname
lib-$(1)
endef
define binname
print-$(1)
endef
define shlibname
shlib-$(1)$(if $(2),.$(2)$(if $(3),.$(3)$(if $(4),.$(4))))
endef

ABC := $(abDIR_plugin.printer)/bin/abc
ABCLINK := $(abDIR_plugin.printer)/bin/abc-link

DFLAGS :=
OFLAGS :=
WFLAGS :=

PREPROCESS_c := @:
PREPROCESS_cxx := @:
COMPILE_c := $(ABC)
COMPILE_cxx := $(ABC)
LINK_c := $(ABCLINK)
LINK_cxx := $(ABCLINK)
CCXX_GEN_DEPS := @:

# Usage: $(call include_flags,include-dirs)
define include_flags
        $(foreach I,$(1),-I$(I))
endef

# Usage: $(call make_obj,compiler,pic,flags,src,obj)
define make_obj
        $(1) $(3) -c $(4) -o $(5)
endef

# Usage: $(call make_lib,objects,library-filename)
define make_lib
        cat $(1) > $(call libname,$(2))
endef

# Usage: $(call make_bin,linker,compiler-flags,linker-flags,objects,libdirs,libs,binary-filename)
define make_bin
        $(1) $(2) $(3) $(foreach I,$(4),-o $(I)) \
                   $(foreach I,$(5),-L $(I)) \
                   $(foreach I,$(6),-l $(I)) \
                   -b $(call binname,$(7))
endef

# Usage: $(call make_shlib,linker,compiler-flags,linker-flags,objects,libdirs,libs,shlib-filename,major,minor,revision)
define make_shlib
        $(1) $(2) $(3) $(foreach I,$(4),-o $(I)) \
                   $(foreach I,$(5),-L $(I)) \
                   $(foreach I,$(6),-l $(I)) \
                   -b $(call shlibname,$(7),$(8),$(9),$(10))
endef

plugin/plugins/printer/toolchains/abc-help.txt

The "abc" toolchain is a simple example toolchain support file.  It
doesn't do much of anything, but does illustrate many of the
capabilities provided by abuild's ccxx rules.

You can see the help text by running abuild --help rules toolchain:abc, and you can discover that this toolchain is available by provided abuild --help rules list from anywhere in the plugins tree. To understand this file, you should read through the comments in rules/object-code/ccxx.mk in the abuild distribution (Appendix I, The ccxx.mk File). In this case, our plugin also creates the compiler itself in bin/abc and bin/abc-link. Our “compilers” here just create text files of the source code with numbered lines. Doing this particular operation with a plugin is a bit absurd—using some external utility would be a better implementation. Still, it illustrates the mechanics of setting up an additional platform type, and it is not at all uncommon for a native compiler plugin to provide wrappers around the real compiler.

Note that to invoke our compiler, the abc.mk file uses $(abDIR_plugin.printer) to refer to a file in its own directory, just as would be necessary in rules provided by a regular build item. Abuild provides these variables to the make backend and also makes this information available to the Groovy backend. [60]

To see this plugin in action, build the build item in other/bin with --with-deps. You will see not only the normal native executable program being built, but you will also see a second output directory called abuild-zzprinter.any.test-suite.abc which contains a file called print-program. This happens because both the bin build item and the lib build item on which it depends include the printer platform type in their platform-types keys in their Abuild.conf files:

plugin/other/lib/Abuild.conf

name: lib
platform-types: native printer

plugin/other/bin/Abuild.conf

name: bin
platform-types: native printer
deps: lib

Here is the build output:

plugin-other-bin.out

abuild: build starting
abuild: lib (abuild-<native>): all
make: Entering directory `--topdir--/plugin/other/lib/abuild-<native>'
This is a message from the echoer plugin.
The value of ECHO_MESSAGE is default message
Compiling ../lib.cc as C++
Creating lib library
make: Leaving directory `--topdir--/plugin/other/lib/abuild-<native>'
abuild: bin (abuild-<native>): all
make: Entering directory `--topdir--/plugin/other/bin/abuild-<native>'
This is a message from the echoer plugin.
The value of ECHO_MESSAGE is default message
Compiling ../main.cc as C++
Creating program executable
make: Leaving directory `--topdir--/plugin/other/bin/abuild-<native>'
abuild: lib (abuild-zzprinter.any.test-suite.abc): all
make: Entering directory `--topdir--/plugin/other/lib/abuild-zzprinter.a\
\ny.test-suite.abc'
This is a message from the echoer plugin.
The value of ECHO_MESSAGE is default message
Compiling ../lib.cc as C++
Creating lib library
make: Leaving directory `--topdir--/plugin/other/lib/abuild-zzprinter.an\
\y.test-suite.abc'
abuild: bin (abuild-zzprinter.any.test-suite.abc): all
make: Entering directory `--topdir--/plugin/other/bin/abuild-zzprinter.a\
\ny.test-suite.abc'
This is a message from the echoer plugin.
The value of ECHO_MESSAGE is default message
Compiling ../main.cc as C++
Creating program executable
make: Leaving directory `--topdir--/plugin/other/bin/abuild-zzprinter.an\
\y.test-suite.abc'
abuild: build complete

Here is the print-program file. This file contains the concatenation of all the source files used to create the executable as well as the “libraries” it “links” against:

printer-program.out

------ ==> ../../lib/abuild-zzprinter.any.test-suite.abc/lib-lib <== ------


------ ../lib.cc ------

1: #include "lib.hh"
2: 
3: #include <iostream>
4: 
5: void f()
6: {
7:     std::cout << "I am a function named f." << std::endl;
8: }

------ main.obj ------


------ ../main.cc ------

1: #include <iostream>
2: #include "lib.hh"
3: 
4: int main()
5: {
6:     f();
7:     std::cout << "I, this program, am aware of myself." << std::endl;
8:     std::cout << "Does that mean I'm alive?" << std::endl;
9:     return 0;
10: }

29.5.4. Plugins and Tree Dependencies

In the example/plugin/outside build tree, we have a tree that includes our plugin tree as an tree dependency. This tree contains the prog2 build item which depends on the same lib as our previous example's bin build item. This build tree does not declare any plugins, so even though its tree dependency declares plugins, those plugins are not used within this tree. When we build the prog2 build item with dependencies, although the lib build item still builds as before, prog2 completely disregards the existence of the other platform type and the echoer's additional build steps. This is very important. Sometimes, a build tree may declare a plugin that works for every item in its own tree but that would not necessarily work for items in other trees. Examples might include strict static analyzers or other code checkers. It may be desirable to allow the products of this build tree to be usable by others that do not wish to follow the same restrictions. Here is the output of building prog2 with dependencies:

plugin-outside.out

abuild: build starting
abuild: lib (abuild-<native>): all
make: Entering directory `--topdir--/plugin/other/lib/abuild-<native>'
This is a message from the echoer plugin.
The value of ECHO_MESSAGE is default message
make: Leaving directory `--topdir--/plugin/other/lib/abuild-<native>'
abuild: lib (abuild-zzprinter.any.test-suite.abc): all
make: Entering directory `--topdir--/plugin/other/lib/abuild-zzprinter.a\
\ny.test-suite.abc'
This is a message from the echoer plugin.
The value of ECHO_MESSAGE is default message
make: Leaving directory `--topdir--/plugin/other/lib/abuild-zzprinter.an\
\y.test-suite.abc'
abuild: prog2 (abuild-<native>): all
make: Entering directory `--topdir--/plugin/outside/prog2/abuild-<native>'
Compiling ../main.cc as C++
Creating prog2 executable
make: Leaving directory `--topdir--/plugin/outside/prog2/abuild-<native>'
abuild: build complete

29.5.5. Native Compiler Plugins

In the example/native-compiler directory, we have a plugin that defines a native compiler. The plugin is in the compiler directory and is called plugin.compiler. In this plugin, we are adding a new platform to support our alternative compiler. We don't have to add any new platform types since we are just adding this platform to the native platform type. Since this is a relatively common operation, abuild provides a short syntax for doing it. Here is the list_platforms program:

native-compiler/compiler/list_platforms

#!/usr/bin/env perl
BEGIN { $^W = 1; }
use strict;

my $lowpri = '';
if ((exists $ENV{'QCC_LOWPRI'}) && ($ENV{'QCC_LOWPRI'} eq '1'))
{
    $lowpri = ' -lowpri';
}
if (! ((exists $ENV{'NO_QCC'}) && ($ENV{'NO_QCC'} eq '1')))
{
    print "native-compiler$lowpri qcc.release\n";
    print "native-compiler$lowpri qcc.debug\n";
    print "native-compiler$lowpri qcc\n";
}

It generates this output which automatically creates platforms with the same first three fields (os, cpu, and toolset) as other native platforms, with the qcc compiler as the fourth field, and with release, debug, or nothing as the fifth field:

native-compiler qcc.release
native-compiler qcc.debug
native-compiler qcc

Since new platforms take precedence over old platforms by default when abuild chooses which platform to use for a given platform type, our list_platforms script offers the user a way of suppressing this platform and also of making these low priority compilers. In this case, our list_platforms program doesn't generate any output if the NO_QCC environment variable is set, and if the QCC_LOWPRI environment variable is set, it declares these as low priority compilers which makes them available but prevents them from being selected by default over built-in compilers or compilers declared by earlier plugins. Setting that environment variable would make that platform completely unavailable, regardless of any compiler preferences expressed by the user. (We could also prevent the platform using this compiler from being built by default without making it disappear entirely by using platform selectors as discussed in Section 24.1, “Platform Selection”). Note that we generate output for the qcc compiler with the release and debug flags as per our usual convention. By placing the compiler with no options last, we make abuild select it by default over the other two. It will also be selected over any built-in platforms or platforms provided by earlier plugins.

In addition to listing the compiler in list_platforms, we have to provide a support file for it in toolchains/qcc.mk:

native-compiler/compiler/toolchains/qcc.mk

.LIBPATTERNS = lib-%
OBJ = o
LOBJ = o
define libname
lib-$(1)
endef
define binname
bin-$(1)
endef
define shlibname
shlib-$(1)$(if $(2),.$(2)$(if $(3),.$(3)$(if $(4),.$(4))))
endef

QCC = echo

DFLAGS =
OFLAGS =
WFLAGS =

# Convention: clear OFLAGS with debug option and DFLAGS with release option.
ifeq ($(ABUILD_PLATFORM_OPTION), debug)
OFLAGS =
endif
ifeq ($(ABUILD_PLATFORM_OPTION), release)
DFLAGS =
endif

PREPROCESS_c = @:
PREPROCESS_cxx = @:
COMPILE_c = $(QCC)
COMPILE_cxx = $(QCC)
LINK_c = $(QCC)
LINK_cxx = $(QCC)
CCXX_GEN_DEPS = @:

# Usage: $(call include_flags,include-dirs)
define include_flags
        $(foreach I,$(1),-I$(I))
endef

# Usage: $(call make_obj,compiler,pic,flags,src,obj)
define make_obj
        $(1) make-obj $(5)
        touch $(5)
endef

# Usage: $(call make_lib,objects,library-filename)
define make_lib
        $(QCC) make-lib $(call libname,$(2))
        touch $(call libname,$(2))
endef

# Usage: $(call make_bin,linker,compiler-flags,linker-flags,objects,libdirs,libs,binary-filename)
define make_bin
        $(1) make-bin $(call binname,$(7))
        touch $(call binname,$(7))
endef

# Usage: $(call make_shlib,linker,compiler-flags,linker-flags,objects,libdirs,libs,shlib-filename,major,minor,revision)
define make_shlib
        $(1) make-bin $(call shlibname,$(7),$(8),$(9),$(10))
        touch $(call shlibname,$(7),$(8),$(9),$(10))
endef

This file illustrates a degenerate compiler implementation, providing minimal implementations of all the variables and functions that ccxx.mk requires. For details, please read the comments in rules/object-code/ccxx.mk in the abuild distribution (Appendix I, The ccxx.mk File).

In the native-compiler/outside directory, there is another build tree that lists the plugin tree, in this case native-compiler, as a tree dependency:

native-compiler/outside/Abuild.conf

tree-name: outside
tree-deps: native-compiler
name: outside
platform-types: native
deps: lib

This tree doesn't know about the qcc compiler, so when we build the outside build item, it would build only with the default native compiler. In a default invocation of abuild (i.e., one without any platform selectors), the lib build item on which this depends would only be built with qcc because of the plugin in its build tree (which is a tree dependency of this tree). However, the lib build item could also be built with the default native compiler. Abuild recognizes this fact and will therefore compile lib with both qcc and the default native compiler. This is an example of abuild's ability to add additional build platforms as needed based on the dependency graph:

as-needed-platforms.out

abuild: build starting
abuild: lib (abuild-<native>): all
make: Entering directory `--topdir--/native-compiler/lib/abuild-<native>'
Compiling ../lib.cc as C++
Creating lib library
make: Leaving directory `--topdir--/native-compiler/lib/abuild-<native>'
abuild: lib (abuild-<native-os-data>.qcc): all
make: Entering directory `--topdir--/native-compiler/lib/abuild-<native-\
\os-data>.qcc'
Compiling ../lib.cc as C++
make-obj lib.o
Creating lib library
make-lib lib-lib
make: Leaving directory `--topdir--/native-compiler/lib/abuild-<native-o\
\s-data>.qcc'
abuild: outside (abuild-<native>): all
make: Entering directory `--topdir--/native-compiler/outside/abuild-<nat\
\ive>'
Compiling ../outside.cc as C++
Creating outside executable
make: Leaving directory `--topdir--/native-compiler/outside/abuild-<native>'
abuild: build complete

29.5.6. Checking Project-Specific Rules

Another use of a plugin could be to enforce additional build tree-specific rules that fall outside of abuild's normal dependency checking capabilities. As an example, suppose you had a build item that you wanted all build items to depend on and that you couldn't make it a plugin because it had to build something. You could have that build item set a variable to some specific value in its Abuild.interface file. Then you could create a plugin that would check that the variable had that value, which would effectively make sure everyone depended on the item that set the variable. This plugin would have a plugin.mk file that would check to make sure that the variable was set and report an error if not. Since all build items would see the plugin code, it would make this plugin an effective checker for enforcing some rule that can't otherwise by expressed.

We illustrate this pattern in our rule-checker example which can be found in doc/example/rule-checker. This directory includes four build items: plugin.auto-checker, auto-provider, item1, and item2. The goal is that every build item whose target type is object-code should depend on auto-provider. This rule is enforced with the plugin.auto-checker plugin which is declared as a plugin in the tree's root Abuild.conf:

rule-checker/Abuild.conf

tree-name: rule-checker
child-dirs: auto-checker auto-provider item1 item2
plugins: plugin.auto-checker

The plugin.auto-checker build item contains two files aside from its Abuild.conf. It has a plugin.interface file that declares a variable that indicates whether the auto-provider build item has been seen:

rule-checker/auto-checker/plugin.interface

declare SAW_AUTO_PROVIDER boolean
fallback SAW_AUTO_PROVIDER = 0

This plugin interface file is automatically loaded by all build items before their own interface files or any of the interface files of their dependencies. We include a fallback assignment of a false value to this variable. The auto-provider build item sets this variable to true in its Abuild.interface file:

rule-checker/auto-provider/Abuild.interface

# The plugin.auto-checker plugin must be enabled on any build tree
# whose item depend on this since its plugin.interface file provides a
# declaration for the SAW_AUTO_PROVIDER variable.  Additionally, the
# plugin.auto-checker plugin makes sure everyone depends on this
# item.  This item cannot itself be a plugin because it has an
# Abuild.mk file.

SAW_AUTO_PROVIDER = 1

# Make the automatically generated file visible
INCLUDES = $(ABUILD_OUTPUT_DIR)

For completeness, here are the rest of the files from auto-provider:

rule-checker/auto-provider/Abuild.mk

LOCAL_RULES := provide-auto.mk

rule-checker/auto-provider/provide-auto.mk

all:: auto.h

auto.h:
        @$(PRINT) Generating $@
        echo '#define AUTO_VALUE 818' > $@

Since auto-provider sets the SAW_AUTO_PROVIDER variable, it possible for the plugin.auto-checker build item to detect that auto-provider is in the dependency list by checking the value of that variable. It does this in its plugin.mk file, which is included by abuild's make code for every make-based build item:

rule-checker/auto-checker/plugin.mk

ifeq ($(ABUILD_TARGET_TYPE), object-code)
 ifeq ($(SAW_AUTO_PROVIDER), 0)
 $(error This item is supposed to depend on auto-provider, but it does not)
 endif
endif

To see what happens when a build item forgets to depend on auto-provider, we will look at item1. Here is its Abuild.conf:

rule-checker/item1/Abuild.conf

name: item1
platform-types: native

As you can see, there is no dependency on auto-provider. When we try to build this item, we get the following error:

rule-checker-item1-error.out

abuild: build starting
abuild: item1 (abuild-<native>): all
make: Entering directory `--topdir--/rule-checker/item1/abuild-<native>'
../../auto-checker/plugin.mk:3: *** This item is supposed to depend on a\
\uto-provider, but it does not.  Stop.
make: Leaving directory `--topdir--/rule-checker/item1/abuild-<native>'
abuild: item1 (abuild-<native>): build failed
abuild: build complete
abuild: ERROR: at least one build failure occurred; summary follows
abuild: ERROR: build failure: item1 on platform <native>

This is the error that was issued from plugin.auto-checker's plugin.mk above. The build item item2 does declare the appropriate dependency:

rule-checker/item2/Abuild.conf

name: item2
platform-types: native
deps: auto-provider

Its build proceeds normally:

rule-checker-item2-build.out

abuild: build starting
abuild: auto-provider (abuild-indep): all
make: Entering directory `--topdir--/rule-checker/auto-provider/abuild-i\
\ndep'
Generating auto.h
make: Leaving directory `--topdir--/rule-checker/auto-provider/abuild-indep'
abuild: item2 (abuild-<native>): all
make: Entering directory `--topdir--/rule-checker/item2/abuild-<native>'
Compiling ../item2.c as C
Creating item2 executable
make: Leaving directory `--topdir--/rule-checker/item2/abuild-<native>'
abuild: build complete

This examples shows how little code is required to implement your own rule checking. The possibilities for use of this technique are endless. Such techniques could be used to enforce all sorts of project-specific architectural constraints, build item naming conventions, or any number of other possibilities. You could even create a single project-wide global plugin that checked to make sure other plugins defined in other trees were appropriate declared, thus effectively working around the limitation of only being able to declare a single global tree dependency in a forest.

29.5.7. Install Target

Still another use of plugins could be to implement an install target. Although abuild provides most of what is required to use build products within the source tree, in most real systems, there comes a time when a distribution has to be created. You can write your own install target or similar using plugins.



[58] We had originally wanted to call these pre-plugin instead of preplugin, but this interferes with the way Groovy generates classes for scripts. Since pre-plugin is not a valid class name and we want to avoid specific mixed case file names (like prePlugin, we went with preplugin. The make version is called the same thing for consistency.

[59] This odd name has been picked to facilitate testing of all examples in abuild's own automated test suite. By starting the platform name with zz, we effectively ensure that it will always appear alphabetically after whatever the real native platform is on our build system.

[60] For the deprecated xml-based ant backend, corresponding ant properties abuild.dir.build-item are available.