Build system

The following document briefly describes the steps and methodologies used for the new and improved Makefile system.

The Problem

The problem with the old Makefile system is that it was very difficult to maintain and it lacked any sense of formal structure, thus developing for LTP and including new targets was more difficult than it should have been (maintenance). Furthermore, proper option-based cross-compilation was impossible due to the fact that the Makefiles didn’t support a prefixing system, and the appropriate implicit / static rules hadn’t been configured to compile into multiple object directories for out-of-tree build support (ease of use / functionality). Finally, there wasn’t a means to setup dependencies between components, such that if a component required libltp.a in order to compile, it would go off and compile libltp.a first (ease of use).

These items needed to be fixed to reduce maintenance nightmares for the development community contributing to LTP, and the project maintainers.

Design

The system was designed such that including a single GNU Makefile compatible set in each new directory component is all that’s essentially required to build the system.

Say you had a directory like the following (with .c files in them which directly tie into applications, e.g. baz.c -> baz):

.../foo/
    |--> Makefile
    |
    --> bar/
        |
        --> Makefile
        |
        --> baz.c
…/foo/Makefile
# SPDX-License-Identifier: GPL-2.0-or-later

top_srcdir          ?= ..

include $(top_srcdir)/include/mk/env_pre.mk
include $(top_srcdir)/include/mk/generic_trunk_target.mk
…/foo/bar/Makefile
# SPDX-License-Identifier: GPL-2.0-or-later

top_srcdir          ?= ../..

include $(top_srcdir)/include/mk/env_pre.mk
include $(top_srcdir)/include/mk/generic_leaf_target.mk

Kernel Modules

Some of the tests need to build kernel modules, happily LTP has infrastructure for this.

ifneq ($(KERNELRELEASE),)

obj-m := module01.o

else

top_srcdir  ?= ../../../..
include $(top_srcdir)/include/mk/testcases.mk

REQ_VERSION_MAJOR   := 2
REQ_VERSION_PATCH   := 6
MAKE_TARGETS                := test01 test02 module01.ko

include $(top_srcdir)/include/mk/module.mk
include $(top_srcdir)/include/mk/generic_leaf_target.mk

endif

This is a Makefile example that allows you to build kernel modules inside LTP. The prerequisites for the build are detected by the configure script.

The REQ_VERSION_MAJOR and REQ_VERSION_PATCH describe minimal kernel version for which the build system tries to build the module.

The build system is also forward compatible with changes in Linux kernel internal API so that, if module fails to build, the failure is ignored both on build and installation. If the userspace counterpart of the test fails to load the module because the file does not exists, the test is skipped.

Note the ifneq($(KERNELRELEASE),). The reason it exists, it is that the Makefile is executed twice: once by LTP build system and once by kernel kbuild, see https://docs.kernel.org/kbuild/modules.html in the Linux kernel documentation for details on external module build.

Make Rules and Make Variables

When using make rules, avoid writing ad hoc rules like:

[prog]: [dependencies]
    cc -I../../include $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(LDLIBS) \
        -o [prog] [dependencies]

This makes cross-compilation and determinism difficult, if not impossible. Besides, implicit rules are your friends and as long as you use MAKEOPTS=; in the top-level caller (or do $(subst r,$(MAKEOPTS)) to remove -r), the compile will complete successfully, assuming all other prerequisites have been fulfilled (libraries, headers, etc).

Variable

Explanation

$(AR)

The library archiver

$(CC)

The system C compiler

$(CCP)

The system C preprocessor

$(CFLAGS)

C compiler flags

$(CPPFLAGS)

Preprocessor flags, e.g. -I arguments

$(DEBUG_CFLAGS)

Debug flags to pass to $(CC), -g, etc

$(KVM_LD)

Special linker for wrapping KVM payload binaries into linkable object files. Defaults to $(LD). Change this variable if the KVM Makefile fails to build files named *-payload.o

$(LD)

The system linker (typically $(CC), but not necessarily)

$(LDFLAGS)

What to pass in to the linker, including -L arguments and other ld arguments, apart from -l library includes (see $(LDLIBS)). This should be done in the $(CC) args passing style when LD := $(CC), e.g. -Wl,-foo, as opposed to -foo

$(LDLIBS)

Libraries to pass to the linker (e.g. -lltp, etc)

$(LTPLDLIBS)

LTP internal libraries i.e. these in libs/ directory

$(OPT_CFLAGS)

Optimization flags to pass into the C compiler, -O2, etc. If you specify -O2 or higher, you should also specify -fno-strict-aliasing, because of gcc fstrict-aliasing optimization bugs in the tree optimizer. Search for fstrict-aliasing optimization bug with your favorite search engine.

Examples of more recent bugs: tree-optimization/17510 and tree-optimization/39100.

Various bugs have occurred in the past due to buggy logic in the tree-optimization portion of the gcc compiler, from 3.3.x to 4.4.

$(RANLIB)

What to run after archiving a library

$(WCFLAGS)

Warning flags to pass to $(CC), e.g. -Werror, -Wall, etc.

Make System Variables

A series of variables are used within the make system that direct what actions need to be taken. Rather than listing the variables here, please refer to the comments contained in include/mk/env_pre.mk.

Guidelines and Recommendations

Of course, GNU Make manual is the key to understand the Make system, but following manuals are probably the most important:

Warning

Rebuild from scratch before committing anything in the build system.