A Practical Makefile Setup that helps you be more productive

  • Thread starter Thread starter Ranpu
  • Start date Start date
Joined
11/22/24
Messages
13
Points
153
When creating C++ projects, we often struggle with the tedious cycle of compilation and execution. The manual process of remembering compiler flags, tracking dependencies, and ensuring all files are properly rebuilt can become a significant burden as projects grow.

Makefile designed to eliminate these common frustrations. To help us understand the power of it, lets start with a simple project !

1. functions.h (Header File)
  • The header file contains function declarations but does not provide implementations.
  • It is included in other .cpp files so they can use the declared functions.
2. printhello.cpp
  • This file contains the implementation of the printhello() function.
  • It includes functions.h to ensure that the function signature matches its declaration.
  • This file will be compiled separately and linked with the main program.
3. factorial.cpp
  • This file implements the factorial(int) function.
  • Like printhello.cpp, it also includes functions.h for consistency.
4. main.cpp (Main Program)
  • The entry point of the program, where execution starts.
  • It calls the printhello() and factorial(int) functions.
  • Includes functions.h so it can reference these functions correctly.
  • It does not contain the function definitions but relies on linking with printhello.cpp and factorial.cpp.
1742507750396.webp


However, When a C++ project consists of multiple .cpp files (e.g., main.cpp, printhello.cpp, factorial.cpp), manually compiling and linking all files every time is tedious. For example:
1742507797829.webp

If the project is complex, recompiling all files every time you modify one file is inefficient and time-consuming.

One of the solution is using makefile. A makefile automates the compilation process and ensures that only modified files are recompiled. Instead of manually entering multiple g++ commands, you can simply run:
make ./output

While Makefiles offer numerous capabilities—including automatically detecting project dependencies and intelligently handling compilation, execution, and cleanup—I'll begin with the simplest scenarios and rules to help everyone understand the fundamental logic behind Makefiles.

Of course, if you prefer brevity, feel free to skip to the end and simply copy my Makefile template, which includes most functionality and scenarios that beginners are likely to encounter in this course.

Version 1:
1742508095818.webp

· Compiles and links all .cpp files in one step to generate the executable hello.
· Does not create .o (object) files, meaning all files are recompiled every time, even if only one file changes.
1742508224221.webp

Version 1 is Suitable for small projects, but inefficient for larger projects as recompilation takes longer.


Version 2:
1742508254809.webp

·Compiles each .cpp file separately into an .o file, then links the .o files to create the executable hello.
·Supports incremental compilation:
·More suitable for medium to large projects, as it significantly improves compilation efficiency.
1742508384325.webp

You only need to type "make" and makefile will compile everything.
However, both these methods have a drawback: we still need to manually input filenames from our project. Makefiles actually have the ability to automatically locate files, which can save us significant time. Imagine repeatedly creating and deleting files during development—each time requiring you to add or remove these filenames. To address this, I'm providing a third version with automatic compilation capabilities.

Version 3
1742508460136.webp

This version automates the compilation of a C++ project by detecting all .cpp files, compiling them into .o object files, and linking them to create an executable (hello). It supports incremental compilation, meaning it only recompiles files that have changed, making the process more efficient. Additionally, it includes a clean rule to remove compiled files when necessary. The key advantage is that new .cpp files are automatically included without modifying the Makefile.
1742508531408.webp

-Wall
→ Enables compiler warnings to detect potential issues, that’s why we can see a warning here.

My customized makefile
Of course, Makefile's capabilities extend far beyond this. I understand that developers sometimes have more sophisticated requirements. Therefore, I've customized a Makefile specifically for this C++ course. It can:
· Automatic compilation - Uses find . -type f -name '*.cpp' -not -path "*/.*" to automatically discover all C++ files in your project directory structure, excluding hidden files and directories.
· Dependency tracking - Employs compiler flags -MMD -MP to auto-generate dependency files (.d) that track header dependencies; these are included with -include $(DEPS) to ensure files are rebuilt when any dependency changes.
· Organized output - Uses $(patsubst ./%.cpp,$(BUILD_DIR)/%.o,$(SOURCES)) to transform source paths to equivalent build directory paths and mkdir -p $(@D) to automatically create subdirectories as needed.
1742508873122.webp

Besides "make, make run, make clean," I've also customized various functions that can check your basic syntax and code style errors.


make check - Checks all source files for syntax errors
make check-headers - Syntax-checks all header files
make check n=file.cpp - Checks a specific file
1742508956534.webp


make check-cppcheck - Performs static analysis on all source files
make check-headers-cppcheck - Static analysis for all header files
1742509006507.webp

However, it's important to note that this Makefile doesn't actually "compile" individual header files into object files, which is standard C++ practice. Headers are meant to be included in .cpp files which are then compiled.
Basically, its track header dependencies through the auto-generated .d files, ensuring that when you modify a header file, any source files that include it will be recompiled automatically.

make run-file FILE=your_file.cpp
1742509050116.webp

While standard C++ projects typically have a single entry point, the added run-file command enables running individual CPP files, which is useful for:
Testing small, self-contained code snippets or quick experimentation without modifying the main project.
This is a convenient alternative to manually compiling and running files in the terminal.
Especially when it involves with Boost, you need to type such for a single compile.

clang++ -std=c++17 -stdlib=libc++ -Wall -Wextra -g -I/opt/homebrew/Cellar/boost/1.87.0/include main.cpp european_option.cpp mesh_generator.cpp option_data.cpp perpetual_american_option.cpp -o program
 

Attachments

  • 1742507856652.webp
    1742507856652.webp
    1.3 KB · Views: 0
  • 1742508162824.webp
    1742508162824.webp
    4.5 KB · Views: 0
  • 1742508206666.webp
    1742508206666.webp
    9.9 KB · Views: 0
  • makefile.docx
    makefile.docx
    21.8 KB · Views: 2
Back
Top Bottom