Make & Makefiles

NamyaLG
Dev Genius
Published in
4 min readApr 2, 2022

--

As Wikipedia says, Make is a build automation tool that automatically builds executable programs and libraries from source code by reading files called Makefiles which specify how to derive the target program.

Why are build systems required?

  • Simplify the process of managing a lot of program files
  • Compile only files that have changed (Save on time and resources)
  • Automate linking and compilation of files

I'll be considering a C/C++ based project, the same idea is applied to all languages alike

To put it in context, consider a very large project, containing a lot of files, each file in turn is dependent on many other files. An obvious approach is to develop a modular structure of the code such that, the separate components are placed in separate files, however, these files need to work in unison. In a non-makefile approach, building the executable every single time will involve compiling the .c/.c++ files to give the object files and linking all of these. This is a very painful process and one that can be automated and simplified. It’s a smart recipe to build your executable.

Steps to create an executable in c++

  1. Preprocessing: #include and #define are resolved. This means, the content of the header files is placed in the file, and values referenced by #define are placed as is in the code.
  2. Compiler: Converts the cpp file to assembly code.
  3. Assembler : The next step is to create object code from the assembly code
  4. Linking: Link the object files to create an executable

In C++, object files are created by :

gcc -c name-of-file.cpp

Various object files can be linked as follows to create an executable, here final-exec is the final executable

gcc obj-file1.o obj-file2.o -o finalexecutable

Makefile

A Makefile is written in the following format :

target: dependencies
action #needs to be preceeded by a tab NOT spaces

The target indicates what is being built, dependencies indicate that the target is dependent on the dependencies, and the action refers to what has to be done to get the target from the dependencies

An example Makefile

# -*- MakeFile -*- (for comments)# target: dependecies#   action (after a tab only)# final executable created (only has a target and dependencies)all: finalexec# final exec depends on the object files file1.o an file2.ofinalexec: file1.o file2.o# action needed to create finalexecg++ file1.o file2.o -o finalexec
# object file of file1 depends on file1.c++file1.o: file1.c++g++ -c file1.c++# object file of file2 depends on file2.c++file2.o: file2.c++g++ -c file2.c++
# removes the object files, can be run as make cleanclean:rm -rf *.o

A dependency graph is created here, the first instruction to be performed is the one that does not depend on any other files, eg: here the instructions file1.o and file2.o are the target, can be performed first as they do not have any dependencies.

Dependency graph of the various targets

In a sense, the order of execution of instructions in a Makefile can be determined by a topological ordering (As defined by Wikipedia, In computer science, a topological sort or topological ordering of a directed graph is a linear ordering of its vertices such that for every directed edge uv from vertex u to vertex v, u comes before v in the ordering) of the dependencies and targets, extra consideration is that only for an instruction where there is a change in a file, its direct and indirect connections will be compiled again. In the dependency tree, considering the executable is at the highest level, the concept of timestamps is used to decide which branch should be recompiled and what should not.

Remember: The files lower in the dependency graph, are compiled before the higher levels (the executable is the last to be created). If a file has been compiled or last created at a time later than a file above it, then it means a change has been made to the file, hence that branch will require re-compilation.

The timestamp is referred to as the system time.
Wikipedia says: For example, Unix and POSIX-compliant systems encode system time (“Unix time”) as the number of seconds elapsed since the start of the Unix epoch on 1 January 1970 at 00:00:00 UT, with exceptions for leap seconds.

Order of executing instructions when the command `make` is run

But there could be a possibility of a cyclic dependency, where a target file f1, has a dependency of file f2, and the target file f2, has a dependency of file f1 (Not sure how this is resolved, maybe an alert is raised)

References
- https://www.youtube.com/watch?v=DtGrdB8wQ_8
- https://www.youtube.com/watch?v=GExnnTaBELk&t=1s
- https://www.youtube.com/watch?v=upX8WjCITXg&list=PLalVdRk2RC6rektqao7a6_mMQJAZPbERk

The above-mentioned are super useful references!
-

--

--