Make
Install make
sudo apt install make
or in rpm
sudo yum install make
make can be installed as a part of build-essentials
sudo apt install build-essentials
Check version
/usr/bin/make --version
GNU Make 4.2.1 Built for x86_64-pc-linux-gnu Copyright (C) 1988-2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.
How Makefiles are typically used
Makefiles are used to reduce the number of useless compillations
Typically this applies to
C
or
C++
.
Other programming languages have their own tools used for the same purpose as Make.
Make can be used not only for compiling but also as a tool tracking updates in files or as a set of commands organized in named groups
In this article you can read about C/C++ compilation and about running multiple bash commands in a more or less organized way.
Below you can see a dependency graph for some project.
If some of the dependencies change - make will trigger recomplation.
Syntax
Makefile is made from rules.
First is target, then dependencies (prerequisites)
and then recipe - set of actions/commands that should be executed
Dependencies are not always needed. For starters we can considre them as files that make should track. When something is updated new compilation should be triggered.
Indentation should be made with TAB. If you do not like to make indentations with TAB - you can set your own option in .RECIPEPREFIX
target: prerequisites
recipe
Typical usage: one or more prerequisites is changed → receipe is executed and target file is created.
output: main.o message.o
g++ main.o message.o -o output
clean:
rm *.o output
As in the previous article Configure, make, install in these example we use stadart targets
You are welcome to read about -o и -c options in a dedicated article «Compiling in C++
Option | Purpose |
---|---|
-c |
Compile or assemble the source files, but do not link. The
linking stage simply is not done. The ultimate output is in
the form of an object file for each source file.
By default, the object file name for a source file is made by replacing the suffix .c, .i, .s, etc., with .o. Unrecognized input files, not requiring compilation or assembly, are ignored. man |
-o filename |
Place output in file file. This applies to whatever sort of
output is being produced, whether it be an executable file,
an object file, an assembler file or preprocessed C code.
If -o is not specified, the default is to put an executable file in a.out, the object file for source.suffix in source.o, its assembler file in source.s, a precompiled header file in source.suffix.gch, and all preprocessed C source on standard output. |
-S | Directs the compiler to produce an assembly source file but not to assemble the program. |
Further reading: gnu.org: Rule-Introduction
If you do not want any files to be created you can you special .PHONY targets
.PHONY
PHONY is one of
Special Built-in Target Names
Let take a look on the following
Makefile
.PHONY: site
site:
echo "HeiHei.ru"
Now if you execute
make site
The output will be
echo "HeiHei.ru"
HeiHei.ru
Remove site from the first line and do not touch the receipe
make site
echo "HeiHei.ru"
HeiHei.ru
Nothing is changed but if you create file site in the same directory with Makefile and execute target again - it will not work
touch site
make site
make: 'site' is up to date.
Now target site is reall and as make did not find changed there it did nothing.
Because of this simple coincidence of target name and file name scrtip can stop working.
PHONY can prevent this from happening
Sometimes PHONY is handy because it is possible to list all targest in the beginning of the file.
Example from C++
Let consider example from header files .h article
There are three files
ls
Functions.cpp Functions.h Main.cpp
Main.cpp
#include <iostream>
#include "Functions.h"
int main() {
double b = add(1.3, 4.5);
cout << "1.3 + 4.5 is " << b << "\n";
return 0;
}
Functions.cpp
double add(double x, double y)
{
return x + y;
}
Functions.h
#pragma once
double add(double x, double y);
If one of those files is updated - project should be recompiled. Let' consider the following command
g++ -o output Main.cpp Functions.cpp
First it will compile then link
Let's creat a new Makefile and open it in a text editor, e.g. in Vim
touch Makefile
vi Makefile
Add the following code to the Makefile
output: Main.cpp Functions.cpp Functions.h
g++ -o output Main.cpp Functions.cpp
To trigger compilation it is enough to execute
make output
or simply
make
As a result we will get the output file
Next actions to be added are: performing compilation separately and cleaning the directory.
If you have difficulties in understanding what is going on the
«C++ compilation»
article may help.
.PHONY: clean
output: Main.o Functions.o
g++ Main.o Functions.o -o output
Main.o: Main.cpp
g++ -c Main.cpp
Functions.o: Functions.cpp
g++ -c Functions.cpp
clean:
rm *.o output
To start script execute
make
g++ -c Main.cpp
g++ -c Functions.cpp
g++ -o output Main.o Functions.o
If you need only to compile Main execute
make Main.o
g++ -c Main.cpp
ls
In this case Main.o will be created but others (Functions.o, output) will not be created
Functions.cpp Functions.h Main.cpp Main.o Makefile
After executing
make Main.o
we can get a hint on why the term target is used in Makefiles
make
Main.o
is a command that aims to create a
Main.o
file
an the receipe in Makefile defines a way how to do it.
If you execute make again
make
Main.o will not be recompiled. Only second and third steps wil be executed
g++ -c Functions.cpp
g++ -o output Main.o Functions.o
Execute make if you did not executed it yet and do not execute clean.
Let's add one more function to our project. We should define it in Functions.* files
We will not call it yet so no need to change
Main.cpp
Functions.cpp
bool test(bool x)
{
return x;
}
Functions.h
bool test(bool x);
make
g++ -c Functions.cpp g++ -o output Main.o Functions.o
Note that
Main.cpp
was not recompiled because it was not updated.
Also not the modification fime of the
output
file - it should change.
Do not change anything and execute
make
make: 'output' is up to date.
There are no changes so no need to recompile.
Variables
.PHONY: clean
objects = Main.o Functions.o
output: $(objects)
g++ -o output $(objects)
Main.o: Main.cpp
g++ -c Main.cpp
Functions.o: Functions.cpp
g++ -c Functions.cpp
clean:
rm *.o output
Run Docker container from Makefile
There is an intro article
«Introduction to Docker»
if you are interested.
Example
.PHONY: docker
docker:
docker-compose -f docker/dev/docker-compose.yml build
Parameters
?= Allows variable to be initialized with existing environment variables value
:= changes variable value
PROJECT_NAME ?= myproject
ORG_NAME ?= heihei
REPO_NAME ?= myproject
#Filenames
DEV_COMPOSE_FILE := docker/dev/docker-compose.yml
REL_COMPOSE_FILE := docker/release/docker-compose.yml
.PHONY: test release
test:
docker-compose -f $(DEV_COMPOSE_FILE) build
docker-compose -f $(DEV_COMPOSE_FILE) up agent
docker-compose -f $(DEV_COMPOSE_FILE) up test
release:
docker-compose -f $(REL_COMPOSE_FILE) build
docker-compose -f $(REL_COMPOSE_FILE) up agent
docker-compose -f $(REL_COMPOSE_FILE) run --rm app manage.py collectstatic --noinput
docker-compose -f $(REL_COMPOSE_FILE) run --rm app manage.py migrate --noinput
docker-compose -f $(REL_COMPOSE_FILE) up test
clean:
docker-compose -f $(DEV_COMPOSE_FILE) kill
docker-compose -f $(DEV_COMPOSE_FILE) rm -f
docker-compose -f $(REL_COMPOSE_FILE) kill
docker-compose -f $(DEV_COMPOSE_FILE) rm -f
BUILD_ID
It is common practice to use BUILD_ID to make variable unique
# Docker Compose Project Names
REL_PROJECT := $(PROJECT_NAME)$(BUILD_ID)
DEV_PROJECT := $(REL_PROJECT)dev
USER_ID
To get ID of the user who executed GNUmakefile you can use USER_ID
USER_ID = $(shell id -u ${USER})
Alternatives to Make
Populat builders for C/C++ are
SCons, CMake, Bazel и Ninja. Some IDEs like
Microsoft Visual Studio
, have their own builders
In
Java
there are Ant,
Maven
and Gradle.
Other languages such as
Go
and Rust, have their own tools
Interpreted languages such as
Python
,
Ruby
or
JavaScript
, do not need direct analog for make.
When script is started it uses the last version of code and that is it.
Meaning of cc -c
cc is a C compiler
There are several commonly available C compilers
In this article
gcc
was used
-c is
here
whoami
In the common
Bash script
it is enough to run $(whoami) and it is equeal to bash whoami command
Most likely it will not work in your makefile. There are two ways to overcome this issue:
`whoami`
and
$$(whoami)
Ignore errors
If there is an error in the receipe the later commands will not be executed.
Let's consider an example
RPM_DIR=/home/$$(whoami)/rpms/ .PHONY: clean-repo clean-repo: @sudo rm $(RPM_DIR)release/* @sudo rm $(RPM_DIR)master/*
If there is nothing in …release/ make will not even try to delete …master/
There will be an error instead:
sudo rm /home/$(whoami)/rpms/release/* rm: cannot remove ‘/home/andrei/rpms/release/*’: No such file or directory make: *** [clean-repo] Error 1
Using - in from of the command will allow make to ignore an error
RPM_DIR=/home/$$(whoami)/rpms/ .PHONY: clean-repo clean-repo: @-sudo rm $(RPM_DIR)release/* @-sudo rm $(RPM_DIR)master/*
[andrei@localhost ~]$ make clean-repo
rm: cannot remove ‘/home/andrei/Downloads/privx/release_rpms/*’: No such file or directory
make: [clean-repo] Error 1 (ignored)
make complains but moves forward and cleans the master directory
make | |
Linux | |
Bash | |
C | |
C++ | |
C++ Header files | |
Configure make install | |
DevOps | |
Docker | |
OpenBSD | |
Errors make |