Tutorial and Notes on the MAKE Program


FIRST STEP INTO MAKE

  1. Introduction to Make The "make" software is a tool for maintaining files in projects. For instance, whenever you modify a Java program, you might want to recompile all the other programs that depends on the modified Java program. This action can be automated using "make". The instructions for "make" is an ASCII file that you write. The name of this file is usually called "Makefile" (capital "M"). We will assume this name below.

    "make" is a standard UNIX tool, but can be found in Windows environment as well (although it is usually hidden in the IDE's which automatically generates "Makefiles").

  2. Special filenames If you call "make" without specifying the file, the file in the current directory with name "Makefile" will be read.
  3. Sample Makefile Here is a sample Makefile that you can download and examine.
  4. Variants of "make" There are many variant of "make". Perhaps the most common variant is "gmake" (the GNU version of "make"). In these notes, I refer to all these variants by the name "MAKE".
  5. How does MAKE work? You can simply invoke MAKE by just issuing the command "MAKE [target]" where [target] is the name of a task that you want to accomplish.

    Then MAKE will read from a file named "Makefile" in the current directory. The Makefile will associate with each possible [target] with one or more "actions". E.g., you might associate the action "javac MyClass.java" with the target "compile". Then when you type "make compile", the action to compile "MyClass.java" will be taken.

  6. How I use MAKE in classes For each programming assignment in my course, I require students put all the requisite files into a single directory, and to provide a "Makefile". This is the first file that I look at in order to understand the structure of your directory.
  7. Makefiles as self-documentation A Makefile is an executable documentation of your work! It tells me what are the tasks (targets) in this directory, how to accomplish each task, how each file is dependent on other files, and so on.

    Always make sure that the default action (i.e., simply invoked by "make") results in a useful action.

  8. Basic Semantics of MAKE program As the previous example shows, a target line and its associated action lines has the following form:
    		[target_name1] [target_name2] ... : [dep_name1] [dep_name2] ...
    		---> [action_1]
    		---> [action_2]
    		...
    	
    E.g., to compile and run a program called "Box.java" we might have a target called "run" or "r" (for short):
    		run r: Box.java
    		---> javac Box.java
    		---> java Box
    	
    YOU MAY SKIP THE FOLLOWING EXPLANATION IF YOU COULD INFER THE BASIC MEANING OF THE TARGET/ACTION LINES!

    The [target_name1], [target_name2], ... are alternative names for the same target.

    The [dep_name1], [dep_name2], ... are names of the dependencies. Note the colon ":" that separates the target names from the dependencies.

    The target line is immediately followed by a list of actions, one per line, each line beginning with a tab.

    If a target name is a file, the time-date of the file is remembered as our "target time-date". If there are multiple file names, presumably the oldest time-date is chosen (this is not clear).

    The [dep_name1], [dep_name2], ... are names of files or names of other targets. Thus the current target DEPENDS on these files or targets.

    If [dep_name1] is a target name, it will first invoke this target (recursively).

    For each [dep_name1] that is not a target name, but a file, make will make sure that a file by this name exists. If it does not exist, we return failure. Otherwise, we check if it has a date-time older than the target time-date. If so we continue to process [dep_name2], etc. If not, it will perform the actions of the current target: [action_1], [action_2], etc.


HINTS

  1. Comments
  2. Using multiple target names
  3. Variables
  4. Types of Variables and Assignment
  5. Command Line Arguments
  6. Platform Customization
  7. Using For-Loops
  8. Using include files
  1. HINT: Comments    [back to top]    [back to hints]

    As noted above, a Makefile can be viewed as an executable documentation of tasks relevant to the current directory. By scanning through this "documentation" I can identify the tasks (target names), and what is involved in those tasks, etc. It is clearly important to put comments into documentations.

    HOW DO YOU PUT COMMENTS INTO A MAKE FILE? The COMMENT CHARACTER is "#".

    The make program is line-based. On each line, it will ignore the rest of the line following the first occurrence of the "#" character.

  2. HINT: Using multiple target names    [back to top]    [back to hints]

    Each target can have multiple names. I suggest using short names in addition to fully descriptive long names for the same target.

    E.g., the following target has four equivalent names:

    		la latex p.dvi p.ps: p.tex
    		---> latex p 
    		---> dvips p -o
    	
    Note that "--->" is a representation of the TAB character. It is essential to use a single tab at the beginning of the line following the target, as shown.

    Now, typing "make la" or "make latex" or "make p.dvi" or "make p.ps" will invoke the same action. The action consists of calling "latex p", followed by "dvips p -o".

  3. HINT: Variables    [back to top]    [back to hints]

    take advantage of variables in our Makefile. You do not have to declare the variables before using them!

    ALL VARIABLE IN THE SHELL'S ENVIRONMENT ARE AUTOMATICALLY INHERITED BY THE MAKEFILE. E.g., the variable PATH will have a list of search paths.

    If you have assigned a variable named "X", you can get its value by writing "${X}" or "$(X)". E.g.,

    	# VARIABLES:
    	X = 2
    	# TARGETS:
    	default:
    	---> @echo The value of X is ${X}
    	
    This will print the message "The value of X is 2". Notice that we used "@echo" instead of a plain "echo". The "@" symbol tells make to execute this command silently, instead of printing the command on the screen before executing it. Otherwise, the output of this target may look a bit confusing for the obvious reasons.

    We can now elaborate the latex example by using a variable for the name of the latex file. Choose a short variable name like "p" for the name of the file.

    		p=foo
    		la latex dvi ps: ${p}.tex
    		---> latex ${p} 
    		---> dvips ${p} -o
    	
  4. Types of Variables and Assignment    [back to top]    [back to hints]

    It turns out that there are three kinds of assignments:

    		 = (recursive kind)
    		:= (standard assignment)
    		?= (conditional assignment)
    	
    This distinction may be special to GNU Make, and not universal. In describing variables above, we only use the recursive kind (=). The conditional kind (?=) is easy to understand: the assignment takes place only if the variable has no previous value.

    The recursive kind (=) is so called because, when the right hand side is a variable name, its value is used. If the value is a variable name, this expansion continues indefinitely. When MAKE detects an infinite loop, it will tell you.

    The standard kind (:=) does this expansion of value once. Hence it is more predictable, and recommended when you recursively process Makefiles in subdirectories.

    The variable on the left-hand side of the recursive or standard assignment is called a recursive or standard variable, respectively. (This terminology may be nonstandard).

    EXAMPLE OF USE OF STANDARD VARIABLES: In Core Library, we like to compile a program, say foo.cpp, into one of three accuracy levels. To do this, our Makefiles has a variable LEV that is set to 1, 2 or 3, to choose the level. To compile foo.cpp at level 2, say, we want to create the file foo_level2.o and foo_level2. The following rules accomplishes this:

    	%_level${LEV}.o: %.cpp
    		${CXX} -c -DCORE_LEVEL=${LEV} $(CXXFLAGS) $(CORE_INC) $< -o $@
    
    	%_level${LEV}: %_level${LEV}.o
    		${CXX} $(LDFLAGS) $? -lcorex++$(VAR)_level${LEV} $(CORE_LIB) -o $@
    	
    However, the above rules will not work unless LEV is a standard variable! Therefore, we need to declare LEV using, say, LEV:=1.
  5. HINT: Command Line Arguments    [back to top]    [back to hints]

    The user can override the values of variables at the command line. THIS IS A VERY USEFUL FEATURE.

    E.g., continuing with the previous Makefile, to compile "myProg.cpp", we just type "make".

    To compile "herProg.cpp", we could use an editor to modify the Makefile to assign "p=herProg".

    But instead of this, you can simply type

    		> make p=herProg  
    	

    It is relatively easy to write programs that process command line arguments in which the arguments are "positional" i.e., the use of an argument depends on its position in the list of arguments. Even if all these arguments are optional, positional arguments is awkward to use because you cannot provide the 4-th argument without also specifying the first three arguments. There are libraries that allow you to write "position-free" arguments (you need to associate an argument-name with each argument). But using Make, you can easily convert any program with positional arguments into position-free program. Even the program has no optional arguments, you can automatically provide optional arguments! E.g., suppose you have the program

     
    		> plot [curve="Y=X^2"] [xmin=-1] [xmax=1] [ymin=-1] [ymax=1]
    	
    where all arguments have default values. You can convert it into a position-free program by creating a "makefile" target called "myPlot":
    		curve="Y-X^2"
    		xmin=-1
    		xmax=1
    		ymin=-1
    		ymax=1
    
    		myPlot:
    			plot ${curve} ${xmin} ${xmax} ${ymin} ${ymax}
    	
    Then you can invoke this by calling, e.g., "make myPlot ymin=-2".
  6. HINT: Platform Customization    [back to top]    [back to hints]

    Suppose you have a Makefile that sometimes is used in the Cygwin environment and sometimes used in a Unix environment. It would be nice to set a switch so that it will work equally in either environment.

    For instance, the default output file of the C compiler in Unix is called "a.out", but in Cygwin, it is called "a.exe". We shall illustrate this simple customization:

    		####################################  VARIABLES
    		# Introduce a platform variable "pf"
    		####################################  
    		ifndef pf
    			pf=unix
    			pf=cyg
    		endif
    
    		####################################  
    		# Platform dependent customization
    		####################################  
    		ifeq (${pf},cyg)
    			suffix=exe
    		else
    		ifeq (${pf},unix)
    			suffix=
    		endif
    		endif
    
    		####################################  TARGETS
    		p=homework1
    		p=homework2
    
    		default:
    		---> gcc ${p}.c
    		---> a.${suffix}
    	
    Now, you can run this Makefile in Unix environment by typing
    		> make pf=unix
    	
    In other words, if you assign the value of a variable in the command line, it will override any assignment made within the Make file.

    Hint: If you had set up your .bashrc file so that the variable "pf" automatically has the correct value, then this Makefile will use the predefined value! That is why, in our Makefile above, we use the test "ifndef" (if-not-defined).

  7. HINT: Using For-Loops    [back to top]    [back to hints]

    Example

    		####################################  VARIABLE
    		MAKE=make
    
    		progs=myFirstProgram \
    			mySecondProgram \
    			myThirdProgram \
    			myFourthProgram \
    			myLastProgram 
    
    		####################################  TARGETS
    		clean: 
    		---> @for p in ${progs}; do \
    		    	if test -d "$$p"; then echo $$p;\
    		    		${MAKE} -C $$p clean;\
    		    		echo ""; fi; \
            	     done
    		####################################  
    	
    EXPLANATION: Note that we break very long command lines in the Makefile with the usual backslash "\".

    For each program, we test if a directory by that name exists. If so, we echo that name, and then we chdir to that subdirectory (using the -C flag). In that subdirectory, we do a "make clean" which presumably will remove temporary files, etc.

    The "$$p" indicates a local variable in the loop.

    We use "@for" instead of "for" to make the execution of the "for" command a silent one. Otherwise, make will "echo" the command at each iteration.

  8. HINT: Using include files    [back to top]    [back to hints]

    You can include a file called "foo" into your make file using the command:

    		-include foo
    	
    The "-" in front of "include" is useful to avoid error messages in case "foo" does not exist. WHY is this "-" useful? Very often, you have a standard set of inclusions in a Makefile (which will be used in various situations, perhaps through other includes!). These missing include files are not meant to be errors, only that you do not need them in this instance.

    Example of usage. I like all my include files to have names of the form "make.*" (e.g., make.preclude, make.include, make.postclude). Here "preclude" does not have your usual meaning, but it means the "first" inclusion file! I have a fixed set of tasks for each chapter of my book, and each chapter is in their own directory. So, each chapter has its own "Makefile", but generally, they all include the "make.include" from its parent directory. But within each chapter, I can (if desired) have local versions of "make.preclude" and "make.postclude".


FAQs

  1. Q: Why am I getting the missing separator error?

    ANS: This is a most common error when trying to execute your own Makefile. The message typically says

    	          Makefile:14: *** missing separator.   Stop.
    	
    This means that line 14 did not begin with a TAB. Recall in our "First Step" above, we said that each action line must begin with a TAB. A tab character is indicated by "--->". The solution is to change the first 8 spaces into a tab in line 14.

    This error in a Makefile usually happens when you use cut-and-paste. The editor might substitute tabs with 8 spaces, so that, visually, you THINK that the editor has faithfully copied the text.

  2. Q: Why am I getting the following mysterious error message when executing a compiled C program through make?
    		>make t
    		gcc hw.c
    		a.exe
    		make: *** [exe] Error 1
    	
    My make file has these lines:
    		t:
    			gcc hw.c	# this creates the file a.exe on CYGWIN (or a.out on unix)
    			a.exe
    	
    The program hw.c is executing correctly -- in fact, when I execute "a.exe" directly from the command line, I did not get any error message. So why does "make" think there is an error?

    ANS: Here is what we have encountered in such a case: the main program in "hw.c" did not return a value, or returned a non-zero.

    By convention, the main subroutine in a C program must return an integer value. Only the zero return value is regarded as normal return. Otherwise, you get an error return. Makefile is just reporting the error according to this convention.

  3. Q: How do I get a target to execute silently?

    ANS: Normally, when you invoke "make foo", it will first print each action associated with target "foo", and then execute the action. This is annoying when the action is simply "echo ...".

    One solution is to invoke make with the "-s" option: e.g., "make foo -s". But is there another way of ensuring this behavior without having to specify "-s" option each time? Here is the trick: in your Makefile, you write this

    		# Makefile
    		...
    		fooey:
    			action 1
    			action 2
    			...
    
    		foo:  
    			make fooey -s
    	
    
    


Please send comments to yap (at) cs (dot) nyu (dot) edu.