Each programming language has its own set of commands for turning source code into a executable program. Back in the olden days this was done using an old and by today’s standards a tool with limited functionality. This tool as call make and when combined with a configuration file called the Makefile it would perform a number of steps utilizing any of the programs installed on your computer to generate your executable.for
Usually, the steps are easy to transform source code into programs, at least in theory.
hello.h
#define TEXT "hello world!"
hello.c
#include
#include "hello.h"
int main(int argc, char **args)
{
printf("%s\n", TEXT);
return 0;
}
You invoke the name of the compiler to read the source code and output the finished program.
gcc hello.c -o helloworld
The compiler reads in hello.c and creates the program with the name helloworld.
Yet, that is too simplistic for pretty much all commercial programs in the world. For programs more complicated than this the code base is usually split across possibly dozens or even hundreds of files. It would be too much work just to type all the names on the command line each time. One possibility, which is pretty inefficient, is to have a script to compile all files every time it is executed. This is inefficient as it will compile all files even if only a single file has changed.
The goal of the make utility is to allow us to define relationships between the various source files. Thus only the minimum subset of files are rebuilt when a change occurs.
The two important concepts is that of dependencies and and targets. You simply define a target, ie a set of rules to be executed, whenever one of the dependencies is newer.
Anatmony of a Makefile
CFLAGS = -Wall -I.
HEADER = hello.h
SOURCE = hello.c
TARGET = helloworld
all: $(TARGET)
$(TARGET): $(SOURCE) $(HEADER)
gcc $(CFLAGS) $(SOURCE) -o $@
Make has variables called macros. There are a few special predefined macros which when are evaluated return either a file or rule name.
$@ | name of the target rule name |
$? | list of dependencies more recent than target |
$^ | all dependencies regardless of if they are more recent than target |
These predefined macros are very convenient but it is also possible to define any user defined macros to hold any values. These user defined macros are used to help make the process as generic as possible but also when setting up special or unique rules. Macros are usually used for the same reason variables are used in programs, it is possible to define the value at one location and use it in many.
Makefile new rule
clean:
rm -f $(TARGET)
Of course the make command is used for transform source code into programs but it is possible to define any arbitrary set of commands as part of the build process. It is possible to have rules setup to perform certain pre-processing steps prior to the build or other rules that are helpful during development. These rules might even be to install the program or create a environment.
Has make’s time has come, and gone?
A makefile is a very useful tool to run a compiler to generate object code. As previously mentioned, it is possible to take any command line program and execute it as part of a target. This program could be like lint which is used to analyze source code for programming errors. Yet it is possible to create your own scripts or programs designed to perform other “special” tasks that are unique to your own company.
Although make is a nice lightweight tool other solutions such as Maven, Ant or Gradle can offer more than just building a program. Maven for example is built around a life-cycle of the program.
- compile the source code
- compile the unit tests
- run the unit tests
- package up the program or library
- deploy it
Not only that but Maven actually stores all of the Maven known dependencies. Not only that just stores but will retrieve them as needed. This is not just a single version of a jar file but can actually internally maintain several versions of this same jar.
Not only that but Maven also can be used to create a template of your project. This is both convenient for junior developers and helps to ensure a certain level of consistency within the organization.
Make is only limited by your imagination however if you want to support the same level of functionality (described in last two paragraphs) would require special effort by the developers and no matter how good they are the ability to download and keep track of different dependency versions would not be as good.
Each tool has its own uses and for small projects with no dependencies that have unique steps may be a great task for Make. That said, there does not seem to be a great clambering in the market for people with makefile knowledge but rather those familiar with a fresher toolset such as Maven. I have a few brief blog posts for anyone interested in getting their feet wet on this topic.