Colleges will be back in session in a month or two. When I first started school, I was forced to use Unix for all CS classes, but given very little instruction in it. Very “sink-or-swim”. I actually still have a page of notes where I wrote down basic commands, such as ‘ls’ and ‘pwd’, from a lecture given on the first day. After that first day, you were on your own.
This same philosophy applied to our projects. Projects typically consisted of 5 or more files and it quickly became tedious to compile and link all those files by hand. We were given no hints to use Make to assist us. After discovering this tool, it greatly increased my productivity. Because of the gains I saw, I thought I’d write a few tutorials on using Make. Hopefully, it will help a beginning user to get accustomed with the tool and point them in the right direction to learn more.
What this tutorial covers:
- What make is.
- How to determine if and what type of make you have.
- How to build a basic makefile with no dependencies
- How to build a makefile using dependencies.
- Tips for making your makefile better
What I expect you to already know:
- Basic ability at the Unix command line.
- To know some programming language (I’m using C for this tutorial, but any will do).
If you’ve never used make before, you’re probably asking yourself what is this tool and why should I use it? Quoting from make’s website, “Make is a tool which controls the generation of executables and other non-source files of a program from the program’s source files.” Kind of confusing though, right? Well, have you ever had a project that had more than 1 source file? I’m sure you got tired of typing “gcc file1.c file2.c …” after a while. Removing object files is also a hassle. Well, with make, you can build, clean up, and install your project, all with one step.
For this tutorial, I’ll be using about GNU make. This is a version made by the GNU Project. There are other versions of make out there; I know that Sun makes one. All versions of make are similar, but the subtle differences make it difficult to generalize about all of them at once.
If you are using a Linux computer, you most likely have make already. To find out, type ‘make’ at a command line. You should see something like:
$ make make: *** No targets specified and no makefile found. Stop. $
(The $ is a command prompt sign.)
If you are on another type of Unix computer or don’t know what the operating system is, open a command prompt and type ‘make –version’. If you have GNU make, you will see something like:
$ make --version GNU Make 3.81 Copyright (C) 2006 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. This program built for x86_64-pc-linux-gnu $
If you see something different, such as a warning, you have a different version of make. Try using the ‘gmake’ command. (This stands for GNU make.)
If you don’t have make installed at all, you can use your operating systems package manager, get a download from Savannah, or email your system administrator.
If you have Windows, you can get make here.
Let’s start with an example.
Create two files (I’ll be using C for this tutorial.) that both get compiled into one executable. Something like file1.c, file2.c, and file2.h. To make an executable called ‘demo’, you would have to type ‘gcc -o demo file1.c file2.c’. As you make changes to the files, that will get tedious as well as typo prone. Instead, make can automate this whole process
To use make, you write what’s called a makefile. You can name this whatever you want, but if you name it ‘makefile’, make will find it automatically.
Inside a makefile, you create rules to build a target, based on certain dependencies. These are three terms you will hear a lot when dealing with make. For the above, example, here is an example makefile (note that you must use tabs, not 4 spaces in makefiles):
all: gcc -o demo file1.c file2.c clean: rm demo
If you save that in a file called ‘makefile’ and type ‘make’, make will build your project:
$ make gcc -o demo file1.c file2.c .$ ./demo Hello world! $ make clean rm demo $ ls file1.c file2.c file2.h makefile $
The session above builds the executable, runs ‘demo’, and automatically cleans up.
In the makefile, ‘all’ is a target. It is special though since it is the first one listed in the makefile. If you just type ‘make’ at a command prompt, the all target is used, as you saw above. To run a different target, you type ‘make target‘. An example of this is the ‘make clean’ command above.
When you build a target, it will run the list of commands that you have specified in the makefile. Note that these commands should be on their own line and have a tab in front of them, as in the example.
That makefile can greatly speed up your work by not making you type a bunch of gcc commands, but it doesn’t use any dependencies. What I mean is that if you type ‘make’, it will always build the ‘demo’ executable, even if it is already built and the source files haven’t changed since the last build. Clearly, that’s doing too much work. To address that, we can add demo as a dependency of the ‘all’ target. Our modified makefile now looks like:
all: demo demo: gcc -o demo file1.c file2.c clean: rm demo
What changed here? We added ‘demo’ as a dependency as of all. That means that whenever you type ‘make’, it will check to see if a file called ‘demo’ is present. If it is, the tool just exits. If it’s not though, it will build the demo executable by processing the demo target.
One thing I’d like to point out though is that while targets usually refer to a desired output file, this is not required. Instead, they can simply be a set of actions that you want to perform. For example, you may have a target that moves the output files to an appropriate obj/ directory or, an even better example, clean up object files, which is what the clean target does.
If you use the makefile above, you may notice that if you change the source files and don’t delete the demo executable, this makefile will not rebuild it. That’s not what we want. We can make this makefile better by rebuilding the executable whenever the source files are changed, even if the executable is already present. We can do this by adding file1.c and file2.c as a dependency to the demo target. To do this, add ‘file1.c file2.c’ after ‘demo: ‘. That way, whenever you change file1.c or file2.c, demo will be rebuilt.
There is a problem with this approach though. Let’s say your executable was made by compiling 100 files instead of 2. The way described above would compile all 100 whenever you changed even one file. That’s just doing too much work. Instead, you should make your targets depend on object files and simply link them together. Doing it that way will only build the file that you changed instead of all the source files. Below is a makefile to do this:
all: demo demo: file1.o file2.o gcc -o demo file1.o file2.o clean: rm demo file1.o file2.o
The above makefile now depends on file1.o and file2.o rather than the corresponding .c files. You might be wondering how make will know how to build the .o file since we didn’t specify specifically how to do this. Well, a neat feature of make is that for C and C++, it can build them automatically! Don’t believe me? Here’s some output that this project will produce:
bash-3.2$ make cc -c -o file1.o file1.c cc -c -o file2.o file2.c gcc -o demo2 file1.o file2.o bash-3.2$ vim file1.c bash-3.2$ make cc -c -o file1.o file1.c gcc -o demo2 file1.o file2.o bash-3.2$
When you first type make, it automatically generates the object file by calling the C compiler. Then it goes back to the demo target and links them using the gcc command you specified.
If you aren’t using C or C++ to make your project, simply add another target to build the files that your executable depends on.
Between the 1st and 2nd call to make, I made a change to file1.c. When make is run again, it updates file1.o. Note that it updates only file1.o though! This saves effort since there is no need to rebuild file2.o.
The concepts presented above will be enough to get you started using make. It can be kind of difficult to properly set up your first few makefiles, but after a while, it should become pretty intuitive. The structure of the makefile I presented above should do the job for most projects where you only have one executable.
You can find the final makefile here.
In the next installment of this tutorial, I plan to discuss:
- Using variables.
- Special make variables.
- Integrating with the shell
I hope this tutorial was helpful to you and good luck with make!