Build Quick Start Guide

This guide will walk you through the examples in the distribution tarball. Please follow the instructions there and install the builder. You can then change into the examples subdirectory, which should have the following contents:

examples/
 |_README
 |_Howto.cook               # Master Howto.cook file at the top of source tree
 |_3party/                  # Third party product repository
 |  |_noarch/               #  Platform independent section
 |     |_hello_world.tar.gz #   Sample
 |_pkg/                     # Packaging (rpm) subdirectory
 |  |_Howto.cook            #  Settings specific to rpm packing
 |  |_MANIFEST.preamble     #  First lines of the rpm spec %files section
 |  |_manifest.spec-inc     #  Utility shell functions used for packing rpms
 |  |_hello_world/          #  Sample rpm spec directory
 |     |_Howto.cook         #   Contains "make rpm;" directive to build an rpm
 |     |                    #    and all the dependencies to the src/ tree
 |     |_hello_world.spec   #   The rpm spec file for hello_world rpm
 |                          #
 |_src/                     # Sources
    |_hello_world_1/        #  Simple executable
    |  |_Howto.cook         #   Contains "make simplex;" directive
    |  |_hello_world_1.c    #   Single source file containing main()
    |                       #
    |_hello_world_2/        #  Complex executable
    |  |_Howto.cook         #   Contains "make complex;" directive
    |  |_hello.h            #   Header of factored out code
    |  |_hello.c            #   Implementation of factored out code
    |  |_hello_world_2.c    #   Contains main() and calls hello
    |                       #
    |_hello_world_3/        #  Program + Library
    |  |_Howto.cook         #   Contains include path setting
    |  |_bin/               #   Program
    |  |  |_Howto.cook      #    Contains "make simplex;" directive
    |  |  |_hello_world_3.c #    Contains main() and calls library function
    |  |_lib/               #   Libraries
    |     |_hello3          #    libhello3.a
    |        |_Howto.cook   #     Contains "make archive;" directive
    |        |_hello.h      #     Header of library function
    |        |_hello.c      #     Implementation of library function
    |                       #
    |_hello_world_4         #  Program + Third Party Product
       |_Howto.cook         #   Contains include path setting
       |_bin/               #   Program
       |  |_Howto.cook      #    Contains "make simplex;" directive
       |  |_hello_world_3.c #    Contains main() and calls library function
       |_lib/               #   Libraries
          |_hello3          #    libhello3.a
             |_Howto.cook   #     Contains "make archive;" and "use hello_world;"
             |_hello.h      #     Header of library function
             |_hello.c      #     Implementation of library function

Simple Executable

This is the simplest of all models. All C/C++ files are expected to be self-contained programs, each with their own main() function. They will be compiled and linked into separate executables. The Howto.cook file contains the following:

make simplex;

use c;

The first directive indicates that separate executables are to be compiled. The second directive indicates that C linkage is to be used (as opposed to C++ linkage). That's it.

Complex Executable>

This is probably a more common case. A large executable compiled from several source files. Note that the source file containing main() must be named like the directory - in this case hello_world_2.c. The Howto.cook file contains the following:

make complex;

use c;

There is almost no difference there to the first case. The build behaves totally differently though: All files except for the one containing main() will be compiled and packed into an archive. Then the source file containing main() will be compiled and linked against that archive. The reason for doing that is to allow other executables to link against the code used in this executable, allowing unit tests to be written, for example.

The complex executable model should be the preferred way of building large pieces of code.

Program and Library

As the project grows, you will refactor code into shared libraries. The implicit library in the complex executable may be good enough for small projects, but as soon as there are more than one library, you should switch to this. The top level Howto.cook file contains the following:

cc_I_flags += -Isrc/hello_world_3/lib;

This defines the include file search path. The builder will automatically infer a link dependency every time a header file is included from a library locatioon. Note that paths are always from the top of the source tree.

There are now two parts in this project: a simple executable in bin, and a library in lib/hello3. Note that the directory name will be the library name - and in this case it will build libhello3.a. The Howto.cook file inside contains the following:

make archive;

use c;

When running b in the src/hello_world_3/bin directory, note how it will build the library first, then link it into your program. Examine the last_command convenience symlink to see that it indeed linked the library.

Program using Third Party Product

This example demonstrates one way to include third party products. Here, we created a very simple third party product which consists of only one header file. The remainder of the code is just like hello_world_3, except we changed the Howto.cook file which builds the archive as follows:

make archive;

use c;
use hello_world;

The use hello_world; directive calls upon the definitions included in the distribution, stored in prefix/share/cook/autobuild/include/use/hello_world. These specify how compiler and linker flags will be modified to use the specified third party product. In this case, they just add something to the include file search path. They will also cause the tarball in 3party/noarch/ to be unpacked.

Note that if the third party product contained linker settings, they would automatically be propagated to all invocations refering the library using the third party product. This way, users of library code do not need to know all the details about how the library uses third party code.

Packaging with RPM

The pkg/ subdirectory contains RPM spec files which the builder can use to package up build products.

Packaging is still very much product dependent, and not nearly as amenable to standardized patters as the build. Also, packaging serves as a final sanity check to verify that all components have really been built. Therefore the code in this section is much more explicit about the process than in the remainder of the examples section, and the builder provides a loose framework for organizing this code.

The example here can scale up to a large product - in fact, it was extracted from an actual source tree in real production use.

The pkg/Howto.cook file defines an include search path for rpm spec include files. Interestingly enough, rpm does not provide an obvious mechanism for including files, so the builder implements one based on the C/C++ preprocessor. This allows code to be shared between various rpm spec files, without having to actually declare global macros. In this example, we share two files:

pkg/manifest.spec-inc
Contains a shell function which will generate packing manifests which can be used in the %files section of actual spec files. One thing we do is to include those manifest files in the rpm proper, as this simplifies writing %post and custom %verify scriptlets.
pkg/MANIFEST.preamble
Contains the first lines of the file manifest. This is where one defines %defattr and perhaps %defverify, if custom verification is used.

We then create a directory for every rpm we wish to produce. That directory will contain at least a Howto.cook file and a rpm spec file. It is possible to have multiple spec files in the same directory, but not recommended, since they must all share the same dependencies.

In this example, the pkg/hello_world/Howto.cook file contains this:

make rpm;

pkg/hello_world/[do]/[unix]/%9/hello_world.rpmbuild-output:
  pkg/MANIFEST.preamble
  [defined-or-warn build_target_for_src/hello_world_1/%9]
  [defined-or-warn build_target_for_src/hello_world_2/%9]
  [defined-or-warn build_target_for_src/hello_world_3/bin/%9]
  [defined-or-warn build_target_for_src/hello_world_4/bin/%9]

The make rpm; directive simply causes an rpm to be built. The subsequent lines express the dependencies of the build target. We use the variables above to abstract away what kind of build target is used in the various subdirs. The defined-or-warn function guards against undefined variables caused by b skip, which skips over all the dependent builds for speed.