Introduction

Instead of separately documenting each build template (i.e. program, library, sub directory recursion, etc.) we will illustrate a complete (yet absolutely pointless) mixed C/C++ project that will let us understand how all the available build patterns get together to implement the final build tree.

A sample project

Suppose we are creating a project named test which provides a main tool called prog and related man pages. The tool depends on some custom routines grouped in form of a C/C++ library and on a couple of external tool which have their own build system and needs perhaps to be patched in order to suit our needs.

Let’s start by creating a sensible file system layout, like the following,

test/
    prog/       # sources for the prog(1) tool
    man/        # man page(s) documenting the prog(1) tool
    libx/       # coustom routines needed by prog(1)
    include/    # header files for the library
    autoprog/   # an external dependency (autotools based)
    party/      # 3rd party dependencies handled by MaKL

Top Level Makefile's

The top level Makefile is nothing but a dispatcher of build actions across all the project directories. It includes the subdir.mk template and populates the SUBDIR variable with the list of directories with stuff that needs to be built.

SUBDIR = lib party prog autoprog include man

include subdir.mk

All the directories listed in a SUBDIR variable must expose a Makefile that will be called with the goal requested by the user (e.g. all, clean, cleandepend, install, etc.).


NOTE: MaKL tries to enforce a neat interface, exposing a coherent set of targets which are made available throughout all its template files. Here they are (quite self-explainatory):

  • all and clean
  • install and uninstall
  • depend and cleandepend

MaKL users are clearly encouraged to use them, as they tend to be convenient in most cases. Anyway, should you need (1) to add a different target name for some special action, or (2) to give another semantics for one of the standard targets, that will be obtained quite straightforwardly: since MaKL files are plain GNUMake files, the target override capabilities are completely retained. Just mind the relative order between the template includes and target (re)definition to get the expected result.


Program

The prog(1) Makefile can be as simple as that:

include common.mk
include ../Makefile.conf

PROG = prog
SRCS = prog.c
LDADD += ../lib/libx.a

include prog.mk

Plus an optional add-ons section:

clean-hook-pre:
    $(ECHO) "do something here, just before clean up"

Here - as in most MaKL Makefiles - we can clearly identify three different sections.


  • Global and per-project settings
include common.mk          # MaKL global settings
include ../Makefile.conf   # per-project settings (override globals)

Each MaKL Makefile shall begin with the “include common.mk” directive, which tells GNU Make to pick up MaKL global varialbles’ settings, basically: toolchain variables (CC, CFLAGS, LD and friends) together with various system paths (DESTDIR, BINDIR, etc.) and the corresponding credentials (BINOWN, LIBGRP, etc.). Since these global variables can be overwritten (the “=” assignement operator), or sometimes enriched (using the “+=” operator) on a per-project/per-module basis, this include has to be placed on top: following includes or straight variable settings will possibly override these values.

Quite usually, medium-to-large sized project will have their own top level Makefile.conf with project specific settings (in most cases it will be automatically created by an autoconfiguration script) which need to be included just after the common.mk and before any per-module setting.


  • Module specific settings
# (override per-project and globals' settings)
PROG = prog
SRCS = prog.c
LDADD += ../lib/libx.a

Each build template exposes its unique set of variables, by which the developer customizes the build action. Template specific variables usually set sensible defaults, but this is not always true, e.g. the prog.mk template needs that you give values for at least the following:

variable semantics
PROG the name of the executable that will be produced
SRCS the list of C/C++ files which will be compiled and linked to produce $(PROG)

Other prog.mk variables that you can optionally set (depending on your needs) are:

variable semantics example default value
USE_CXX if defined (i.e. set to some non-void value) this will tell MaKL to use the C++ compiler instead of plain C compiler to drive the linking stage USE_CXX=trueunset
LDADD set this with libraries (archive files) which your program depends on (linking ops) LDADD+=../lib/libx.a unset
DPADD set this with files which your program depends on (will feed the depend target) DPADD+=../lib/libx.a unset
LDFLAGS additional loader flags LDFLAGS=-static depends on host or target platform 1)
CLEANFILES add specific files that need to be removed on a clean goal invocation CLEANFILES+=.done $(PROG) and $(OBJS)
CFLAGS flags to the C compiler CFLAGS+=-g -DDEBUG depends on host or target platform
CXXFLAGS flags to the C++ compiler CXXFLAGS+=-fdollars-in-identifiers depends on host or target platform
BINOWN owner of the installed binary BINOWN=nobody interactive user credentials or $(DEFOWN) if set
BINGRP group of the installed binary BINGRP=nobody interactive user credentials or $(DEFGRP) if set
BINMODE mode of the installed binary, either an octal or symbolic value BINMODE=0755 0555 (i.e. ugo=rx)
BINDIR installation directory BINDIR=$(LIBEXDIR) $(DESTDIR)/bin


  • Specify the MaKL build template
include prog.mk

Here we are telling GNU Make to load MaKL program template, i.e. a set of targets and corresponding actions that will be used to generate the executable. Note that this comes last in order to let all kind of variable settings (i.e. global, per-project and per-module) to take place before actual building.

In case you need to override or hook-up a MaKL target (i.e. one of all, clean, depend, cleandepend, install, uninstall) or define your target, this will be safely accomplished by placing relevant code just after this section.


  • Optionl add-ons

Add-ons comes in three different flavours: hook, redefinition and new target.

# hook
clean-hook-pre:
    $(ECHO) "do something here, just before clean up"

Each MaKL target has hooks that can be used to install user code before and after each MaKL action. Hooks follow this typo pattern: <action>-hook-pre for code to be inserted before action takes place, and <action>-hook-post for code that shall be executed after the specific action (one of the already mentioned MaKL targets).

# new target
test:
    $(ECHO) "I feel lazy today and don't want to stress a single bit..."

In this case you just state your target and related actions.

# ... variable settings ...

# default install target inhibition
NO_INSTALL = true

include prog.mk

# install target redefinition
install:
    $(ECHO) "my custom install directive"

Since GNU make does not allow overriding an already defined target, resetting MaKL default targets’ action to some user defined behaviour, needs some special trick. For each default target, MaKL has a special variable which, if given an arbitrary non-void value (e.g. true), resets the action(s) associated with that target to the null action. All MaKL template files expose the following variables: NO_CLEAN, NO_DEPEND, NO_CLEANDEPEND, NO_INSTALL, NO_UNINSTALL that come in handy in the aforementioned example, where, by setting one of them just before including the needed template, raises the possibility to override the corresponding target with user defined actions later on.

Library

Let’s go back to our example and see how the library template works.

As we have seen before, the prog(1) program depends on some mixed C/C++ code which has been factored out and packaged into a library named libx. If we cd libx/ and cat Makefile that’s what we get:

include common.mk
include ../Makefile.conf

LIB = x
SRCS = a.c b.c x.cc

USE_CXX = true
# '+=' => in order to inherit default CFLAGS settings
CXXFLAGS += -fdollars-in-identifiers

include lib.mk

ifdef OS_DARWIN
install-hook-post:
    $(RANLIB) $(LIBDIR)/lib$(LIB).a
endif

Note that the same basic structure as in the program example (e.g. preamble, variables’ settings and lib.mk inclusion) is retained. There is also an add-on section with code needed to work around an ld(1) bug on Darwin. This hook code will be executed after all the default install actions have been carried out, and just in case the OS_DARWIN variable have been set (perhaps in the top level Makefile.conf by the auto configuration script).

The lib.mk template needs that at least the two following variables are set:

variable semantics
LIB the name of the library (will be suffixed with lib)
SRCS the list of C/C++ source files with API and internal routines

By default MaKL builds the static archive library. If we’d want to get a shared version of the x library we’d have to add the following settings before including the lib.mk template:

...
SHLIB = true
SHLIB_MAJOR = 1   # set this as appropriate
SHLIB_MINOR = 4   # ...
SHLIB_TEENY = 2   # ...
...

In the example above we’ve also set the USE_CXX variable so that the C++ compiler is used in the dynamic library assembly step. This is needed anytime the SRCS variable contains a C++ file and SHLIB is also defined.

On Darwin OS, MaKL defines two variables which shall be used in case the Mach-O bundle format needs to be produced - as opposed to the dynamic library format, which is the default:

variable semantics
BUNDLE if set a Mach-O bundle is created
BUNDLE_EXT extension for the produced bundle file (e.g. kext for kernel modules)

Other lib.mk variables that you can optionally set (depending on your needs) are:

variable semantics example default value
USE_CXX same meaning as in the prog.mk template USE_CXX=trueunset
CLEANFILES same meaning as in the prog.mk template CLEANFILES+=.done $(OBJS) and the library archive file
CFLAGS same meaning as in the prog.mk template CFLAGS+=-g -DDEBUG depends on host or target platform
CPICFLAGS shared library extra compiler flags CPICFLAGS+=-fPIC depends on host or target platform
LIBOWN owner of the installed library LIBOWN=admin interactive user credentials or $(DEFOWN) if set
LIBGRP group of the installed library LIBGRP=admin interactive user credentials or $(DEFGRP) if set
LIBMODE mode of the installed library, either an octal or symbolic value LIBMODE=0644 0444 (i.e. ugo=r)
LIBDIR installation directory LIBDIR=$(DESTDIR)/private/lib $(DESTDIR)/lib

Shared libraries use some other variables mostly related to shlib naming conventions. Those variables will not be documented here because they heavily depend on the target platform. Should you need some special behaviour, try hacking in the shlib/ directory.

Manual Page(s)

Our sample project groups all its UNIX Manual pages into one directory (man/). This is a practice which MaKL tries to enforce via the man.mk template logic.

As usual, the developer communicates with MaKL through the set of variables exposed by the included template (i.e. man.mk), which provides back a set of standard actions. In this case only the install and uninstall targets have some associated action (those that you can easily imagine), while all other MaKL standard targets are available through their -pre and -post hooks.

include common.mk
include ../Makefile.conf

MANFILES = prog.1 libx.3

# MLINKS = {<file> <symlink>}i=0...n
MLINKS += prog.1 progalias.1
MLINKS += libx.3 foo1.3
MLINKS += libx.3 foo2.3
MLINKS += libx.3 foo3.3

include man.mk

What the above example does is declaring a couple of real UNIX manual pages (prog.1 and libx.3) through the MANFILES variable, and then their corresponding symlinks (progalias.1 and foo[1-3].3) through MLINKS.

MANFILES is the only mandatory variable in the man.mk template, and must hold files with a suitable extension, i.e. the manual section indicator. The MLINKS variable has special semantics. It’s a function-args variable2) whose values are used as parameters to an internal processing loop: at each iteration a couple of values are passed as arguments to the link function. This is a simple and powerful approach, however it must be handled with some caution since one single stepping error breaks the following processing chain.

Other man.mk variables that you can optionally set (depending on your needs) are:

variable semantics example default value
MANOWN owner of the installed man pages MANOWN=man interactive user credentials or $(DEFOWN) if set
MANGRP group of the installed man pages MANGRP=man interactive user credentials or $(DEFGRP) if set
MANMODE mode of the installed man pages, either an octal or symbolic value MANMODE=0644 0444 (i.e. ugo=r)
MANDIR installation directory MANDIR=$(DESTDIR)/private/share/man $(DESTDIR)/share/man

Headers

All our test project headers are kept into a separate directory (include/) that works as a common container for at least the project’s header files that need to be externalized, i.e. made public between modules and perhaps to the whole system.

If we go straight peeking at its Makefile, that’s what we get:

include common.mk
include ../Makefile.conf

INCS = a.h b.h x.h

include incs.mk

which is indeed a very small and simple object.

The incs.mk template has two only precooked actions (install and uninstall) and a single mandatory variable (INCS) to drive the whole build mechanism. The INCS variable holds the list of the header files in the directory, but even outside it (absolute or relative path), that need to be installed in the system.

Like nearly every MaKL template we’ve been playing with, the incs.mk template too has variable that allow to customize the location, credential and permissions of the installed objects:

variable semantics example default value
INCOWN owner of the installed header files INCOWN=admin interactive user credentials or $(INCOWN) if set
INCGRP group of the installed header files INCGRP=admin interactive user credentials or $(INCGRP) if set
INCMODE mode of the installed header files, either an octal or symbolic value INCMODE=0644 0444 (i.e. ugo=r)
INCDIR installation directory INCDIR=$(DESTDIR)/proj $(DESTDIR)/include

External Dependency

Configuration, Compilation and Installation of External Dependencies can be handled by using the party.mk template.

Suppose that the previously mentioned test project depends on packages libfoo and libbar. One would create two subdirectories libfoo/ and libbar/, each with an appropriate Makefile which describes how the packages should be built and/or installed.

Let libfoo be a package to be downloaded from the official Internet site (www.libfoo.org) and installed. A sample Makefile for such dependency follows:

include ../Makefile.conf

PARTY_NAME = libfoo
PARTY_URL = http://www.libfoo.org/download

PARTY_ARGS = --prefix=$(PARTY_CONF_PREFIX)

include party.mk

Makefile.conf is a base level Makefile containing the common configuration for all 3rd party dependencies (in this case only PARTY_CONF_PREFIX which defines the prefix for installation of dependencies). This file may be static or generated dynamically by a configure script. Given the PARTY_URL shown above, the file named http://www.libfoo.org/download/libfoo.tar.gz will be retrieved by default. This default value can be changed by modifying the PARTY_FILE variable. PARTY_DECOMP and PARTY_DECOMP_ARGS can be used to set the command used for decompression and its arguments (if different from tar with gzip compression). The package will be then configured using the script named ./configure in the base directory of the decompressed file and installation prefix PARTY_CONF_PREFIX.

Let’s have a look at another sample dependency, libbar. The source code for this package is included within the test package and should not be downloaded remotely. Its Makefile is shown below:

include ../Makefile.conf

PARTY_NAME = libbar
PARTY_NO_DOWN = true
PARTY_PATCH = libbar-patch.diff

PARTY_CONF = ./conf.sh
PARTY_ARGS = --prefix=$(PARTY_CONF_PREFIX) --enable-cool-feature

include party.mk

PARTY_NO_DOWN disables package download. The file libbar.tar.gz is expected to be found in the current directory. The default variables can be overridden as mentioned in the previous paragraph. The package is decompressed and patched using the ‘diff’ file given by the value of PARTY_PATCH. The default configuration script command ./configure is overwritten by PART_CONF and an extra argument –enable-cool-feature has been added to the configuration arguments PARTY_ARGS.

In order to install both of the above packages automatically, the top-level Makefile simply needs to use the subdir.mk template as follows.

SUBDIR = libfoo libbar

include subdir.mk

As a result of building the 3rd party dependencies using party.mk, a log file named party.log is generated in each directory. The path of these files can be modified by setting PARTY_LOG.

1) See installed toolchain.mk file
2) MaKL has other variables with a similar behavior, e.g. SUBST_RULE in the subst.mk template, and DISTREMAP in dist.mk
 
makl_proj.txt · Last modified: 2007/10/29 09:44
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki