Now that we've seen the topics of build sets and traits, we're ready to revisit our previous examples. This time, we will talk about how traits are used in a build tree, and we will demonstrate the results of running abuild with different build sets. We will also make use of the special target no-op which can be useful for debugging your build trees.
Any arguments to abuild that are not command-line options are
interpreted as targets. By default, abuild uses the
all target to build each build item in the
build set. If targets are named explicitly, for the build items
to which they apply, they are passed directly to the backend.
There are two exceptions to this rule: the special targets
clean and no-op are
trapped by abuild and handled separately without invocation of
the backend. We have already seen the clean
target: it just removes any abuild output directories in the
build item directory. The special no-op
target causes abuild to go through all the motions of building
except for actually invoking the backend. The
no-op command is useful for seeing what build
items would be built on what platforms in a particular
invocation of abuild. It does all the same validation on
Abuild.conf
files as a regular build, but
it doesn't look at Abuild.interface
files
or build files (Abuild.mk
, etc.).
We return now to the reference/common
directory to demonstrate both the no-op
target and some build sets. From the
reference/common
directory, we can run
abuild --build=local no-op to tell abuild
to run the special no-op target for every
build item in the local build tree. Since this tree has no tree
dependencies, there is no chance that there are any dependencies
that are satisfied outside of the local build tree. Running
this command produces the following results (with the native
platform again replaced by the string
<native>
):
reference-common-no-op.out
abuild: build starting abuild: common-lib1.src (abuild-<native>): no-op abuild: common-lib1.test (abuild-<native>): no-op abuild: common-lib3.src (abuild-<native>): no-op abuild: common-lib2.src (abuild-<native>): no-op abuild: common-lib2.test (abuild-<native>): no-op abuild: common-lib3.test (abuild-<native>): no-op abuild: build complete
Of particular interest here is the order in which abuild
visited the items. Abuild makes no specific commitments about
the order in which items will be built except that no item is
ever built before its dependencies are built.
[23]
Since common-lib2.src
depends on
common-lib3.src
(indirectly through its
dependency on common-lib3
), abuild
automatically builds common-lib3.src
before it builds common-lib2.src
. On the
other hand, since common-lib2.test
has no
dependency on common-lib3.test
, no
specific ordering is necessary in that case. If you were to run
abuild --clean=local from this directory, you
would not observe the same ordering of build items since
abuild does not pay any attention to dependencies when it is
running the clean target, as shown:
reference-common-clean-local.out
abuild: cleaning common-lib1 in lib1 abuild: cleaning common-lib1.src in lib1/src abuild: cleaning common-lib1.test in lib1/test abuild: cleaning common-lib2 in lib2 abuild: cleaning common-lib2.src in lib2/src abuild: cleaning common-lib2.test in lib2/test abuild: cleaning common-lib3 in lib3 abuild: cleaning common-lib3.src in lib3/src abuild: cleaning common-lib3.test in lib3/test
Note also that only the build items that have
Abuild.mk
files are cleaned. Abuild
knows that there is nothing to build in items without
Abuild.mk
files and skips them when it is
building or cleaning multiple items.
If you are following along, then go to the
reference/common
directory and run
abuild --build=desc check. This will build
and run the test suites for all build items at or below that
directory, which in this case, is the same collection of build
items as the local
build set.
[24]
This produces the following output, again with some
system-specific strings replaced with generic values:
reference-common-check.out
abuild: build starting abuild: common-lib1.src (abuild-<native>): check make: Entering directory `--topdir--/general/reference/common/lib1/src/a\ \build-<native>' Compiling ../CommonLib1.cpp as C++ Creating common-lib1 library make: Leaving directory `--topdir--/general/reference/common/lib1/src/ab\ \uild-<native>' abuild: common-lib1.test (abuild-<native>): check make: Entering directory `--topdir--/general/reference/common/lib1/test/\ \abuild-<native>' Compiling ../main.cpp as C++ Creating lib1_test executable ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/lib1.test lib1 1 (test lib1 class) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/common/lib1/test/a\ \build-<native>' abuild: common-lib3.src (abuild-<native>): check make: Entering directory `--topdir--/general/reference/common/lib3/src/a\ \build-<native>' Compiling ../CommonLib3.cpp as C++ Creating common-lib3 library make: Leaving directory `--topdir--/general/reference/common/lib3/src/ab\ \uild-<native>' abuild: common-lib2.src (abuild-<native>): check make: Entering directory `--topdir--/general/reference/common/lib2/src/a\ \build-<native>' Compiling ../CommonLib2.cpp as C++ Creating common-lib2 library make: Leaving directory `--topdir--/general/reference/common/lib2/src/ab\ \uild-<native>' abuild: common-lib2.test (abuild-<native>): check make: Entering directory `--topdir--/general/reference/common/lib2/test/\ \abuild-<native>' Compiling ../main.cpp as C++ Creating lib2_test executable ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/lib2.test lib2 1 (test lib2 class) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/common/lib2/test/a\ \build-<native>' abuild: common-lib3.test (abuild-<native>): check make: Entering directory `--topdir--/general/reference/common/lib3/test/\ \abuild-<native>' Compiling ../main.cpp as C++ Creating lib3_test executable ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/lib3.test lib3 1 (test lib3 class) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/common/lib3/test/a\ \build-<native>' abuild: build complete
This example includes the output of qtest test suites. QTest is a simple and robust automated test framework that is integrated with abuild and used for abuild's own test suite. For information, see Section 10.2, “Integration with QTest”.
By default, when abuild builds multiple build items using a
build set, it will stop after the first build failure.
Sometimes, particularly when building a large build tree, you
may want abuild to try to build as many build items as it can,
continuing on failure. In this case, you may pass the
-k
option to abuild. When run with the
-k
option, abuild will continue building
other items after one item fails. It will also exit with an
abnormal exit status after it builds everything that it can, and
it will provide a summary of what failed. When run with
-k
, abuild also passes the corresponding
flags to the backends so that they will try to build as much as
they can without stopping on the first error. Both the
make and
Groovy backends behave similarly to
abuild: they will keep going on failure, skip any targets that
depend on failed targets, and exit abnormally if any failures
are detected.
Ordinarily, if one build item fails, abuild will not attempt
to build any other items that depend on the failed item even
when run with -k
. If you specify the
--no-dep-failures
option along with
-k
, then abuild will not only continue after
the first failure but will also attempt to build items even when
one or more of their dependencies have failed. Use of this
option may result in cascading errors since the build of one
item is likely to fail as a result of failures in its
dependencies. There are, however, several cases in which this
option may still be useful. For example, if building a large
build tree with known problems in it, it may be useful to first
tell abuild to build everything it possibly can. Then you can
go back and try to clean up the error conditions without having
to wait for the compilation of files that would have been
buildable before. Another case in which this option may be
useful is when running test suites: in many cases, we may wish
to attempt to run test suites for items even if some of the test
suites of their dependencies have failed. Essentially, running
-k --no-dep-failures
allows abuild to attempt
to build everything that the backends will allow it to build.
Returning to the project area, we demonstrate how item
dependencies may be satisfied in trees named as tree
dependencies and the effect this has on the build set. Under
reference/project
, we have just two public
build items called project-main
and
project-lib
. The
project-lib
build item is structured like
the libraries in the common area. The
project-main
build item has a
src
directory that builds an executable and
has its own test suite. We have already seen that
reference/project/Abuild.conf
has a
tree-deps key that lists
common
and that items from the
project
tree depend on build items from
common
. Specifically,
project-lib
depends on
common-lib1
and
project-main
depends on
common-lib2
which in turn depends on
common-lib3
.
If we go to reference/project/main/src
and
run abuild no-op, we see the following
output:
reference-project-main-no-op.out
abuild: build starting abuild: common-lib1.src (abuild-<native>): no-op abuild: common-lib3.src (abuild-<native>): no-op abuild: common-lib2.src (abuild-<native>): no-op abuild: project-lib.src (abuild-<native>): no-op abuild: project-main.src (abuild-<native>): no-op abuild: build complete
Notice here that abuild only built the build items whose names
end with .src
, that it built the items in
dependency order, and that it built all the items from
common
before any of the items in
project
. We can also run abuild
--apply-targets-to-deps check to run the
check target for each of these build items.
This generates the following output:
reference-project-main-check.out
abuild: build starting abuild: common-lib1.src (abuild-<native>): check abuild: common-lib3.src (abuild-<native>): check abuild: common-lib2.src (abuild-<native>): check abuild: project-lib.src (abuild-<native>): check make: Entering directory `--topdir--/general/reference/project/lib/src/a\ \build-<native>' Compiling ../ProjectLib.cpp as C++ Creating project-lib library make: Leaving directory `--topdir--/general/reference/project/lib/src/ab\ \uild-<native>' abuild: project-main.src (abuild-<native>): check make: Entering directory `--topdir--/general/reference/project/main/src/\ \abuild-<native>' Compiling ../main.cpp as C++ Creating main executable ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/main.test main 1 (testing project-main) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/project/main/src/a\ \build-<native>' abuild: build complete
The presence of the --apply-target-to-deps
flag
caused the check target will be run for our
dependencies as well as the current build item. In this case,
there were no actions performed building the files in
common
because they were already built. If
individual files had been modified in any of these build items,
the appropriate targets would have been rebuilt subject to the
ordinary file-based dependency management performed by make or
ant.
In our previous example, we saw the check
target run for each item (that has a build file). Since the
items other than project-main
don't
contain their own test suites, we see the test suite only for
project-main
. Sometimes we might like to
run all the test suites of all the build items we depend on,
even if we don't depend on their test suites directly. We can
do this using traits, assuming our build tree has been set up to
use traits for this purpose. Recall from earlier that our
common
build tree declared the
tester trait in its root build item's
Abuild.conf
. Here is that file again:
general/reference/common/Abuild.conf
tree-name: common child-dirs: lib1 lib2 lib3 supported-traits: tester
Also, recall that all the test suites declared themselves as
testers of the items that they tested. Here again is
common-lib1.test
's
Abuild.conf
, which declares
common-lib1.test
to be a tester of
common-lib1.src
:
general/reference/common/lib1/test/Abuild.conf
name: common-lib1.test platform-types: native deps: common-lib1 traits: tester -item=common-lib1.src
Given that all of our build items are set up in this way, we can instruct abuild to run the test suites for everything that we depend on. We do this by running abuild --related-by-traits tester check. This runs the check target for every item that declares itself as a tester of the current build item or any of its dependencies, and the all target for everything else, including any additional dependencies of any of those test suites. That command generates the following output:
reference-project-main-trait-test.out
abuild: build starting abuild: common-lib1.src (abuild-<native>): all abuild: common-lib1.test (abuild-<native>): check make: Entering directory `--topdir--/general/reference/common/lib1/test/\ \abuild-<native>' ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/lib1.test lib1 1 (test lib1 class) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/common/lib1/test/a\ \build-<native>' abuild: common-lib3.src (abuild-<native>): all abuild: common-lib2.src (abuild-<native>): all abuild: common-lib2.test (abuild-<native>): check make: Entering directory `--topdir--/general/reference/common/lib2/test/\ \abuild-<native>' ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/lib2.test lib2 1 (test lib2 class) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/common/lib2/test/a\ \build-<native>' abuild: common-lib3.test (abuild-<native>): check make: Entering directory `--topdir--/general/reference/common/lib3/test/\ \abuild-<native>' ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/lib3.test lib3 1 (test lib3 class) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/common/lib3/test/a\ \build-<native>' abuild: project-lib.src (abuild-<native>): all abuild: project-lib.test (abuild-<native>): check make: Entering directory `--topdir--/general/reference/project/lib/test/\ \abuild-<native>' Compiling ../main.cpp as C++ Creating lib_test executable ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/lib.test lib 1 (test lib class) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/project/lib/test/a\ \build-<native>' abuild: project-main.src (abuild-<native>): check make: Entering directory `--topdir--/general/reference/project/main/src/\ \abuild-<native>' ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/main.test main 1 (testing project-main) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/project/main/src/a\ \build-<native>' abuild: build complete
Observe that the previously unbuilt
project-lib.test
build item was built
using the check target by this command, and
that all the test suites were run. If your development area has
good test suites, you are encouraged to use a trait to indicate
which items they test as we have done here using the
tester trait. This enables you to run the
test suites of items in your dependency chain. This can give
you significant assurance that everything you depend on is
working the way it is supposed to be each time you start a
development or debugging session.
Suppose you have made a modification to a particular build item,
and you want to make sure the modification doesn't break anyone
who depends on that build item, whether the dependent item is in
the modified item's tree or not. In order to do this, you can
specify the --with-rdeps
flag when building the
modified item. This will cause abuild to add all of that
item's reverse dependencies to the build set. For example, this
is the output of running abuild --with-rdeps
in the general/reference/common/lib2/src
directory:
reference-common-lib2-rdeps.out
abuild: build starting abuild: common-lib1.src (abuild-<native>): no-op abuild: common-lib3.src (abuild-<native>): no-op abuild: common-lib2.src (abuild-<native>): no-op abuild: common-lib2.test (abuild-<native>): no-op abuild: common-lib3.test (abuild-<native>): no-op abuild: project-lib.src (abuild-<native>): no-op abuild: project-main.src (abuild-<native>): no-op abuild: derived-main.src (abuild-<native>): no-op abuild: build complete
This includes all direct and indirect reverse dependencies of
common-lib2.src
. If you really want to be
make sure that everything that is related to this build item by
dependency in any way is rebuilt, you can use the
--repeat-expansion
option as well. This will
repeat the reverse dependency expansion after adding the other
dependencies of your reverse dependencies, and will continue
repeating the expansion until no more items are added. If we
run abuild --with-rdeps --repeat-expansion
no-op from here, we get this output:
reference-common-lib2-rdeps-repeated.out
abuild: build starting abuild: common-lib1.src (abuild-<native>): no-op abuild: common-lib1.test (abuild-<native>): no-op abuild: common-lib3.src (abuild-<native>): no-op abuild: common-lib2.src (abuild-<native>): no-op abuild: common-lib2.test (abuild-<native>): no-op abuild: common-lib3.test (abuild-<native>): no-op abuild: project-lib.src (abuild-<native>): no-op abuild: project-lib.test (abuild-<native>): no-op abuild: project-main.src (abuild-<native>): no-op abuild: derived-main.src (abuild-<native>): no-op abuild: build complete
Observe the addition of common-lib1.test
and project-lib.test
, which are reverse
dependencies of libraries added to satisfy the dependencies of
some of common-lib2
's dependencies! If
that seems confusing, then you probably don't need to worry
about ever using --repeat-expansion
! Using
--repeat-expansion
with
--with-rdeps
will usually a lot of build items
to the build set. In this example, it actually adds every build
item in the forest to the build set. The only build items that
would not be added would be completely independent sets of build
items that happen to exist in the same forest.
Finally, we return to our derived project build tree in
reference/derived
. This build tree
declares project
as a tree dependency. As
pointed out before, although derived
does
not declare common
as a tree dependency, it
can still use build items in common
because
tree dependencies are transitive. If we run abuild
--build=desc check from
reference/derived
, we will see all our
dependencies in common
and
project
being built (though all are up to
date at this point) before our own test suite is run, and we
will also see that all the items in common
build first, followed by the items in
project
, finally followed by the items in
derived
. This is the case even though they
are not all descendants of the current directory. This again
illustrates how abuild adds additional items to the build set
as required to satisfy dependencies:
reference-derived-check.out
abuild: build starting abuild: common-lib1.src (abuild-<native>): all abuild: common-lib3.src (abuild-<native>): all abuild: common-lib2.src (abuild-<native>): all abuild: project-lib.src (abuild-<native>): all abuild: derived-main.src (abuild-<native>): check make: Entering directory `--topdir--/general/reference/derived/main/src/\ \abuild-<native>' Compiling ../main.cpp as C++ Creating main executable ********************************* STARTING TESTS on ---timestamp--- ********************************* Running ../qtest/main.test main 1 (testing derived-main) ... PASSED Overall test suite ... PASSED TESTS COMPLETE. Summary: Total tests: 1 Passes: 1 Failures: 0 Unexpected Passes: 0 Expected Failures: 0 Missing Tests: 0 Extra Tests: 0 make: Leaving directory `--topdir--/general/reference/derived/main/src/a\ \build-<native>' abuild: build complete
We can also observe that we do not see this behavior with the
special clean target. Both abuild
--clean=desc and abuild
--clean=local produce this output when run from
reference/derived
:
reference-derived-clean-local.out
abuild: cleaning derived-main in main abuild: cleaning derived-main.src in main/src
As another demonstration of the transitive nature of tree
dependencies, run abuild --clean=all from the
root of the derived
build tree. That
generates this output:
reference-derived-clean.out
abuild: cleaning common-lib1 in ../common/lib1 abuild: cleaning common-lib1.src in ../common/lib1/src abuild: cleaning common-lib1.test in ../common/lib1/test abuild: cleaning common-lib2 in ../common/lib2 abuild: cleaning common-lib2.src in ../common/lib2/src abuild: cleaning common-lib2.test in ../common/lib2/test abuild: cleaning common-lib3 in ../common/lib3 abuild: cleaning common-lib3.src in ../common/lib3/src abuild: cleaning common-lib3.test in ../common/lib3/test abuild: cleaning derived-main in main abuild: cleaning derived-main.src in main/src abuild: cleaning project-lib in ../project/lib abuild: cleaning project-lib.src in ../project/lib/src abuild: cleaning project-lib.test in ../project/lib/test abuild: cleaning project-main in ../project/main abuild: cleaning project-main.src in ../project/main/src
Here are a few things to notice:
We clean all build items in common
and
project
as well as in
derived
.
Even build items that don't contain build files are visited.
Build items are cleaned in an order that completely disregards any dependencies that may exist among them.
[23] In fact, when abuild creates a build order, it starts with a lexically sorted list of build trees and re-orders it as needed so that trees appear in dependency order. Then, within each tree, it does the same with items. The effect is that items build by tree with most referenced trees building earlier and, with each tree, most referenced items building earlier. Ties are resolved by lexical ordering. That said, the exact order of build items, other than that dependencies are built before items that depend on them, should be considered an implementation detail and not relied upon. Also, keep in mind that, in a multithreaded build, the order is not deterministic, other than that no item's build is started before all its dependencies' builds have completed.
[24] The test suites in this example are implemented with QTest, which therefore must be installed for you to run them. See Chapter 10, Integration with Automated Test Frameworks.