wiki:CaffeinatedMake

Version 3 (modified by kaduk, 14 years ago) (diff)

typo found during talk

The debian/rules file in a Debian package uses GNU Make syntax. Most make syntax is straightforward, but there are several places where it differs from shell syntax.

About make

Make is a system for running certain shell commands under certain conditions: in particular, these conditions can involve previously running other commands. It is most often used for compiling large programs (e.g., "to build myprogram, use these commands which require main.o and library.o to be built; to build a .o file from a .c file, use this command"), because it can keep track of what work it has already done. However, it also turns out to be a good fit for the process of building a package.

A simple Makefile

foo: foo.o
        gcc -o foo foo.o
foo.o: foo.c foo.h
        gcc -c foo.c

You can build foo by running "make foo". If "foo" doesn't exist, or is older than "foo.o", this will execute the first gcc line. But first, if foo.o doesn't exist, or is older than either foo.c or foo.h, it will execute the second gcc line. If either foo.c or foo.h exist, Make will given an error that it, quite understandably, doesn't know how to create those files.

A simple Makefile, debian/rules edition

#!/usr/bin/make -f

install: build
        DESTDIR=$(CURDIR)/debian/$(package) make install

build:
        ./configure --prefix=/usr
        make

binary: install
        dh_installdocs
        dh_installinit
        ...

This is (part of) a debian/rules file using debhelper. When you tell the build system (dpkg-buildpackage in particular, but you'll probably be using debuild or sbuild on top of that) to build a package, it will run debian/rules binary to create the binary package. This is why the file is executable and has a "shebang line" indicating that it should be run with make; you don't actually call the "make" command.

Debhelper is a higher-level tool for building packages than constructing the entire package yourself. All the debhelper commands start with dh_; you can run ls /usr/bin/dh_* to see the list. The traditional way to use debhelper is to have the "binary" target run a bunch of debhelper commands that set up all the things needed by the package -- documentation, window system menu configuration, shared library setup, initscripts, etc. etc. Debhelper commands are generally configured by appropriately-named other files in the debian/ subdirectory.

Note that in this example, building the "binary" target requires the "install" target to be successfully built, and that requires the "build" target.

A simple debian/rules Makefile, CDBS edition

#!/usr/bin/make -f

include /usr/share/cdbs/1/rules/debhelper.mk
include /usr/share/cdbs/1/class/autotools.mk

DEB_DH_INSTALLINIT_ARGS=--no-restart-on-upgrade

CDBS, the Common Debian Build System, is a higher-level system on top of (usually) debhelper. You include some CDBS rules and class files to say what type of rules you want and what class of package you're building, and it goes off and sets up the right targets and calls basically every dh_* script. "autotools.mk" indicates we're using a standard autotools-based package (i.e., ./configure; make; make install), and CDBS will do the right things for it (--prefix, DESTDIR, etc.). This is a complete CDBS-based debian/rules.

To configure things in CDBS, you generally either set variables or use double-colon rules (see below). In this case we tell CDBS to call dh_install with --no-restart-on-upgrade.

Syntax

Make is a bit picky about syntax. Here's what you need to know, but  the official manual is handy.

Tabs vs Spaces

In Makefiles, tabs are used to delineate rules inside a target (e.g., the gcc lines in the first example). You must use a tab, not eight spaces.

When intending Make-syntax code (within a conditional, for example), use spaces, not tabs, to make it clear that it's not a target. Emacs can either make this simple or more confusing. If you get lost, or get weird syntax errors, try M-x whitespace-mode in Emacs, which will represent spaces as "·" and tabs as "»".

Targets

Makefiles primarily contain "targets", which specify what actions to take to build a certain component of the package. CDBS sets up a  large number of targets that you can hook if you need to.

If you're hooking a CDBS target, you generally use a double colon, because Make will not otherwise let you define a target twice (see the section on  multiple rules in the manual). Suppose you had a program with a buggy Makefile, that required you to run "make dependency" before you ran normal "make" (and didn't specify this dependency). You could work around it in the packaging like so:

#!/usr/bin/make -f

include /usr/share/cdbs/1/rules/debhelper.mk
include /usr/share/cdbs/1/class/autotools.mk

configure/mypackage::
        make dependency

This will cause the built-in rules for the configure/mypackage to be run first (i.e., ./configure), then yours, before it goes on to make/mypackage. Note that because Debian source packages can build multiple binary packages, CDBS adds the binary package name to the target.

When creating your own targets, especially if you're building files in your debian/rules (not generally recommended but sometimes acceptable), you use single colons as usual.

Variables

Make variables are not shell or environment variables. Make inherits variables from the environment, but does not set them (unless you export the variable, much like the shell).

Variable names are generally in all caps by convention.

FOO = bar

would set variable FOO to bar.

You can append to a variable with +=

FOO += baz

Variable FOO now contains "bar baz".

To refer to variables, you enclose them with parens and a dollar sign:

$(FOO)

Functions

Functions in make are called with $(function argument0 argument1).

The shell function allows you to run a shell command and returns the output:

ARCH = $(shell uname -m)

would set ARCH to the hardware type.

The wildcard function is a Make function that supports globbing, but also can be used to test for files. For example, $(wildcard /etc/gdm/gdm.conf-custom) could be used to test for the presence of that file on the build system. You can compare its output against the empty string to see if the file is absent.

Conditionals

The most common conditionals are ifeq and ifneq which test for equality and inequality, respectively. Using the ARCH variable from the example above, you could do the following:

ifeq ($(shell uname -m),x86_64)
        #  do 64-bit stuff
else
        #  do something else
endif

As in most languages, the else clause is optional.