textfiles

Various textfiles on technical topics
git clone https://git.bracken.jp/textfiles.git
Log | Files | Refs

idiomatic_makefiles.md (10404B)


      1 # A Template for Portable Idiomatic Makefiles
      2 
      3 2022-04-07 [Original at seninha.org][original]
      4 
      5 In this post, I present the template I use to write portable, idiomatic
      6 Makefiles for building C programs.
      7 
      8 I use `${MYVAR}`, with curly braces, rather than `$(MYVAR)`, with parentheses,
      9 but both notations are supported. In this document I identify two different
     10 people: the developer or Makefile author, who writes the Makefile; and the user
     11 or package maintainer, who defines the proper variables and run `make(1)`.
     12 
     13 ## The Project Files
     14 
     15 There are several files involved in the building process: the target files to be
     16 built, the source files, and the intermediate files. They may be referenced
     17 several times by different rules, so it is a good practice to name them with
     18 variables. Suppose we're building a program called `myprog` composed of three
     19 modules. These are the variables to be defined:
     20 
     21 ```Makefile
     22 PROG = myprog
     23 SRCS = main.c parse.c util.c
     24 OBJS = main.o parse.o util.o
     25 ```
     26 
     27 The variable `PROG` is the final file; `SRCS` lists the source files; and `OBJS`
     28 lists the intermediate, object files. Note that both the list of source files
     29 and of object files are almost equal, differing only by the extension of the
     30 files. POSIX `make(1)` has a notation for changing the ending of each word in a
     31 variable. In order to avoid repeating ourselves, we can use this notation to
     32 define `${OBJS}`.
     33 
     34 ```Makefile
     35 PROG = myprog
     36 SRCS = main.c parse.c util.c
     37 OBJS = ${SRCS:.c=.o}
     38 ```
     39 
     40 We want to build our program when we call make without any arguments. To do
     41 this, the first target should be `${PROG}` itself. However, it is a common
     42 practice to use the target `all` to build the final files. So the first target
     43 is `all`, which just has `${PROG}` as prerequisite.
     44 
     45 ```Makefile
     46 all: ${PROG}
     47 ```
     48 
     49 Next we need to declare the dependencies between the program modules. This is
     50 done with rules without commands.
     51 
     52 ```Makefile
     53 main.o: parse.h util.h
     54 parse.o: parse.h util.h
     55 util.o: util.h
     56 ```
     57 
     58 ## The Compilation Rules
     59 
     60 The compilation process is split in two parts: generate the object files from
     61 the source files, and generate the program from the object files. So we need two
     62 rules.
     63 
     64 The following rule builds object files (.o) from source files (.c). The `.c.o`
     65 is an inference rule that declares each .c file to be the prerequisite of a
     66 homonymous .o file. This notation is defined by POSIX and is, therefore,
     67 portable (different from the `%.o`: `%.c` rule, which is a GNU extension). In
     68 the command of an inference rule (and only in the command of an inference rule),
     69 the `$<` variable evaluates to the prerequisite file.
     70 
     71 ```Makefile
     72 .c.o:
     73         ${CC} -I/usr/X11R6/include ${CFLAGS} ${CPPFLAGS} -c $<
     74 ```
     75 
     76 We use the variable `${CC}` to expand to the proper C compiler command. This
     77 variable is set by default to the proper command. We should use this variable
     78 rather than hardcoding it to `gcc`, for example.
     79 
     80 The variables `${CFLAGS}` and `${CPPFLAGS}` contains options that the user or
     81 package maintainer wants to pass to the compiler or preprocessor. It is a bad
     82 practice to define those variables in a Makefile; let the user (or package
     83 maintainer) define them. Any option that must be passed to the compiler (such as
     84 `-I/usr/X11R6/include` above) should be passed before those variables. If the
     85 Makefile author, for example, define `${CFLAGS}` to `-I/usr/X11R6/include`,
     86 either this value may override the values set by the user, or the values set by
     87 the user may shadow the option set by the Makefile author.
     88 
     89 The following rule links all object files into the program. We define `${OBJS}`
     90 to be the prerequisites of `${PROG}`. The `$@` variable evaluates to the target
     91 file (`${PROG}` in our case). Since this is not an inference rule, the `$<`
     92 variable cannot be used; we must write `${OBJS}` both in the rule and in the
     93 command.
     94 
     95 ```Makefile
     96 ${PROG}: ${OBJS}
     97         ${CC} -o $@ ${OBJS} -L/usr/X11R6/lib -lX11 ${LDFLAGS}
     98 ```
     99 
    100 The variable `${LDFLAGS}` contains options that the user or package maintainer
    101 wants to pass to the linker. Again, it is a bad practice to define it in the
    102 Makefile. The options `-L/usr/X11R6/lib` and `-lX11` are passed before this
    103 variable (so the user can override or increment them if necessary).
    104 
    105 ## The Installation rules
    106 
    107 Your Makefile may include rules for installing the final files in the system. In
    108 this example, two files are installed, `${PROG}` (the final, compiled program),
    109 and `${PROG}.1` (the manpage, named as the program followed by `.1`). The
    110 following rule performs the installation.
    111 
    112 ```Makefile
    113 PREFIX    = /usr/local
    114 MANPREFIX = ${PREFIX}/share/man
    115 
    116 install: all
    117         mkdir -p ${DESTDIR}${PREFIX}/bin
    118         mkdir -p ${DESTDIR}${MANPREFIX}/man1
    119         install -m 755 ${PROG} ${DESTDIR}${PREFIX}/bin/${PROG}
    120         install -m 644 ${PROG}.1 ${DESTDIR}${MANPREFIX}/man1/${PROG}.1
    121 ```
    122 
    123 Before installing, the program should have been built; therefore `all` must be a
    124 prerequisite for `install`.
    125 
    126 The user or package maintainer can set the variable `${DESTDIR}` to specify a
    127 different installation destination. This variable must be prepended to each
    128 installation path; and the Makefile author should not define it (it is left to
    129 the user or package maintainer to define it). Note that there is no bar
    130 separating ``${DESTDIR}`` from what follows, because the `${PREFIX}` and
    131 `${MANPREFIX}` variables should already begin with a bar.
    132 
    133 The Makefile author, however, is expected to define two variables pointing to
    134 installation prefixes: `${PREFIX}`, pointing to the general installation prefix;
    135 and `${MANPREFIX}`, pointing to the manual page installation prefix. There are
    136 other commonly defined prefixes, such as `${bindir}`, set to `${PREFIX}/bin`.
    137 The user or package maintainer can then invoke `make(1)` with those variables
    138 assigned to different prefixes. On most GNU/Linux systems, for example,
    139 `${PREFIX}` is assigned to `/usr`; and on OpenBSD, `${MANPREFIX}` is assigned to
    140 `${PREFIX}/man` (without the `share/` part).
    141 
    142 The variables `${PREFIX}` and `${MANPREFIX}` are not automatically assigned, but
    143 they can be changed by the user or package maintainer. These variables are
    144 commonly assigned in the Makefile by the Makefile author with the `?=` operator,
    145 which assign them only if not already defined, rather than with the common `=`
    146 operator. Thus, the values of these variables can be inherited from the
    147 environment, and the user need not have to assign them on each invocation. This
    148 operator is a non-POSIX extension, however, although supported by both GNU and
    149 BSD make implementations.
    150 
    151 Looking back at the installation commands, we first use `mkdir(1)` to create the
    152 destination directories, and then use `install(1)` to install them. We could
    153 simply call `install` with the `-D` flag, which automatically creates the
    154 destination directories if necessary. However, this option is an extension and
    155 is not supported by some implementations (such as FreeBSD's). Remember to
    156 install each file with its proper permission modes with the `-m` option.
    157 
    158 The Makefile author can also create a uninstallation rule, which simply removes
    159 the files from their destination directories.
    160 
    161 ```Makefile
    162 uninstall:
    163         rm ${DESTDIR}${PREFIX}/bin/${PROG}
    164         rm ${DESTDIR}${MANPREFIX}/man1/${PROG}.1
    165 ```
    166 
    167 ## The Cleaning Rule
    168 
    169 The Makefile author can define a rule to clean the build directory and revert it
    170 to its original state. Such rule is commonly called `clean`. It removes the
    171 intermediate object files and the final files. As convenience, for the developer
    172 to clean the build directory from core files that may be created by the system
    173 during the development, the `clean` rule can also delete `.core` files.
    174 
    175 ```Makefile
    176 clean:
    177         -rm -f ${OBJS} ${PROG} ${PROG:=.core}
    178 ```
    179 
    180 Note that the command of this rule begins with an hyphen `-`. This causes make
    181 to not return error (non-zero) exit status when the command fails. This is
    182 handy, for cleaning an already cleaned build directory to not print errors.
    183 
    184 ## The Phony Targets
    185 
    186 In a Makefile, some rules specify "virtual" targets which do not correspond to
    187 any file to be created. These are the "phony" targets. The `.PHONY` special
    188 target is used to mark its prerequisites as phony targets. In our Makefile, we
    189 have four phony targets: `all`, `install`, `uninstall`, and `clean`.
    190 
    191 ```Makefile
    192 .PHONY: all clean install uninstall
    193 ```
    194 
    195 ## The Makefile
    196 
    197 In the end, our Makefile should look like this:
    198 
    199 ```Makefile
    200 PROG = myprog
    201 SRCS = main.c util.c
    202 OBJS = ${SRCS:.c=.o}
    203 
    204 PREFIX    = /usr/local
    205 MANPREFIX = ${PREFIX}/share/man
    206 
    207 all: ${PROG}
    208 
    209 main.o: parse.h util.h
    210 parse.o: parse.h util.h
    211 util.o: util.h
    212 
    213 .c.o:
    214         ${CC} -I/usr/X11R6/include ${CFLAGS} ${CPPFLAGS} -c $<
    215 
    216 ${PROG}: ${OBJS}
    217         ${CC} -o $@ ${OBJS} -L/usr/X11R6/lib -lX11 ${LDFLAGS}
    218 
    219 install: all
    220         mkdir -p ${DESTDIR}${PREFIX}/bin
    221         mkdir -p ${DESTDIR}${MANPREFIX}/man1
    222         install -m 755 ${PROG} ${DESTDIR}${PREFIX}/bin/${PROG}
    223         install -m 644 ${PROG}.1 ${DESTDIR}${MANPREFIX}/man1/${PROG}.1
    224 
    225 uninstall:
    226         rm ${DESTDIR}${PREFIX}/bin/${PROG}
    227         rm ${DESTDIR}${MANPREFIX}/man1/${PROG}.1
    228 
    229 clean:
    230         -rm -f ${OBJS} ${PROG} ${PROG:=.core}
    231 
    232 .PHONY: all clean install uninstall
    233 ```
    234 
    235 ## tl;dr
    236 
    237   * Define variables for the final files to be built, the source files, and the
    238     intermediate object files created by the building process. Those are
    239     commonly named `${PROG}`, `${SRCS}` and `${OBJS}`, respectively.
    240   * Include in the Makefile, but do not assign them, the variables `${CFLAGS}`,
    241     `${CPPFLAGS}`, `${LDFLAGS}` and `${DESTDIR}`. They should be assigned by the
    242     user or package maintainer.
    243   * Evaluate the flag variables (`${CFLAGS}`, `${CPPFLAGS}`, and `${LDFLAGS}`)
    244     after any hardcoded flag, so the user or package maintainer can override it.
    245   * Include the `all` and `clean` phony targets. Optionally include `install`
    246     and `uninstall` phony targets. Always mark them as `.PHONY`.
    247   * Do not use `$<` on anything but on the command of inference rules.
    248   * Do not use `-D` with `install(1)`.
    249   * Do not call `c99` or `gcc` manually. Call the command set in `${CC}`
    250     instead.
    251   * Assign `${PREFIX}` and `${MANPREFIX}` to the proper installation prefixes.
    252     You can assign them with the `?=` operator for the user convenience, but
    253     this assignment operator is not portable, although commonly supported.
    254 
    255 [original]: https://seninha.org/make/