Cross-platform C++ Makefile project template
MIT License
A cross-platform C++ Makefile for any project!
compile_commands.json
See the table of contents at the end.
Alternatively, Clang can be used instead of GCC (see here). The instructions below will focus on GCC.
sudo apt install build-essential
sudo dnf install gcc-c++ make
sudo pacman -S base-devel
xcode-select --install
bin
directory to Windows's system PATH
environment variable.
You will need to use
mingw32-make
instead ofmake
each time themake
command is used in this README.
You will need to use Git Bash instead of PowerShell or
cmd.exe
each time themake
command is used in this README.
$ make help
Usage: make target... [options]...
Targets:
all Build executable (debug configuration by default) (default target)
run Build and run executable (debug configuration by default)
copyassets Copy assets to executable directory for selected platform and configuration
cleanassets Clean assets from executable directories (all platforms)
clean Clean build directory (all platforms)
compdb Generate JSON compilation database (compile_commands.json)
format Format source code using clang-format
format-check Check that source code is formatted using clang-format
lint Lint source code using clang-tidy
lint-fix Lint and fix source code using clang-tidy
docs Generate documentation with Doxygen
help Print this information
printvars Print Makefile variables for debugging
Options:
release=1 Run target using release configuration rather than debug
Note: the above options affect the all, run, copyassets, compdb, and printvars targets
make
This will compile the executable and output it inside the current bin
directory (build/<platform>/<configuration>/bin
). This is equivalent to make all
.
By default, all builds use GCC. To use another compiler, override the CXX
variable when invoking make
. For example, to use Clang:
make CXX=clang++
make run
This will run the executable, rebuilding it first if it was out of date. The working directory will be the executable's directory, i.e. the current bin
directory.
To add files to be copied next to the executable's output location, simply add them to the assets
directory. Then, use the following command:
make copyassets
This will copy the contents of assets
to the current bin
directory, preserving their folder structure.
If you have certain assets which you wish to only copy for certain platforms, you can do the following:
assets_os/<platform>
directory at the root of the project. The <platform>
directory should be named either linux
, macos
, or windows
based on the desired platform for the assets.make copyassets
command as usual. The files copied to the current bin
directory will be the combination of the files in assets
and assets_os
, with files in assets_os
overwriting those in assets
in case of naming clashes.The
assets_os
directory is useful for holding Windows DLLs which need to be copied next to the executable (usingassets_os/windows
).
make cleanassets
This will remove all the files in all bin
directories except the executables.
make clean
This will remove the entire build
directory.
Options can be specified when building, running, and copying assets. These will modify the settings used to build the executable and affect what is considered the current bin
directory when running a command.
By default, builds use the debug configuration. To build for release (including optimizations), add the release=1
option when invoking make
:
make release=1
To use the release
version of the executable, release=1
must also be specified when running or when copying assets. For example:
make copyassets run release=1
Some language servers and tools, like clangd or clang-tidy, rely on a JSON compilation database (compile_commands.json
). To generate this file, use the following command:
make compdb
This will create the compilation database in build/compile_commands.json
. You should rerun this command any time you add files to your project.
make format
This will format all files in the src
and include
directories using clang-format according to the options set in .clang-format
.
To only verify if the files are correctly formatted, use the following command:
make format-check
This will return exit code 1
if any files are not formatted.
make lint
This will lint all files in the src
and include
directories using clang-tidy according to the options set in .clang-tidy
. This will return exit code 1
if any files have lint errors.
To apply the suggested fixes to errors found by clang-tidy, use the following command:
make lint-fix
Documentation can be generated from documentation comments using Doxygen.
Create a new docs
directory at the root of the project.
Generate a new Doxyfile in docs/Doxyfile
:
cd docs
doxygen -g
Or, to use the graphical wizard instead:
cd docs
doxywizard
make docs
This will generate the documentation according to the rules found in docs/Doxyfile
and output it in the docs
directory.
There are several ways to add a library to your project.
For more complex projects, using a package manager is the recommended way to add libraries. This method ensures that your libraries are managed consistently across platforms.
You can integrate Conan with the Makefile by using the MakeDeps generator.
Install Conan using pip
:
pip install conan
Create a Conan profile:
conan profile detect --force
The path of the generated profile can be found using conan profile path default
. You can edit this file and set compiler.cppstd
to your desired C++ standard (e.g. compiler.cppstd=20
).
Create a conanfile.txt
at the root of the project:
[requires]
# Add dependencies...
[generators]
MakeDeps
Edit the Makefile:
# Conan
CONAN_DEFINE_FLAG = -D
CONAN_INCLUDE_DIR_FLAG = -I
CONAN_LIB_DIR_FLAG = -L
CONAN_BIN_DIR_FLAG = -L
CONAN_LIB_FLAG = -l
CONAN_SYSTEM_LIB_FLAG = -l
include $(BUILD_DIR_ROOT)/conandeps.mk
# Sources (searches recursively inside the source directory)
[...]
# Includes
INCLUDE_DIR = include
INCLUDES := -I$(INCLUDE_DIR) $(CONAN_INCLUDE_DIRS)
# C preprocessor settings
CPPFLAGS = $(INCLUDES) -MMD -MP $(CONAN_DEFINES)
[...]
# Linker flags
LDFLAGS = $(CONAN_LIB_DIRS)
# Libraries to link
LDLIBS = $(CONAN_LIBS) $(CONAN_SYSTEM_LIBS)
[...]
# Generate Conan dependencies
$(BUILD_DIR_ROOT)/conandeps.mk: conanfile.txt
@echo "Generating: $@"
@conan install . --output-folder=build --build=missing
# Build executable
[...]
See this gist for an example of the modifications to make.
You can integrate vcpkg with the Makefile by using the manual integration.
Add vcpkg as a submodule:
git submodule add https://github.com/Microsoft/vcpkg.git
Run the bootstrap script to build vcpkg:
./vcpkg/bootstrap-vcpkg.sh
Create a vcpkg.json
at the root of the project:
{
"dependencies": [
// Add dependencies...
]
}
Install the dependencies listed in vcpkg.json
:
./vcpkg/vcpkg install
Edit the Makefile:
# Platform-specific settings
ifeq ($(OS),windows)
[...]
# Windows-specific settings
INCLUDES += -Ivcpkg_installed/x64-windows/include
LDFLAGS += -Lvcpkg_installed/x64-windows/lib
LDLIBS += # Add libraries with -l...
else ifeq ($(OS),macos)
# macOS-specific settings
INCLUDES += -Ivcpkg_installed/x64-osx/include
LDFLAGS += -Lvcpkg_installed/x64-osx/lib
LDLIBS += # Add libraries with -l...
else ifeq ($(OS),linux)
# Linux-specific settings
INCLUDES += -Ivcpkg_installed/x64-linux/include
LDFLAGS += -Lvcpkg_installed/x64-linux/lib
LDLIBS += # Add libraries with -l...
endif
See this gist for an example of the modifications to make.
Header-only libraries are composed solely of header files. This way, no separate compilation or linking is necessary.
external
directory at the root of the project.external
directory, create a <library-name>
sudirectory to contain the library's header files.external/<library-name>
.-Iexternal/<library-name>
to the INCLUDES
variable at line 19 of the Makefile.Some libraries can be installed system-wide, using your system's package manager. For example:
apt
dnf
pacman
These system package managers install dependencies in a default system-wide directory, such as /usr/lib
and /usr/include
on Linux. Some important system-wide libraries may also come preinstalled on your system.
Relying on a system package manager for your libraries can make it less straightforward for other developers using a different platform to start working on your project. Nevertheless, they can be a quick way for you to start using a library, especially if this library is already required by the system.
Use your system package manager to install the library's development package. Often, these will have the -dev
or -devel
suffix.
Link with the library: add -l<library-name>
to the LDLIBS
variable at line 33 of the Makefile.
Depending on the library, more than one library name may need to be added with the -l
option. Refer to your library's documentation for the names to use with the -l
option in this step.
Note: for macOS, you may need to link your library using -framework
rather than -l
.
Alternatively, if the library is not available in any package manager, you can build it from source or download its compiled artifacts and add them to your project.
If this is the first library you are adding, create a new external
directory at the root of the project.
Inside the external
directory, create a <library-name>
sudirectory to contain the library's files.
Build or download the library's compiled files and add them to external/<library-name>
.
You may instead prefer to add the library as a Git submodule inside the external
directory to make updates easier.
Add the library's header files to the preprocessor's search path: add -Iexternal/<library-name>/include
to the INCLUDES
variable at line 19 of the Makefile.
Add the library's compiled files to the linker's search path: add -Lexternal/<library-name>/lib
to the LDFLAGS
variable at line 30 of the Makefile.
Link with the library: add -l<library-name>
to the LDLIBS
variable at line 33 of the Makefile.
Depending on the library, more than one library name may need to be added with the -l
option. Refer to your library's documentation for the names to use with the -l
option in this step.
Note: for macOS, you may need to link your library using -framework
rather than -l
.
Note that the folder structure inside external/<library-name>
will vary from one library to the next. In these instructions:
include
subdirectory refers to a directory containing all of the library's header files.lib
subdirectory refers to a directory containing all of the library's compiled files (e.g. .so
, .a
, .lib
, .framework
, etc.). If you have chosen to build the library from source, you should copy the output of the compiled library to the lib
directory.These directories may be named differently: refer to your library's documentation for more information.
The following table presents an overview of the most commonly changed settings of the Makefile:
Configuration | Variable | Line |
---|---|---|
Change the output executable name | EXEC |
6 |
Select the C++ compiler (e.g. g++ or clang++ ) |
CXX |
25 |
Add preprocessor settings (e.g. -D<macro-name> ) |
CPPFLAGS |
22 |
Change C++ compiler settings (useful for setting the C++ standard version) | CXXFLAGS |
26 |
Add/remove compiler warnings | WARNINGS |
27 |
Add includes for libraries common to all platforms (e.g. -Iexternal/<library-name>/include ) |
INCLUDES |
19 |
Add linker flags for libraries common to all platforms (e.g. -Lexternal/<library-name>/lib ) |
LDFLAGS |
30 |
Add libraries common to all platforms (e.g. -l<library-name> ) |
LDLIBS |
33 |
Add includes/linker flags/libraries for specific platforms |
INCLUDES - LDFLAGS - LDLIBS
|
49-68 |
All the configurable options are defined between lines 1-68. For most uses, the Makefile should not need to be modified beyond line 68.
The previous sections explain how to add a library using the common INCLUDES
, LDFLAGS
, and LDLIBS
variables which are shared between all platforms. However, in some cases, the library may need to be linked differently by platform. Examples of such platform-specific library configurations include:
-framework
over -l
to link a library on macOS-L
The Makefile is designed to support these kinds of platform-specific configurations alongside one another.
Lines 49-68 of the Makefile contain platform-specific INCLUDES
, LDFLAGS
, and LDLIBS
variables which should be used for this purpose. To add a library for a certain platform, simply add the options to the variables under the comment indicating the platform.
The common
INCLUDES
(line 19),LDFLAGS
(line 30), andLDLIBS
(line 33) variables should only contain options which are identical for all platforms. Any platform-specific options should instead be specified using lines 49-68.
.
├── assets
│ └── <assets>
├── assets_os
│ └── linux | macos | windows
│ └── <assets>
├── build
│ └── linux | macos | windows
│ └── debug | release
│ ├── bin
│ │ ├── executable
│ │ └── <assets>
│ └── obj
│ ├── **/*.o
│ └── **/*.d
├── docs
│ ├── Doxyfile
│ └── **/*.html
├── include
│ └── **/*.h
├── src
│ ├── main.cpp
│ └── **/*.cpp
├── .clang-format
├── .clang-tidy
├── .gitattributes
├── .gitignore
├── Makefile
└── README.md
To comply with the terms of the MIT license in your project, simply copy-pasting the entire contents of the provided LICENSE
file as a comment at the top of the Makefile is sufficient. By doing so, you do not need to include the LICENSE
file directly since it is is now contained in the Makefile. You can then reuse the LICENSE
filename for your own license if you wish.