Advanced guide

This section provides advanced examples of common operations, and how to correctly perform them using bconf to avoid issues.

Building executables

A binary executable is built in two steps:

  • Compile the source code into object files.
  • Link the object files into a binary executable.

The bconf rule to compile object file matches %o: %.c, so this rule is mostly used implicitly. The link rule, on the other hands, matches all elements of the host-bin variable. Thus, to declare a binary executable, define its binary executable in host-bin and extend its dependencies to include all its object files:

hello: hello.o # hello depends on hello.o, which in turns depends on hello.c.

host-bin+=hello # Link as host binary executable.

Building libraries

Like executables, libraries are built in two steps, compiling objects, and then linking them. The compiled binary objects are the same kind of artifacts used in building executables. The difference occurs during the link operation, where different rules are applied.

The link rule matches all elements in the host-lib variable, however, a different rule is applied depending on whether the element matches %.a or matches %.$(ld-so). The ld-so is a variable defined in the GNUmakefile specifying the extension of shared libraries (for now only so, even on Darwin-based systems). Overriding the value of ld-so to a is an implicit way to deactivate shared libraries generation.

foo-libs:=libfoo.a # libfoo supports static linking.
ifneq ($(ld-so),a) # If dynamic linking supported, add shared libfoo.
foo-libs+=libfoo.$(ld-so)
endif

$(foo-libs): foo.o # For static libfoo, and maybe shared libfoo, define dependencies.

host-lib+=$(foo-libs) # Link all libfoo variations.

Build flags, linking with a library

To specify build options, bconf relies on overrides and extensions of variables for specific targets:

# Expanding on the previous example:

bar-objs:=bar.o baz.o # Define all object files for a target.

$(bar-objs): CPPFLAGS+=-I$(srcdir)/include # Add headers from the source directory.
$(bar-objs): CFLAGS+=-std=c11 # Override the C standard used by the compiler when building
# Note here we override for $(bar-objs) instead of bar, that's because
# `make bar.o` wouldn't propagate the overrides from the `bar` target.

ifneq ($(CONFIG_BAR_OPTION),)
bar.o: CPPFLAGS+=-DCONFIG_BAR_OPTION='$(CONFIG_BAR_OPTION)'
# Only bar.o will receive this option during compilation.
endif

bar: $(bar-objs) # bar target objects require for linking
bar: LDFLAGS+=-Wl,-rpath='$ORIGIN/../lib' # Passing an option to the linker through the compiler driver.
bar: LDLIBS+=libfoo.$(ld-so) # bar will link with either static or shared libfoo.

host-bin+=bar # Link as host binary executable.

Cleaning artifacts

To avoid confusion between what can be cleaned and what cannot, bconf leaves the PHONY clean target choice of artifacts entirely to the end user. All objects specified in the clean-up variable will be removed on running the clean target.

# Expanding on the previous example:

clean-up+=$(bar-objs) $(host-bin) $(host-lib)

Compiling assembler files

Assembler files are compiled from the rule matching all %.o: %.S from the host-$(host-arch) variable:

# Pre-supposing that an x86_64 assembly file asm.S exists in either $(objdir) or $(srcdir):
host-x86_64+=asm.o
# Now if $(host-arch) is x86_64, asm.S will be compiled with the `CC` compiler driver.

The uppercase .S extension is linked to GCC (and other C compilers) interpreting the file either as needing C preprocessing (with uppercase), or not (with a lowercase). In the case of bconf, uppercase is chosen as the default as a non-preprocessed file can be compiled with the preprocessor while the other way is not always valid.

Custom rules

Just like any Makefile, you can write rules in the bconf.mk:

# Dependencies are resolved either from the current working directory,
# or from the $(srcdir) thanks to the $(VPATH) feature of GNU-make, always
# reference them by $^ or $< so GNU-make resolves them correctly.

.PHONY: install-mandir

# Install manpages
install-mandir: man/bar.1
	$(v-e) INSTALL $(notdir $^)
	$(v-a) $(INSTALL) -d -- "$(DESTDIR)$(mandir)"
	$(v-a) $(INSTALL-DATA) -- $^ "$(DESTDIR)$(mandir)"

# Extend the empty install-data PHONY target to install runtime documentation.
install-data: install-mandir

The v-e and v-a macros work differently depending on whether the variable V is set to 1 or not, if V is equal to 1, $(v-e) prefixed directives are ignored and $(v-a) prefixed ones are printed as usual. If not, $(v-e) echoes the following string, and $(v-a) executes the command silently (verbose or fancy output).