Makefile
Makefile copied to clipboard
Cross-platform C++ Makefile project template
Makefile
A cross-platform C++ Makefile for any project!
Features
- Cross-platform: works on Linux, macOS, and Windows (32- and 64-bit)
- Automatic: all source files are automatically found and compiled
- Efficient: only the modified files are recompiled and their dependencies are automatically generated
- Debug and release configurations
- Configurable: easily add libraries or change compilation settings
- Formatting with clang-format
- Linting with clang-tidy
- Generate documentation from Doxygen comments
- Built-in generation of
compile_commands.json - Compatible with VS Code's Makefile Tools extension
See the table of contents at the end.
Prerequisites
GCC & Make
Alternatively, Clang can be used instead of GCC (see here). The instructions below will focus on GCC.
- Linux:
- Debian/Ubuntu:
sudo apt install build-essential - Fedora:
sudo dnf install gcc-c++ make - Arch:
sudo pacman -S base-devel
- Debian/Ubuntu:
- macOS:
- Run the following command:
xcode-select --install - In the window which pops up, click "Install" and follow the instructions.
- Run the following command:
- Windows:
- Install MinGW-w64 from WinLibs.com.
- Add the path to MinGW-64's
bindirectory to Windows's systemPATHenvironment variable.You will need to use
mingw32-makeinstead ofmakeeach time themakecommand is used in this README. - Install Git Bash by installing Git for Windows.
You will need to use Git Bash over PowerShell or cmd.exe each time the
makecommand is used in this README.
Optional dependencies
- For formatting: clang-format
- For linting: clang-tidy
- For generating documentation: Doxygen and Graphviz
Usage
Overview of commands
$ make help
Usage: make target... [options]...
Targets:
all Build executable (debug mode by default) (default target)
install Install packaged program to desktop (debug mode by default)
run Build and run executable (debug mode by default)
copyassets Copy assets to executable directory for selected platform and configuration
cleanassets Clean assets from executable directories (all platforms)
clean Clean build and bin directories (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
win32=1 Build for 32-bit Windows (valid when built on Windows only)
Note: the above options affect the all, install, run, copyassets, compdb, and printvars targets
Building
make
This will compile the executable and output it inside the bin directory. This is equivalent to make all.
Using a different compiler
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++
Running
make run
This will run the executable, rebuilding it first if it was out of date.
Assets
Copying assets
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:
- Create an
assets_os/<platform>directory at the root of the project. The<platform>directory should be named eitherlinux,macos,windows32, orwindows64based on the desired platform for the assets. - Inside this new directory, add all the assets to be copied only for this platform.
- Use the
make copyassetscommand as usual. The files copied to the currentbindirectory will be the combination of the files inassetsandassets_os, with files inassets_osoverwriting those inassetsin case of naming clashes.
The
assets_osdirectory is useful for holding Windows DLLs which need to be copied next to the executable (usingassets_os/windows64orassets_os/windows32, depending on the target version).
Cleaning assets
make cleanassets
This will remove all the files in all bin directories except the executables.
Cleaning
make clean
This will remove the entire build and bin directories.
Options
Certain 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.
Release
By default, builds are in debug mode. 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
32-bit (Windows only)
By default, builds on Windows target 64-bit. To build a 32-bit executable, add the win32=1 option when invoking make.
make win32=1
This can also be combined with the release=1 option to build for 32-bit release.
Don't forget to also specify
win32=1when running or when copying assets!
Generating a JSON compilation database
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.
Formatting
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
Linting
make lint
This will lint all files in the src and include directories using clang-tidy according to the options set in .clang-tidy.
To apply the suggested fixes to errors found by clang-tidy, use the following command:
make lint-fix
Generating documentation
First time use
-
Create a new
docsdirectory at the root of the project. -
Generate a new Doxyfile in
docs/Doxyfile:cd docs doxygen -gOr, to use the graphical wizard instead:
cd docs doxywizard
Updating the documentation
make docs
This will generate the documentation according to the rules found in docs/Doxyfile and output it in the docs directory.
Adding libraries
There are several ways to add a library to your project.
Using a package manager
For more complex projects, using a package manager is the recommended way to add libraries. This method ensures that your libraries are managed identically across platforms.
vcpkg
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.jsonat the root of the project:{ "dependencies": [ // Add dependencies... ] } -
Install the dependencies listed in
vcpkg.json:./vcpkg/vcpkg install -
Edit the Makefile:
# OS-specific settings ifeq ($(OS),windows) [...] ifeq ($(win32),1) # Windows 32-bit settings INCLUDES += -Ivcpkg_installed/x86-windows/include LDFLAGS += -Lvcpkg_installed/x86-windows/lib LDLIBS += # Add libraries with -l... else # Windows 64-bit settings INCLUDES += -Ivcpkg_installed/x86-windows/include LDFLAGS += -Lvcpkg_installed/x86-windows/lib LDLIBS += # Add libraries with -l... endif 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.
Conan
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 --forceThe path of the generated profile can be found using
conan profile path default. You can edit this file and setcompiler.cppstdto your desired C++ standard. -
Create a
conanfile.txtat 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 [...] # 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
See this gist for an example of the modifications to make.
Header-only library
Header-only libraries are composed solely of header files. This way, no separate compilation or linking is necessary.
- If this is the first library you are adding, create a new
externaldirectory at the root of the project. - Inside the
externaldirectory, create a<library-name>sudirectory to contain the library's header files. - Download the library's header files and add them to
external/<library-name>. - Add the library's header files to the preprocessor's search path: add
-Iexternal/<library-name>to theINCLUDESvariable at line 21 of the Makefile.
Library installed system-wide
Some libraries can be installed system-wide, using your system's package manager. For example:
- On macOS, using Homebrew or MacPorts
- On Debian/Ubuntu, using
apt - On Fedora, using
dnf - On Arch Linux, using
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 developers across platforms to start working on your project. Nevertheless, they can be a quick way 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
-devor-develsuffix. -
Link with the library: add
-l<library-name>to theLDLIBSvariable at line 35 of the Makefile.Depending on the library, more than one library name may need to be added with the
-lflag. Refer to your library's documentation for the names to use with the-lflag in this step.Note: for macOS, you may need to link your library using
-frameworkrather than-l.
Library built from source
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
externaldirectory at the root of the project. -
Inside the
externaldirectory, 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 wish to instead add the library as a Git submodule inside the
externaldirectory to make updates easier. -
Add the library's header files to the preprocessor's search path: add
-Iexternal/<library-name>/includeto theINCLUDESvariable at line 21 of the Makefile. -
Add the library's compiled files to the linker's search path: add
-Lexternal/<library-name>/libto theLDFLAGSvariable at line 32 of the Makefile. -
Link with the library: add
-l<library-name>to theLDLIBSvariable at line 35 of the Makefile.Depending on the library, more than one library name may need to be added with the
-lflag. Refer to your library's documentation for the names to use with the-lflag in this step.Note: for macOS, you may need to link your library using
-frameworkrather than-l.
Note that the folder structure inside external/<library-name> will vary from one library to the next. In these instructions:
- The
includesubdirectory refers to a directory containing all of the library's header files. - The
libsubdirectory 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 thelibdirectory. These directories may be named differently: refer to your library's documentation for more information.
Configuration
Frequently changed settings
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 |
27 |
Add preprocessor settings (e.g. -D<macro-name>) |
CPPFLAGS |
24 |
| Change C++ compiler settings (useful for setting the C++ standard version) | CXXFLAGS |
28 |
| Add/remove compilation warnings | WARNINGS |
29 |
Add includes for libraries common to all platforms (e.g. -Iexternal/<library-name>/include) |
INCLUDES |
21 |
Add linker flags for libraries common to all platforms (e.g. -Lexternal/<library-name>/lib) |
LDFLAGS |
32 |
Add libraries common to all platforms (e.g. -l<library-name>) |
LDLIBS |
35 |
| Add includes/linker flags/libraries for specific platforms | INCLUDES - LDFLAGS - LDLIBS |
51-82 |
All the configurable options are defined between lines 1-82. For most uses, the Makefile should not need to be modified beyond line 82.
Platform-specific library configuration
The previous sections explain how to configure 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:
- Adding a library needed only for code enabled on a certain platform
- Using
-frameworkover-lto link a library on macOS - Specifying a different path for a library's compiled files with
-L
The Makefile is designed to support these kinds of platform-specific configurations alongside one another.
Lines 51-82 of the Makefile contain platform-specific INCLUDES, LDFLAGS, and LDLIBS variables which should be used for this purpose. To configure a library for a certain platform, simply add the options to the variables under the comment indicating the platform.
The common
INCLUDES(line 21),LDFLAGS(line 32), andLDLIBS(line 35) variables should only contain options which are identical for all platforms. Any platform-specific options should instead be specified using lines 51-82.
Project hierarchy
.
├── assets
│ └── <assets>
├── assets_os
│ └── linux | macos | windows32 | windows64
│ └── <assets>
├── bin
│ └── linux | macos | windows32 | windows64
│ └── debug | release
│ ├── executable
│ └── <assets>
├── build
│ └── linux | macos | windows32 | windows64
│ └── debug | release
│ ├── **/*.o
│ └── **/*.d
├── docs
│ ├── Doxyfile
│ └── **/*.html
├── include
│ └── **/*.h
├── src
│ ├── main.cpp
│ └── **/*.cpp
├── .clang-format
├── .clang-tidy
├── .gitattributes
├── .gitignore
├── Makefile
└── README.md
License
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.
Table of contents
- Features
- Prerequisites
- GCC & Make
- Optional dependencies
- Usage
- Overview of commands
- Building
- Using a different compiler
- Running
- Assets
- Copying assets
- Cleaning assets
- Cleaning
- Options
- Release
- 32-bit (Windows only)
- Generating a JSON compilation database
- Formatting
- Linting
- Generating documentation
- First time use
- Updating the documentation
- Adding libraries
- Using a package manager
- vcpkg
- Conan
- Header-only library
- Library installed system-wide
- Library built from source
- Using a package manager
- Configuration
- Frequently changed settings
- Platform-specific library configuration
- Project hierarchy
- License