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
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
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
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: }
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
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
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.
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.
are available.
build-item