Make

From UCT EE Wiki
Revision as of 12:36, 10 August 2021 by Swinberg (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search


Overview[edit]

Make is defined as a "build automation tool" which automatically builds executable programs, libraries or other type of target (desired file) from source code. Usually this desired 'target' is an program that you want to run. For full details on make and makefiles see the official GNU Make documentation at https://www.gnu.org/software/make/manual/make.html. Make is a way to capture and automate the build process, to capture the compile process, how the generation of intermediate files depend on other files. If you are not already familiar with the reason for using Makefiles in a project you are recommended to read the Gnu Make Overview.

Makefiles are not limited to just making executables from C / C++ or compiling of code. They can be used for generating other kinds of execution objects which might involve generating some sort of runtime environment in which the exucatable, or executables, run within.

Of course, IDEs often use a makefile or other method of compiling behind the scenes, and you may likely not need to both with needing to edit the makefile itself. The IDE is likely to provide a settings menu where you can specify things like include library paths and compiler options. However, there may still be occasions where you need to manipulate the makefile itself to adjust the method being used to generate you target file(s).

Planning a Makefile[edit]

The first thing, before you attempt writing a makefile is to know how the application you want to make is generated. Knowing of whatever compilers, assemblers, linkers and other tools to go from the code to an executable. When talking about C and using GCC, the essential thing you should hopefully know already, before you attempt constructing a makefile, is the basics of the gcc command.

Before making a makefile, you should know what tools that are going to be used in the compile process, and the sequencing of these tools for building your executable.

There may be various other commonly used operations, related to the build process, that might not specifically be part of the build process but needed in starting or setting up the build process, the development environment or the execution environment that you are wanting to utilize. An example of such a process is the ‘clean’ process, which deletes any intermediary files (e.g. object files) and the previously compiled executable. Applying the 'clean' process generally forces the build processes to remake the executable from scratch the next time it is run.

Other common make process are:

  • ‘mark’: which is not really what tutors use for marking your code (it could be!) but rather some companies use it to make a significant state of the project e.g. mark a build in some way, possibly sending it to a repository.
  • ‘backup’: obvious one this. Apply some backup operation, maybe zipping up the project files and uploading it to some file store.
  • ‘test’: either generating testing code (sometimes one generates automated testing code, e.g. a separate project that will test your project) and/or runs tests through your already compile application.

To make this page brief and to give you a quick start to makefiles, a simple example of a makefile for compiling a C program is given below. The subsequent section lists some useful tutorials that you may want to explore further to improve your makefile writing skills.

Examples[edit]

Simple example makefile[edit]

To illustrate the process of developing a makefile, let us consider we want to make a program from the following files:

  test1.c 
  test1.h

i.e. test.c has H-file interface called test.h

We want to compile test1.c with gcc and we want to generate a GDB debugger compatible executable, which needs the -g flag to be specified. We want to call the generated executable test1 (an elf file, not needing extension if done on Linux). We could do this easily with just gcc:

  gcc -g -o test1 test1.c

But this isn’t scalable. And we haven’t captured the information of dependencies and build process (e.g. so future developers or users will see how to build the program).

Let's quickly plan the build method for this. It would be useful to have these two processes available:

  • clean process : to deletes all intermediary files and the executable.
  • build process (the default process) : generate the executable if any of its dependencies (as in the .c or .h files) have changed since the last attempt at building.

Here is a super simple start for the makefile, although for the sake of explaining a little more, some special variables, such as $@ and $< have been used (to save some typing) and these are explained after the example. Together with what is being done on each line.

 1 CC=gcc
 2 # note the -g below for debugging
 3 # and note that hash symbol used for single line comments
 4 CFLAGS=-I. -g
 5 
 6 test1.o: test1.c test1.h
 7         $(CC) -c -o test.o $< $(CFLAGS)
 8 
 9 test1: test1.o
10         $(CC) -o $@ $^ $(CFLAGS)
11 
12 clean:
13         rm -f -r test1 test1.o

Explanation of special variables used in dependency rules:

Variable Explanation
$@ the name of the output to generate, the LHS of the dependency rule.
$< filename of first prerequisite in the dependency definition. For the first dependency definition, i.e. in the entry “test1.o: test1.c …” the first prerequisite corresponds to test.c. So to make test1.o it will call the command “gcc -c -o test.o test.c -I. -g”.
$^ This corresponds to the filenames of all the prerequisites, separated by spaces. So, for the case “test1: test1.” the command that will be invoked is “gcc -o test1 test1.o -I. -g”.

And you might wonder What is the ultimate target or root target? This is something you can effectively force by using a so-called 'Phoney Targets', explained in GNU Make documentation. But if you don't create rules to make the target more explicit, then make will automatically decide (in this case) test1 is the root target, as it is at the base of the dependencies tree (as in there are no entries dependent on test1. And in case you were wondering, 'clean' is not dependent on test1 as test1 is not in its dependency definition, i.e. not listed on the RHS of the dependency definition. So, test1 will be be decided as the file you want to make by default, when calling make with no arguments).

More involved example using wildcards[edit]

Makefile demonstrating wildcards and macros

The following makefile snippet shows how you can set up wildcards / regular expressions for making more generalized rules, as well as saving on typing. This example essentially set a rule to say that every o file that the target is dependent on will be generated using the using $CC -c -o command. If there were lots of .o files listed in the OBJ variable you won't need to add additional entries to explain how the compiling of these c files would work, unless they are compiled in some other way from this first case.

 1 CC=gcc
 2 # note -g flag used so that GDB debugger can be used
 3 CFLAGS=-I. -g
 4 DEPS = test1.h  # list of dependencies for all C files
 5 OBJ = test1.o   # list of object files
 6 
 7 %.o: %.c $(DEPS)
 8         $(CC) -c -o $@ $< $(CFLAGS)
 9 
10 test1: $(OBJ)
11         $(CC) -o $@ $^ $(CFLAGS)
12 
13 clean:
14         rm -f -r test1 *.o

Recommended Tutorials[edit]