Introduction to make

advertisement
Introduction to make
The UNIX make utility facilitates the creation and maintenance of executable programs
from source code. This tutorial will introduce the simple usage of the make utility with the
goal of building an executable program from a series of source code files.
•
•
•
•
•
Introduction
A project
Simple make
A bit more
Library example
Introduction
The UNIX make utility facilitates the creation and maintenance of executable programs
from source code. make keeps track of the commands needed to build the code and
when changes are made to a source file, recompiles only the necessary files. make
creates and updates programs with a minimum of effort.
A small initial investment of time is needed to set up make for a given software project,
but afterward, recompiling and linking is done consistently and quickly by typing one
command: make, instead of issuing many complicated command lines that invoke the
compiler and linker.
This tutorial will introduce the simple usage of the make utility with the goal of building an
executable program from a series of source code files. Most of the varied, subtle, and
complex features of make are the subject of entire books and are not covered here.
This tutorial assumes that you have some familiarity with UNIX, text editors and
compiling programs from source code.
A project
We'll illustrate the use and usefulness of make with a little project. This project does
nothing more than print out the current version of the program.
This project has three source code files
project.f
The main program, which calls a subroutine, print_project_info
print_project_info.f
Contains the subroutine print_project_info
project_info.f
Contains parameter definitions in a Fortran 90 module that are used by the
subroutine print_project_info
!
FILENAME: project.f
!
PROGRAM project
IMPLICIT NONE
CALL print_project_info
STOP
END PROGRAM project
!
FILENAME: print_project_info.f
!
SUBROUTINE print_project_info
USE project_info
IMPLICIT NONE
PRINT *,"This is version",MAJOR_VERSION,".",MINOR_VERSION
END SUBROUTINE print_project_info
!
FILENAME: project_info.f
!
MODULE project_info
IMPLICIT NONE
SAVE
INTEGER, PARAMETER:: MAJOR_VERSION=0, MINOR_VERSION=2
END MODULE project_info
Simple make
We'll use make in its most simple forms to produce an executable program from these
source code files.
Simple compile
In order to create an executable program, all these source code files need to be
compiled and linked. This command line will do it:
% xlf90 -o project project_info.f print_project_info.f project.f
That's a lot of typing even for this simple program. make will save you from having to
remember and type the command line each time.
The make
Makefile
utility gets its instructions from a text file, by default named makefile or
in the current directory.
Here is possibly the simplest and clearest makefile that will perform the above compile
and link:
#
# A first makefile
project : project_info.f print_project_info.f project.f
xlf90 -o project project_info.f print_project_info.f project.f
The pound character, #, in a makefile indicates a comment line. A backslash character
indicates that the line is continued on the next line. If we save these lines in a file named
makefile, typing make at the command line will compile the program just as if we'd typed
in the entire f90 command:
% make
project_info.f:
** project_info
=== End of Compilation 1 ===
1501-510 Compilation successful for file project_info.f.
print_project_info.f:
** print_project_info
=== End of Compilation 1 ===
1501-510 Compilation successful for file print_project_info.f.
project.f:
** project
=== End of Compilation 1 ===
1501-510 Compilation successful for file project.f.
In addition, if we don't change any files and type make again:
% make
Target "project" is up to date.
knows that the file does not need to be recompiled. However, if we change one of
the source code files, make will recompile the program.
make
How does it know how to do this? Let's look at the makefile. The first line,
project : project_info.f print_project_info.f project.f
contains the program name, project, to the left of a colon. The word or label to the left
of the colon is called the target. The files that appear to the right of the colon are called
dependencies. The dependencies are those files that are needed to produce the target.
So this line is telling make that all the source code files are needed to produce the
executable program.
make checks the dependency list and if any of the files have been changed more
recently than the target, make executes the next line in the makefile:
xlf90 -o project project_info.f print_project_info.f project.f
If the target is more recent than the dependency, make will tell you so and skip the
command.
NOTE: Although you can't see it, there is a TAB character before the xlf90 command.
This TAB must be present for make to work. All the blank spaces in the world will not help
if there is no TAB present and make will give you little clue as to why it is failing.
Better compile
In the previous example, every source code file was recompiled every time a new
executable was produced. This is not necessary in general, and it could waste a lot of
time. If we can understand the steps xlf90 takes to build the program, we can instruct
make to work in the most efficient manner.
The xlf90 compiling system works in two stages; it:
1. compiles each .f file into an object file ending with a .o extension
2. links all the .o files and system library files into the executable program
All that is needed to produce the final program are .o files. The .f files are needed to
create .o files. (A possible subtlety regarding xlf90: if a .f file USEs a module contained
in a different .f file, it reads the necessary information from the module's .mod file,
which must exist.)
Here are the dependencies for this program:
This file's creation
project_info.o
depends on these files
project_info.f
print_project_info.o project_info.o print_project_info.f
project.o
project.f
project
project.o print_project_info.o
Here's the makefile that incorporates all these dependencies:
project : project.o print_project_info.o
xlf90 -o project project.o print_project_info.o
project_info.o : project_info.f
xlf90 -c project_info.f
print_project_info.o : print_project_info.f project_info.o
xlf90 -c project_info.o print_project_info.f
project.o : project.f
xlf90 -c project.f
The xlf90 -c command tells the compiler to produce the corresponding .o file without
trying to link it into an executable.
Now let's make the program:
%
make
xlf90 -c project.f
** project
=== End of Compilation 1 ===
1501-510 Compilation successful for file project.f.
xlf90 -c project_info.f
** project_info
=== End of Compilation 1 ===
1501-510 Compilation successful for file project_info.f.
xlf90 -c project_info.o print_project_info.f
** print_project_info
=== End of Compilation 1 ===
1501-510 Compilation successful for file print_project_info.f.
xlf90 -o project project.o print_project_info.o
Typing make again produces:
% make
Target "project" is up to date.
Running the program on the NERSC SP yields
%
./project
This is PROJECT version 0 . 2
Now let's change the version to 0.3. After we have edited the file project_info.f, we
rebuild the program:
% make
xlf90 -c project_info.f
** project_info
=== End of Compilation 1 ===
1501-510 Compilation successful for file project_info.f.
xlf90 -c project_info.o print_project_info.f
** print_project_info
=== End of Compilation 1 ===
1501-510 Compilation successful for file print_project_info.f.
xlf90 -o project project.o print_project_info.o
% ./project
This is PROJECT version 0 . 3
Note that make only executed the steps that were necessary to build the program. In
particular, project.f was not recompiled. print_project_info.o was recreated
because it contains information that is defined in project_info.f.
A bit more
The material presented so far only scratches the surface of the features of the make
utility. make has many default and implicit rules for how to build objects from source
code. These additional features and rules make make very powerful. However, the rules
are often not obvious and can vary from platform to platform, which can make make
frustrating and difficult to use. Books devoted to make are available for those who are
interested in learning all the details.
In this section, we'll discuss a few additional features of make. All we will do is slightly
rework the previous makefile.
Here's a makefile that maintains the same program we've been discussing. The
modifications are described below.
FC = xlf90
FCOPTS = -O3
LD = xlff90
LDOPTS =
EXENAME = project
OBJS = project.o print_project_info.o
$(EXENAME) : $(OBJS)
$(LD) $(LDOPTS) -o $(EXENAME) $(OBJS)
print_project_info.o : print_project_info.f project_info.o
$(FC) $(FCOPTS) -c project_info.o print_project_info.f
.f.o :
$(FC) $(FCOPTS) -c $<
clean:
rm -f core *.o
clobber: clean
rm -f $(EXENAME)
Macros
You can define macros, or symbols that stand for other things, in makefiles. The lines
at the beginning of the makefile are macros. For example FC = xlf90 is a macro.
Everywhere that $(FC) appears in the makefile, it will be replaced with xlf90.
These macro definitions are an easy way to change items that appear in many places in
the makefile. Notice that we defined the macro FCOPTS = -O3 and placed it in all the
xlf90 command options. The -O3 option to xlf90 specifies an optimization level. If we to
change the optimization level for the entire code, we just change the one line in which
FCOPTS is defined.
Implicit suffix rules
You can give make a set of rules for creating files with a certain suffix from files with the
same root file name, but a different suffix. For example, the line
.f.o :
tells make that all .o files are created from the corresponding .f files. The command in
the next line will recompile any .f file if it is newer than the corresponding .o file.
$(FC) $(FCOPTS) -c $<
This line uses the make internal macro $<, which translates to "any dependency that is
more recent than its corresponding target." This internal macro can only be used in
suffix rules.
Exceptions to the suffix rule can be stated explicitly, as is done here for the
print_project_info.o object, which needs the project_info.o module information.
Additional targets
We've added two useful targets in this makefile: clean and clobber. If you type make at
the command line, make makes the first target it encounters in the makefile, in this case
the executable program. If you type make with an argument, make jumps to the target
with the name of the command line argument.
For example, make clean jumps to the clean target. The two new targets perform these
tasks
clean
Since the file "clean" does not exist, make clean will always execute the
command rm -f *.o, which deletes the .o files. This is useful if you want to get
rid of all those files after you're finished building your program.
clobber
make clobber
will get rid of the executable file as well as the .o files. The file
clobber also does not exist so make clobber will always cause the executable to
be removed, and since it depends on clean, it will also imply make clean.
Example: making a library archive
In this example a number of subroutines are incorporated info a library archive.
Routines from the library are linked into the main program during the linking process.
It is often convenient to maintain object files in a library. By doing so, you can reduce
the number of .o files you need to keep in a directory, which can have many
advantages.
The example project
Let's say we have four routines that we want to maintain in a library and a main program
(libex.f90) that will call those routines. In this example, the routines simply perform an
operation on two real numbers and print the result. We'll make a library file, named
operators.a, that will contain the subroutines. After we create the library file, we will
delete the object files (*.o).
Here's what the files look like:
% ls
add.f90
libex.f90
divide.f90
makefile
! FILENAME: add.f
multiply.f90
subtract.f90
SUBROUTINE add(x,y)
IMPLICIT NONE
REAL, INTENT(IN) :: x, y
PRINT *, "The sum of", x, " and ", y, "is ", x+y
RETURN
END SUBROUTINE add
!FILENAME: divide.f
SUBROUTINE divide(x,y)
IMPLICIT NONE
REAL, INTENT(IN) :: x, y
IF(y==0.) THEN
PRINT *, "Can not divide by zero!"
RETURN
END IF
PRINT *, x, "divided by", y, "is ", x/y
RETURN
END SUBROUTINE divide
!FILENAME: multiply.f
SUBROUTINE multiply(x,y)
IMPLICIT NONE
REAL, INTENT(IN) :: x, y
PRINT *, "The product of", x, " and ", y, "is ", x*y
RETURN
END SUBROUTINE multiply
!FILENAME: subtract.f
SUBROUTINE subtract(x,y)
IMPLICIT NONE
REAL, INTENT(IN) :: x, y
PRINT *, "The difference, ", x, " - ", y, "is ", x-y
RETURN
END SUBROUTINE subtract
!FILENAME: libex.f
! An example program that illustrates calling routines
! from a user-built library of object modules.
PROGRAM libex
IMPLICIT NONE
REAL:: a=2.0,b=3.0
CALL
CALL
CALL
CALL
add(a,b)
multiply(a,b)
subtract(a,b)
divide(a,b)
END PROGRAM libex
The makefile
The following simple makefile can be used to maintain the archive and produce the
executable. make has a special syntax for dealing with libraries. The library is defined on
the line
$(LIB) : $(LIB)(add.o) $(LIB)(multiply.o) \
$(LIB)(divide.o) $(LIB)(subtract.o)
The the rule for producing the library is the following (note that the .o files are removed
immediately after updating the library).
.f90.a :
$(CF) -c $<
$(AR) -r $@ $*.o
/bin/rm -f $*.o
This rule says
1. If the .f file has been modified since the .a file was modified, compile the .f file
using $(CF) -c.
2. Take the corresponding .o file and replace it in the proper library (as defined
above) using $(AR) (The ar command builds and maintains libraries, see man
ar).
3. Remove the .o file.
CF = xlf90
LD = xlf90
LIBOBJS = add.o multiply.o divide.o subtract.o
LIB = operators.a
EXE = libex
OBJS = libex.o
AR = ar
$(EXE): $(OBJS) $(LIB)
$(CF) -o $(EXE) $(OBJS) $(LIB)
$(LIB) : $(LIB)(add.o) $(LIB)(multiply.o) \
$(LIB)(divide.o) $(LIB)(subtract.o)
.f.a :
$(CF) -c $<
$(AR) -r $@ $*.o
/bin/rm -f $*.o
.f.o :
$(CF) -c $<
clean :
/bin/rm
$(OBJS)
clobber :
/bin/rm $(EXE) $(LIB) $(OBJS)
The Output
% make
xlf90 -c libex.f
=== End of Compilation 1 ===
Compilation successful for file libex.f.
xlf90 -c add.f
** add
=== End of Compilation 1 ===
1501-510 Compilation successful for file add.f.
ar -r operators.a add.o
ar: Creating an archive file operators.a.
/bin/rm -f add.o
xlf90 -c multiply.f
** multiply
=== End of Compilation 1 ===
1501-510 Compilation successful for file multiply.f.
ar -r operators.a multiply.o
/bin/rm -f multiply.o
xlf90 -c divide.f
** divide
=== End of Compilation 1 ===
1501-510 Compilation successful for file divide.f.
ar -r operators.a divide.o
/bin/rm -f divide.o
xlf90 -c subtract.f
** subtract
=== End of Compilation 1 ===
1501-510 Compilation successful for file subtract.f.
ar -r operators.a subtract.o
/bin/rm -f subtract.o
xlf90 -o libex libex.o operators.a
** libex
1501-510
s00505% ./libex
The sum of 2.000000000 and 3.000000000 is 5.000000000
The product of 2.000000000 and 3.000000000 is 6.000000000
The difference, 2.000000000 - 3.000000000 is -1.000000000
2.000000000 divided by 3.000000000 is 0.6666666865
Download