Chapter 12. Programming in Makefiles

Table of Contents

12.1. Caveats
12.2. Makefile variables
12.2.1. Naming conventions
12.3. Code snippets
12.3.1. Adding things to a list
12.3.2. Echoing a string exacty as-is
12.3.3. Passing CFLAGS to GNU configure scripts
12.3.4. Handling possibly empty variables

Pkgsrc consists of many Makefile fragments, each of which forms a well-defined part of the pkgsrc system. Using the make(1) system as a programming language for a big system like pkgsrc requires some discipline to keep the code correct and understandable.

The basic ingredients for Makefile programming are variables (which are actually macros) and shell commands. Among these shell commands may even be more complex ones like awk(1) programs. To make sure that every shell command runs as intended it is necessary to quote all variables correctly when they are used.

This chapter describes some patterns, that appear quite often in Makefiles, including the pitfalls that come along with them.

12.1. Caveats

  • When you are creating a file as a target of a rule, always write the data to a temporary file first and finally rename that file. Otherwise there might occur an error in the middle of generating the file, and when the user runs make(1) for the second time, the file exists and will not be regenerated properly. Example:

    wrong:
            @echo "line 1" > ${.TARGET}
            @echo "line 2" >> ${.TARGET}
            @false
    
    correct:
            @echo "line 1" > ${.TARGET}.tmp
            @echo "line 2" >> ${.TARGET}.tmp
            @false
            @mv ${.TARGET}.tmp ${.TARGET}
    

    When you run make wrong twice, the file wrong will exist, although there was an error message in the first run. On the other hand, running make correct gives an error message twice, as expected.

    You might remember that make(1) sometimes removes ${.TARGET} in case of error, but this only happens when it is interrupted, for example by pressing ^C. This does not happen when one of the commands fails (like false(1) above).

12.2. Makefile variables

Makefile variables contain strings that can be processed using the five operators ``='', ``+='', ``?='', ``:='', and ``!='', which are described in the make(1) man page.

When a variable's value is parsed from a Makefile, the hash character ``#'' and the backslash character ``\'' are handled specially. If a backslash is followed by a newline, any whitespace immediately in front of the backslash, the backslash, the newline, and any whitespace immediately behind the newline are replaced with a single space. A backslash character and an immediately following hash character are replaced with a single hash character. Otherwise, the backslash is passed as is. In a variable assignment, any hash character that is not preceded by a backslash starts a comment that continues upto the end of the logical line.

The evaluation of variables either happens immediately or lazy. It happens immediately when the variable occurs on the right-hand side of the ``:='' or the ``!='' operator, in a .if condition or a .for loop. In the other cases, it is evaluated lazily.

Some of the modifiers split the string into words and then operate on the words, others operate on the string as a whole. When a string is split into words, it is split like in sh(1).

There are several types of variables that should be handled differently. Strings and two types of lists.

  • Strings can contain arbitrary characters. Nevertheless, you should restrict yourself to only using printable characters. Examples are PREFIX and COMMENT.

  • Internal lists are lists that are never exported to any shell command. Their elements are separated by whitespace. Therefore, the elements themselves cannot have embedded whitespace. Any other characters are allowed. Internal lists can be used in .for loops. Examples are DEPENDS and BUILD_DEPENDS.

  • External lists are lists that may be exported to a shell command. Their elements can contain any characters, including whitespace. That's why they cannot be used in .for loops. Examples are DISTFILES and MASTER_SITES.

12.2.1. Naming conventions

  • All variable names starting with an underscore are reserved for use by the pkgsrc infrastructure. They shall not be used by package Makefiles.

  • In .for loops you should use lowercase variable names for the iteration variables.

  • All list variables should have a ``plural'' name, e.g. PKG_OPTIONS or DISTFILES.

12.3. Code snippets

12.3.1. Adding things to a list

When adding a string that possibly contains whitespace or quotes to a list (example 1), it must be quoted using the :Q modifier.

When adding another list to a list (example 2), it must not be quoted, since its elements are already quoted.

STRING=         foo * bar `date`
LIST=           # empty
ANOTHER_LIST=   a=b c=d

LIST+=          ${STRING:Q}       # 1
LIST+=          ${ANOTHER_LIST}   # 2

12.3.2. Echoing a string exacty as-is

Echoing a string containing special characters needs special work.

STRING=         foo bar <    > * `date` $$HOME ' "
EXAMPLE_ENV=    string=${STRING:Q} x=multiple\ quoted\ words

all:
        echo ${STRING}                  # 1
        echo ${STRING:Q}                # 2
        printf '%s\n' ${STRING:Q}''     # 3
        env ${EXAMPLE_ENV} sh -c 'echo "$$string"; echo "$$x"'   # 4

Example 1 leads to a syntax error in the shell, as the characters are just copied.

Example 2 quotes the string so that the shell interprets it correctly. But the echo command may additionally interpret strings with a leading dash or those containing backslashes.

Example 3 can handle arbitrary strings, since printf(1) only interprets the format string, but not the next argument.

In example 4, the EXAMPLE_ENV does not need to be quoted because the quoting has already been done when adding elements to the list.

12.3.3. Passing CFLAGS to GNU configure scripts

When passing CFLAGS or similar variables to a GNU-style configure script (especially those that call other configure scripts), it must not have leading or trailing whitespace, since otherwise the configure script gets confused. To trim leading and trailing whitespace, use the :M modifier, as in the following example:

CPPFLAGS=               # empty
CPPFLAGS+=              -Wundef -DPREFIX=\"${PREFIX}\"
CPPFLAGS+=              ${MY_CPPFLAGS}

CONFIGURE_ARGS+=        CPPFLAGS=${CPPFLAGS:M*:Q}

all:
        echo x${CPPFLAGS:Q}x            # leading and trailing whitespace
        echo x${CONFIGURE_ARGS:Q}x      # properly trimmed

In this example, CPPFLAGS has both leading and trailing whitespace because the += operator always adds a space.

12.3.4. Handling possibly empty variables

When a possibly empty variable is used in a shell program, it may lead to a syntax error.

EGFILES=        # empty

install-examples:   # produces a syntax error in the shell
        for egfile in ${EGFILES}; do            \
                echo "Installing $$egfile";     \
        done

The shell only sees the text for egfile in ; do, since ${EGFILES} is replaced with an empty string by make(1). To fix this syntax error, use one of the snippets below.

EMPTY=          # empty

install-examples:
        for egfile in ${EGFILES} ""; do         \
                [ -n "$$egfile" ] || continue;  \
                echo "Installing $$egfile";     \
        done

In this case, an empty string is appended to the iteration list (to prevent the syntax error) and filtered out later.

EGFILES=        # empty

install-examples:
.for egfile in ${EGFILES}
        echo "Installing ${egfile}"
.endfor

This variant only works when EGFILES does not contain filenames with spaces, since the .for loop splits on simple whitespace.

To have a shell command test whether a make variable is empty, use the following code: ${TEST} -z ${POSSIBLY_EMPTY:Q}"".