Makefile examples ranging from fundamental to advanced. Conditional compilation of source files based on system details enables makefile to be a portable build tool, e.g., if system is CUDA capable -> use CUDA files or function variants. Stage1 and onward incorporate automated testing/validation using external tools such as valgrind and compute-sanitizer (if installed). Stage3 is essentially a portable template for using makefiles to automate the testing, building, and acceptance of an application or system that in uses multiple: languages, compilers, and testing tools.
- *nix operating system
- C compiler such as gcc or clang
- GNU Make version 4.3+
- GNU bash (developed with version 5.1.16, but should work with almost any version)
- Python 3.X
- Valgrind
- Nvcc
- Compute-sanitizer
Copy and paste below into terminal and press enter:
git clone https://github.com/nellogan/MakefileExamples.git
To build all stages, copy and paste below into a terminal window:
cd MakefileExamples && make
If 'make fresh-start' is entered, make will remove all generated files and directories.
Try changing directories to various Stages, issue make or make and observe results.
cd Stage0
make
./bin/MainExecutable
...terminal output...
cd ../Stage1
make
./bin/MainExecutable
...terminal output...
cd ../Stage2
make system-test
./bin/MainExecutable
...terminal output...
...
Try commenting/uncommenting lines in source files to force errors to see how automated testing reacts.
Fundamental -- File structure: bin, build, include, and src. Each object file (rule) is manually typed with a
limited amount of variables. Rules are somewhat redundantly written.
Simple -- File structure: bin, build, include, src, and test (where test mimics parent structure). File structure
will not change much in further stages. More liberal use of variables. Introduction of make functions such as
wildcard, patterns such as globs(*), patterns(%), and automatic variables such as $<. Test directory contains its
own makefile that is invoked from Stage1 makefile i.e. recursive make. UnitTestRunner bundles all unit tests as a
single executable. Use of a shell conditional if statement under the "test:" target.
Automated testing is introduced. Accomplished by setting the prerequisite of the build target to include the test
target, i.e. build: test $(MAIN_EXECUTABLE). This forces the build target to only be completed after all tests are
built and returned successfully. If any of the tests fail, makefile will exit: $(MAIN_EXECUTABLE) will not be
compiled.
Intermediate -- A system-test in addition to the UnitTestRunner. Two variables (VALGRIND_INSTALLED and
PYTHON3_INSTALLED) defined based if valgrind and python3 are on $PATH. These variables influence how system-test and
test submake are built and evaluated. Commandline arguments to /test/system-test/sh will differ depending on
VALGRIND_INSTALLED (0 if not installed, 1 if installed). Makefile conditional ifeq used to early exit system-test if
PYTHON3_INSTALLED is 0. If VALGRIND_INSTALLED, test results are validated/audited using valgrind with logs saved in
/test/valgrind_logs. If an error and/or leak is found by valgrind, a non-zero exit code is emitted. This will
trigger an early exit, thus halting compilation.
Advanced -- Variables from main makefile are exported to test makefile. Exact paths of directories rather than
starting from a relative file path. Instead of handling all unit tests via a single UnitTestRunner, unit tests are
compiled and evaluated individually. This is done through a macro and conditional compilation in test source files.
Library files are used. Pattern to handle a different file type that requires a different compiler (.cu CUDA files);
therefore, another leak/error checker is used (nvidia compute-sanitizer) on these files. Uses conditionals to
determine rules, e.g., nvcc not on path -> compile main/tests without CUDA files. Since nvcc/compute-sanitizer are
optional (CUDA files), $(MAIN_EXECUTABLE) will compile without these functions if not installed.
Extras: Uses ANSI color codes for fancier SUCCESS/WARNING/ERROR messages. Valgrind does not know how to handle
compiled CUDA code, so a suppression file is added in test/util.
Todo: Save make output to log file for record keeping.
Stage3 with nvcc:
Stage3nvcc.webm
Stage3 without nvcc:
Stage3no_nvcc.webm
Stage3 valgrind error:
Stage3valgrind_error.webm
Stage3 compute-sanitizer error: