I’m happy to have a new contributor to my project, who did some cleanup to Makefile (thanks, @buoto!). I thought it’s a good time to write a few words on how the make tool works.
Windows users: to check if you have make
installed on your machine type in your command line: make --version
. If you don’t, I suggest downloading Gow, which will equip your Windows with most useful Linux commands. Windows 10 users can use Windows Subsystem for Linux feature (how to enable).
What is make
tool for?
Reading my tutorials, you could notice I compile many of my examples by hand, i.e. I run commands like this every time:
$ clang++ -std=c++14 timepoints.cpp -o timepoints.exe && ./timepoints.exe
It takes time, is repetitive, it’s easy to forget some option or make a typo somewhere. Another problem is, the set of commands working on one machine may not work on another because of e.g. different OS installed.
make
provides a unified environment and a framework for building projects. Not only it helps with managing compilation instructions, but also keeps track of what dependencies were already built and what can be reused. For example, C++ files are compiled into object files (.o
). If nothing changed in a particular module, then the corresponding code doesn’t have to be recompiled. The bigger the project the more time you save.

GNU make is probably the most popular implementation
Basic Makefile
We have a simple “Hello World” file. We usually compile it like this:
$ g++ main.cpp -o example $ ./example Hello, make!
Now we would like to use the new method. Create Makefile in your folder:
# This is a sample Makefile build: g++ main.cpp -o example
The syntax is really easy. You put target name, they semicolon. After that, in new lines (starting with a tab!) go commands. If you run make build, g++
will compile the file. You will see all the commands as if you typed them yourself.
Using macros
You can use variables in your Makefile. Some of them are already provided for you, like CXX (c++ compiler tool) or OS. To set variable name, just use normal assignment. To get variable value, use the dollar sign and braces, e.g. $(BIN_DIR)
.
# This is a sample Makefile CXX = clang++ BIN_DIR = bin EXECUTABLE = example build: @echo Building on $(OS): mkdir -p $(BIN_DIR) $(CXX) main.cpp -o $(BIN_DIR)/$(EXECUTABLE) run: ./$(BIN_DIR)/$(EXECUTABLE)
What has changed:
- We have set the compiler to clang++ (you can use anything)
- build target shows what OS we are using (@ at the beginning will suppress writing command to stdout)
- Executable is put in bin directory now
- We have a new rule to run the executable
We can order our tool to build multiple targets at a time, so we will run our program afterward:
$ make build run Building on Windows_NT: mkdir -p bin clang++ main.cpp -o bin/example ./bin/example Hello, make!
Dependencies
Let’s say we need to extract some functionality to another file. Here are the modifications:
main.cpp
#include <iostream> #include "greetings.hpp" int main() { greet(); return 0; }
greetings.hpp
#ifndef GREETINGS #define GREETINGS #include <iostream> void greet(); #endif
greetings.cpp
#include "greetings.hpp" void greet() { std::cout << "Hello, make!" << std::endl; }
Now if we run make build run new files won’t be taken into consideration:
$ make build run Building on Windows_NT: mkdir -p bin clang++ main.cpp -o bin/example C:\Users\kantoniak\AppData\Local\Temp\main-724567.o:(.text+0x67): undefined reference to `greet()' clang++.exe: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [build] Error 1
We have to update Makefile with dependencies. Basically, we say that particular rule needs another to finish before. For example, to build executable, we need source files to be compiled and then we just link all of them.
# This is a sample Makefile CXX = clang++ OBJ_DIR = obj BIN_DIR = bin EXECUTABLE = example $(OBJ_DIR)/greetings.o: greetings.cpp greetings.hpp mkdir -p $(OBJ_DIR) $(CXX) -c greetings.cpp -o $(OBJ_DIR)/greetings.o $(OBJ_DIR)/main.o: main.cpp greetings.hpp mkdir -p $(OBJ_DIR) $(CXX) -c main.cpp -o $(OBJ_DIR)/main.o build: $(OBJ_DIR)/main.o $(OBJ_DIR)/greetings.o @echo Building on $(OS): mkdir -p $(BIN_DIR) $(CXX) $(OBJ_DIR)/main.o $(OBJ_DIR)/greetings.o -o $(BIN_DIR)/$(EXECUTABLE) clean: rm -rf $(OBJ_DIR)/* rm -rf $(BIN_DIR)/* run: ./$(BIN_DIR)/$(EXECUTABLE)
It means that before build target will be made, it needs obj/main.o and obj/greetings.o. Actually, these files get compiled before:
$ make build run clang++ -c main.cpp -o obj/main.o clang++ -c greetings.cpp -o obj/greetings.o Building on Windows_NT: clang++ obj/main.o obj/greetings.o -o bin/example ./bin/example Hello, make!
Need more?
The basics are covered. But make tool is much more, and there are even more tools extending its functionality, adding GUIs, etc. You will find other resources here:
Here comes the hate:
I love make tool and I’m big supporter of Saint IGNUcius church 🙂
What really annoying is: Popup about subscribe to your newsletter during article scrolling!
Thanks for your comment. I removed pop-ups completely in favor of static block in the content. I hope it isn’t too pushy.
Awesome 🙂
“I’m happy to have a new contributor to my project, who did some cleanup to Makefile (thanks, @buoto!). I thought it’s a good time to write a few words on how the make tool works.”
What have been changed? Could you tell, what was wrong with your makefile? 🙂
These were rather minor changes, except for removing the additional dependency on windres on Linux, what, frankly, I couldn’t fix myself. You call see the PR here: https://github.com/kantoniak/konstruisto/pull/1/files