Flexible and simple makefile for c++ projects.
It is basically designed for my personal projects but could be suitable for people who don't want to dive into GNU Make
or CMake
.
- It builds projects of type
lib
,exe
andinc
. - Supports
release
anddebug
compilation modes. - Builds and launches unit tests.
- Launches exe and unit tests with GDB.
- Supports pretty printers for GDB.
- Supports parallel builds.
- Works by convention. No need to list source files etc.
- Produces dynamic libraries with the project's branch in name to allow patches.
- bash completion is available.
- Supports vscode workspace generation.
If somewhere in devdir
your c++ projects are as
devdir
|
+--- prja
|
+--- prjb
Then clone this project next to your projects
devdir
|
+--- prja
|
+--- prjb
|
+--- makefile
Put your code into src/projectname
devdir
|
+--- prja
| |
| +--- Makefile
| |
| +--- src
| |
| +--- prja
| | |
| | +--- MyCode.h
| | |
| | +--- MyCode.cpp
| |
| +--- tests
| | |
| | +--- TestMyCode.cpp
| |
| +--- gdb
| |
| +--- printers.py
|
+--- prjb
|
+--- makefile
In every devdir/projectname
create a simple Makefile
PRJ_NAME := prja
PRJ_BRANCH := 1.0
PRJ_VERSION := $(PRJ_BRANCH).0
PRJ_TYPE := lib
include ../makefile/Makefile
Type make to build your binary.
The variable PRJ_TYPE
in your Makefile
must be lib
, exe
or inc
.
directory | project types | optional | contents |
---|---|---|---|
src/$(PRJ_NAME) |
all | no | .h and .cpp files to compile |
src/tests |
lib inc |
yes | unit tests |
src/gdb |
lib exe |
yes | pretty printers |
build |
all | - | build results and temporary files |
The variable BUILD_MODE
controls compilation options. It can be either release
or debug
and defaults to release
.
# release:
$> make
# is the same as
$> make BUILD_MODE=release
# or
$> make release
# debug:
$> make debug
# is the same as
$> make BUILD_MODE=debug
The compilation result and temporary files will all go into the build
directory.
For instance, the project prja
depicted above, after building the library and unit tests will become as follows:
prja
|
+--- Makefile
|
+--- src ### Source code
| |
| +--- prja
| | |
| | +--- PrjInfo.h ### Generated project meta information
| | |
| | +--- MyCode.h
| | |
| | +--- MyCode.cpp
| |
| +--- tests
| | |
| | +--- TestMyCode.cpp
| |
| +--- gdb
| |
| +--- printers.py
|
+--- build ### Build directory
|
+--- dep ### C++ dependencies
| |
| +--- debug
| | |
| | +--- prja
| | | |
| | | +--- MyCode.mk
| | |
| | +--- tests
| | |
| | +--- TestMyCode.mk
| |
| +--- release
| |
| +--- prja
| | |
| | +--- MyCode.mk
| |
| +--- tests
| |
| +--- TestMyCode.mk
|
+--- obj ### Objects
| |
| +--- debug
| | |
| | +--- prja
| | | |
| | | +--- MyCode.o
| | |
| | +--- tests
| | |
| | +--- TestMyCode.o
| |
| +--- release
| |
| +--- prja
| | |
| | +--- MyCode.o
| |
| +--- tests
| |
| +--- TestMyCode.o
|
+--- lib ### Target libraries if project types is lib
| |
| +--- debug
| | |
| | +--- libprja-1.0.so
| | |
| | +--- libprja-1.0.so-gdb.py
| |
| +--- release
| |
| +--- libprja-1.0.so
| |
| +--- libprja-1.0.so-gdb.py
|
+--- bin ### Unit tests if the project is a lib otherwise
| ### target executables
+--- debug
| |
| +--- tests
| |
| +--- TestMyCode.exe
|
+--- release
|
+--- tests
|
+--- TestMyCode.exe
Thus putting build
into .gitignore
will easily exclude all temporary files.
Makefile
will create src/$(PRJ_NAME)/PrjInfo.h
file.
If you want to disable this feature do this:
PRJ_INFO :=
You can set the variable SRCSUBDIRS
if you have to keep some of your code in sub-directories.
For example for a project like:
prja
|
+--- src
| |
| +--- prja
| | |
| | +--- impl
| | | |
| | | +--- generated
| | | | |
| | | | +--- Stub.h
| | | | |
| | | | +--- Stub.cpp
| | | |
| | | +--- ApiImpl.h
| | | |
| | | +--- ApiImpl.cpp
| | |
| | +--- Api.h
| | |
| | +--- Api.cpp
| |
you will need:
SRCSUBDIRS = impl impl/generated
make go
will launch your executable.make gdb
will start GDB with your executable andLD_LIBRARY_PATH
set properly.make ldd
will launch ldd with your binary andLD_LIBRARY_PATH
set properly.
make clean
will remove./build
make clean-deps
will clean dependenciesmake clean-all
will clean dependencies and your project
For a dynamic library project prja-1.0.0
the binary will be named libprja-1.0.so
where 1.0
comes from PRJ_BRANCH
. The convention is to keep backward compatible all versions on the same branch.
Thus incrementing PRJ_VERSION
will not break linking and runtime dependency. If you break ABI better do it in a different branch.
1.1
|
o <1.1.0>
1.0 |
| |
o |
| |
| |
o <1.0.1> |
| |
| |
o----------+
|
|
o <1.0.0>
|
|
variable | default value | description |
---|---|---|
CPPEXT | cpp | C++ files extension |
COMPILER | c++ | Compiler command |
CPP_STD | -std=c++17 | C++ standard |
CPP_OPTIM | -O0 or -O3 -DNDEBUG | Optimization options |
CPP_PLT | -fno-plt | PLT option |
CPP_PIC | -fPIC | PIC option |
CPP_COVERAGE | coverage options | |
CPP_DEFINES | passed to the compiler | |
CPP_INCLUDES | passed to the compiler | |
CPP_EXTRA_FLAGS | passed to the compiler | |
LINK_EXTRA_LIBS | passed to the linker | |
PRJ_POSTBUILD_TARGET | any post build target |
Write your pretty printers in src/gdb/printers.py
and it will be copied next to your binary so that GDB will recognize it. See examples/02-dll-engine/src/gdb/printers.py
.
All unit test files src/tests/Test*.cpp
are automatically detected. They are considered as separate executables.
For instance a TestXXX.cpp
will be built as bin/release/tests/TestXXX.exe
.
make check
will build and launch all unit tests. Running it again will not launch the tests already done.make recheck
will build if necessary and launch all unit tests.make test-XXX
will build and launch onlybin/release/tests/TestXXX.exe
. Typingmake test-[TAB][TAB]
will propose all available tests if the bash completion has been activated.make test-XXX BUILD_MODE=debug
will build and launch onlybin/debug/tests/TestXXX.exe
make build-all-tests
will only build all unit tests.make gdb-test-XXX [BUILD_MODE=debug]
will build and launch your tests in GDB.make lcov
run unit tests with coverage on and generate lcov reports.make clean-tests
will clean only test related files.make clean-lcov
will clean only coverage related files.
To build with coverage options one can use
$> make clean
$> make COVERAGE=true check
or ask lcov to generate the coverage reports after running unit tests with make lcov
.
If a unit test TestXXX.cpp
requires extra files write them as XXX*.cpp
and they will be compiled and linked along with TestXXX.cpp
.
Example examples/02-dll-engine/src/tests/TestAnything.cpp.
variable | applies to test | description |
---|---|---|
TEST_INCLUDES | all | passed to the compiler |
TEST_DEFINES | all | passed to the compiler |
TEST_EXTRA_LINK_LIBS | all | passed to the linker |
TEST_EXTRA_DEPENDENCY | all | built before all unit tests |
TestXXX_EXTRA_LD_PATH | XXX | injected into LD_LIBRARY_PATH for launching the test XXX |
TestXXX_EXTRA_DEPENDENCY | XXX | built before the test XXX |
If for some reasons you have to disable unit tests XXX and YYY do this:
DISABLED_TESTS = XXX YYY
One can use any unit test framework. Just feed properly the variables TEST_INCLUDES
and TEST_EXTRA_LINK_LIBS
.
If a very simple condition verification is enough, you can use the trivial helper file coming with this makefile.
#include <utests/TrivialHelper.h>
...
CHECK( engine max power, engine.getMaxPower(), >100 )
CHECK( battery charged, battery.getCharge(), >20 )
...
The default behavior is to display all check results independently wether they fail or not. If you want to stop execution on the very first failed condition.
TEST_DEFINES = -DEXIT_ON_ERROR
Sourcing bash-completion-to-source.sh
will make bash completion available for make [TAB][TAB]
.
If you have unit tests make test-[TAB][TAB]
will list all available test to build and run.
Example:
examples/02-dll-engine> make test-[TAB][TAB]
test-Anything test-Engine
make generate-vscode
will create .vscode
dir in all dependencies. It will generate .vscode/tasks.json
and .vscode/launch.json
only within the main project to avoid spamming the pull down menu Ctrl+Shift+b
. If you need tasks from a dependency to be available you can go their and invoke make generate-vscode
.
Example:
examples/05-exe-peugeot> make generate-vscode
specs-1.0.0 /path/to/this/makefile/examples/00-inc-specs
- generating /path/to/this/makefile/examples/00-inc-specs/.vscode/c_cpp_properties.json
battery-2.0.0 /path/to/this/makefile/examples/01-lib-battery 00-inc-specs
- generating /path/to/this/makefile/examples/01-lib-battery/.vscode/c_cpp_properties.json
computer-1.0.0 /path/to/this/makefile/examples/03-dll-computer 01-lib-battery 00-inc-specs
- generating /path/to/this/makefile/examples/03-dll-computer/.vscode/c_cpp_properties.json
engine-1.0.0 /path/to/this/makefile/examples/02-dll-engine 01-lib-battery 00-inc-specs
- generating /path/to/this/makefile/examples/02-dll-engine/.vscode/c_cpp_properties.json
car-1.0.0 /path/to/this/makefile/examples/04-dll-car 02-dll-engine 03-dll-computer 01-lib-battery 00-inc-specs
- generating /path/to/this/makefile/examples/04-dll-car/.vscode/c_cpp_properties.json
peugeot-207.0.0 /path/to/this/makefile/examples/05-exe-peugeot 04-dll-car 02-dll-engine 03-dll-computer 01-lib-battery 00-inc-specs
- generating /path/to/this/makefile/examples/05-exe-peugeot/.vscode/c_cpp_properties.json
- generating /path/to/this/makefile/examples/05-exe-peugeot/.vscode/tasks.json
- generating /path/to/this/makefile/examples/05-exe-peugeot/.vscode/launch.json
- generating /path/to/this/makefile/examples/05-exe-peugeot/.vscode/peugeot.code-workspace
You can now open /path/to/this/makefile/examples/05-exe-peugeot/.vscode/peugeot.code-workspace
In examples you will find primitive but representative projects.
dir | dependencies | nota |
---|---|---|
00-inc-specs | header only project with a unit test | |
01-lib-battery | specs | |
02-dll-engine | battery | has unit tests and pretty printers |
03-dll-computer | battery | |
04-dll-car | engine computer | |
05-exe-peugeot | car |