Chapter 7. Tutorial

Table of Contents

7.1. Project skeleton
7.2. Package definitions
7.3. The docs function
7.4. The configuration script
7.5. Top level logic script
7.6. The sample library
7.6.1. The logic script
7.7. The sample program
7.7.1. The logic script
7.8. The pkgflags file
7.9. Finished

This chapter provides an introductory tutorial for developers. During this, we will create a very simple package from the ground up, which will provide a library (either shared or static) and a binary program. You should be familar with the Buildtool command line before continuing; if you are not, go back to Part I, “User's documentation” and read the chapters there again.

7.1. Project skeleton

Let's get started by creating the basic directory structure for our new project and populating it with the Generic.bt script and several Logic.bt files (see Chapter 8, Script files). We could do this by hand, but the bt_wizard module (see Chapter 16, The project wizard), will generate this basic skeleton for us. To start the wizard, enter a directory were you will be storing all our projects and then call the wizard through the Buildtool's wrapper (see Chapter 2, The buildtool command):

~$ cd ~/projects
~/projects$ buildtool wizard

The program will welcome you and will start asking questions about your new project. Attached below is a sample session with the answers we will use for our tutorial. If a question is left blank, it takes the default value (shown between square brackets):

Project definitions:
- Unix name [foobar]? tutorial
- Initial version [0.1]? 1.0
- License [bsd]?
- Maintainer's email [foo@bar.net]? me@somehost.org
- Homepage (if any) []?
- Short comment [Sample package]? Buildtool's Tutorial

Code details:
- Will this package provide one or more programs [y]?
- Will this package provide one or more libraries [n]? y
- Will you use the C language [y]?
- Will you use the C++ language [n]?
- Will you use CVS [y]?

Dependancies:
- Do you need pkgconfig (not bt_pkgflags) [n]?
- Do you need threads [n]?
- Do you need an X Window System [n]?
- Do you need awk [n]?
- Do you need a lexical analyzer [n]?
- Do you need a LARL parser generator [n]?

Begin process:
- Where should files be created [.]? tutorial

After you answer the last question (be careful to change the default if you do not want to end up with several files in the current directory), the program will generate the project skeleton and place some files inside it, based on your answers. Let's describe what those files are:

Generic.bt

The main Buildtool script. Includes package definitions (see Chapter 9, The definitions script), the configuration script (see Chapter 11, The configuration script) and the main logic script (see Chapter 12, Logic scripts).

README.bt

A short text file to tell users that this package needs Buildtool to be built and where to get more information. You might remove it, but it will help end users to know what tool they need to build your package.

src/Logic.bt

The script that will control the build process of our binary program.

lib/Logic.bt

The script that will control the build process of our library.

data/Logic.bt

The script that will control the build process of our pkgflags file for the library.

data/tutorial.bpf.in

The pkgflags file for the library (see Chapter 15, Package flags).

7.2. Package definitions

The defs function in the Generic.bt file defines (hence the name) several variables that provide general information about your project. This information corresponds to the first block of questions you answered while running the wizard. We will not touch this since the wizard has placed correct values in it. Here is a copy of what the function should look like:

defs() {
    BT_REQUIRE="0.16"

    BT_PKG_NAME="tutorial"
    BT_PKG_VERSION="1.0"
    BT_PKG_LICENSE="bsd"

    BT_PKG_MAINTAINER="foo@bar.net"
    BT_PKG_COMMENT="Sample package"
}

These variables are self descriptive. If you have doubts, refer to Chapter 9, The definitions script which contains a detailed explanation for each of them.

7.3. The docs function

It is very important for a software package to provide documentation to support its build process (also known as distribution documentation). This includes a file explaining what the program is, an outline on how to build and install it, a list of changes between versions, a list of authors and contributors, etc. Please note that documentation specific to how to configure the program, how it works, etc. does not fit this category.

The bt_doc module (see Chapter 3, Build time documentation) is a simple documentation reader for this information; the user will be shown a menu with a list of documents, and given a chance to read them. The docs function in the Generic.bt is used to register such documentation in the reader's menu.

The addition of documents to the menu is done using the bt_doc function (see Chapter 10, The documentation script). The wizard generates a function for you, with some reasonable defaults:

docs() {
    bt_doc CHANGES "Major changes between package versions"
    bt_doc PEOPLE "Authors and contributors"
    bt_doc README "General documentation"
    bt_doc TODO "Missing things"
}

As we are listing all these documentation files, they must exist; if they don't, the installation of the package will fail, because they are automatically copied to BT_DIR_DOC by Buildtool. So we create them:

~/projects/tutorial$ touch CHANGES PEOPLE README TODO

Remember to fill these documents with useful information! There are lots of software packages out there that simply create these empty files to shut up their build infrastructure (i.e., automake). If you do not want to provide them (reconsider your decision), then simply avoid their registration in the menu.

7.4. The configuration script

Each package you create needs a configuration script, even if you do not need to detect specific system features yourself. The script, when executed, will initialize Buildtool for the project so that future calls to bt_logic work.

The configuration script generated by the wizard is included in the config function of the Generic.bt file. If you check the file now, you will see that it does almost nothing; it just checks for a C compilation environment, generates the pkgflags file (so that macros get replaced in it), and generates a configuration header file (bt_config.h) to be included from C sources.

To make things more interesting, we will make use of the new C99 stdbool.h header and the basename function. These are standard, but may not exist in old systems, so we must check if they exist and workaround them if they don't. To check for the header file, we will use the bt_check_hdr function (see Section 11.7.3.1, “Generic headers”), and to check for the function, bt_check_func (see Section 11.7.4.1, “Generic functions”).

So, our config function will end up like this:

config() {
    bt_check_env_c

    bt_check_hdr stdbool.h
    bt_check_func basename

    bt_generate_output data/tutorial.bpf
    bt_generate_configh
}

Note that you can safely remove the config_init function from the script, since we will not be using it.

7.5. Top level logic script

We have got three directories so far in our project, which will contain source files. This means that they will need to take some actions during the build and install stages (between others). We have two alternatives to handle this situation: we can let the top level logic script build everything directly or have a logic script inside each directory. In this tutorial, we opt for the second choice, as it is clearer.

As we will have a logic script for each directory, we need to recurse inside them to execute the right stage. This is done with a very simple top level logic script, embedded inside Generic.bt, that will list those subdirectories as targets:

logic() {
    bt_target lib data src
}

See Chapter 12, Logic scripts for more information.

7.6. The sample library

We will now write the code for our sample library; the function shown below is useless, but is enough to demostrate how to do things. It will be stored into the lib/aux.c file. Our code will make use of the basename function we previously detected.

#include <bt_config.h>

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "aux.h"

static char *filename = NULL;

int
set_filename(const char *fn)
{
#ifdef BT_HAVE_FUNC_C_BASENAME
    filename = strdup(basename(fn));
    return 1;
#else
    return 0;
#endif
}

As usual with C libraries, we have to write a header file with the prototype for our function; this will be stored in lib/aux.h:

#ifndef AUX_H
#define AUX_H

int set_filename(const char *);

#endif /* AUX_H */

7.6.1. The logic script

With the library code in place, we have to write the logic script that will build and install it; it will be stored in lib/Logic.bt. The script will define a single target, using the library type (see Section 12.7.2, “The library type”) to attach the proper stages to it. This will take care to build shared or static libraries (or both) depending on what Buildtool found at configuration time.

Our target defines the library version, the list of sources that need to be built and linked to generate the library, and the list of header files that need to be installed into the system. We will also change the default directory installation (for headers) to include a subdirectory (this is the most common practice, to avoid mixing header files from different libraries).

logic() {
    bt_target auxlib

    target_auxlib() {
        BT_TYPE=library

        # Library version information.
        BT_LIB_MAJOR=1
        BT_LIB_MINOR=2
        BT_LIB_MICRO=5

        # Source files that form the library code.
        BT_SOURCES=aux.c

        # Header files that will be installed in the system.
        BT_INCLUDES=aux.h
        # We want them into our own subdirectory.
        BT_INCLUDESDIR=${BT_DIR_INCLUDE}/auxlib
    }
}

7.7. The sample program

Our sample program will simply call the set_filename function defined by our sample library, use a boolean value and print an error if the function returns false; as we saw above, this will only happen if the basename function does not exist in the host system (detected during the configuration stage). The code to do this is trivial, as seen below. We will store it as src/main.c:

#include <bt_config.h>

#include <stdio.h>
#include <stdlib.h>

#ifdef BT_HAVE_HDR_C_STDBOOL_H
#include <stdbool.h>
#else
#define bool int
#define true 1
#define false 0
#endif

#include <aux.h>

int
main(int argc, char **argv)
{
    bool b;

    if (set_filename(argv[0]))
        b = true;
    else {
        b = false;
        fprintf(stderr, "set_filename failed (basename not present)\n");
    }

    return b ? EXIT_SUCCESS : EXIT_FAILURE;
}

To make the example more complete, we will provide a manual page for our program. Since writting it is out of the scope of the tutorial, we simply create an empty file for it:

~/projects/tutorial$ touch src/myprog.1

7.7.1. The logic script

With the program code in place, we have to write the logic script that will build and install it; it will be stored in src/Logic.bt. The script will define a single target, using the program type (see Section 12.7.1, “The program type”) to attach the proper stages to it.

logic() {
    bt_target myprog

    target_myprog() {
        BT_TYPE=program

        BT_SOURCES=main.c
        BT_MANPAGE=myprog.1

        # Link against our sample library.
        BT_FLAGS_CPP+=-I../lib
        BT_FLAGS_LD+=-L../lib
        BT_LIBS+=-lauxlib
    }
}

7.8. The pkgflags file

Whenever you create a library that will be installed on the system, you should also provide a pkgflags file so that linking to it is easy and painless for other developers (they will only need a call to bt_check_pkgflags from their configuration script; see Section 11.7.6.4, “Package flags”). The wizard is aware of this, so it will create a sample pkgflags file for you, together with a ready to use logic file. Both of these are located in the data directory.

The generated pkgflags file (data/tutorial.bpf.in) needs to be adjusted to your library by hand; particullary, the bpf_libs and bpf_cflags lines need to be uncommented and modified to suit it.

The following is how our pkgflags file will look after beeing modified:

BT_PREFIX="@BT_PREFIX@"
BT_DIR_LIB="@BT_DIR_LIB@"
BT_DIR_INCLUDE="@BT_DIR_INCLUDE@"

bpf_name="@BT_PKG_NAME@"
bpf_descr="@BT_PKG_COMMENT@"
bpf_version="@BT_PKG_VERSION@"
bpf_libs="-L${BT_DIR_LIB} -lauxlib"
bpf_cflags="-I${BT_DIR_INCLUDE}/aux"

For more information about the variables defined in this file, see Section 15.2, “Pkgflags file format”.

7.9. Finished

We are done! If everything went fine, you should be able to read documentation about your package, configure, build, install, deinstall and clean it without problems. Now keep reading all other chapters addressed to the developer.