Automating Program Compilation - Writing Makefiles.pdf

Embed Size (px)

Citation preview

  • 7/27/2019 Automating Program Compilation - Writing Makefiles.pdf

    1/6

    [LUPG Home] [Tutorials] [Related Material] [Essays] [Project Ideas] [Send Comments]

    v1.0.1

    Automating Program Compilation - Writing

    Makefiles

    Table Of Contents:

    1. Preface2. The Structure Of A Makefile

    3. Order Of Compilation4. Starting Small - A Single-Source Makefile Example5. Getting Bigger - A Multi-Source Makefile Example

    6. Using Compiler And Linker Flags7. A Rule For Everyone - Using "File Type" Rules

    8. Automatic Creation Of Dependencies

    Preface

    Compiling a program made of one source file is easy. Compiling one made of few sources isslightly annoying, but may be automated via a simple shell script. Anything larger than that wouldstart to get on your nerves. This is where makefiles are helpful.

    A makefile is a collection of instructions that should be used to compile your program. Once youmodify some source files, and type the command "make" (or "gmake" if using GNU's make), your

    program will be recompiled using as few compilation commands as possible. Only the files you

    modified and those dependent upon them will be recompiled. Of-course, this is not done via usageof magic. You need to supply the rules for compiling various files and file types, and the list of

    dependencies between files (if file "A" was changed, then files "B", "C" and "D" also need to bere-compiled), but that only has to be done once.

    The Structure Of A Makefile

    A typical makefile contains lines of the following types:

    Variable Definitions - these lines define values for variables. For example:

    CFLAGS = -g -Wall

    SRCS = main.c file1.c file2.c

    CC = gcc

    Page 1 of 6Automating Program Compilation - Writing Makefiles

    10/26/2013http://users.actcom.co.il/~choo/lupg/tutorials/writing-makefiles/writing-makefiles.html

  • 7/27/2019 Automating Program Compilation - Writing Makefiles.pdf

    2/6

    Dependency Rules - these lines define under what conditions a given file (or a type of file)

    needs to be re-compiled, and how to compile it. For example:

    main.o: main.c

    gcc -g -Wall -c main.c

    This rule means that "main.o" has to be recompiled whenever "main.c" is modified. The rule

    continues to tell us that in order to compile "main.o", the command "gcc -g -Wall -cmain.c" needs to be executed.

    Note that each line in the commands list must begin with a TAB character. "make" is quitepicky about the makefile's syntax.

    Comments - Any line beginning with a "#" sign, or any line that contains only white-space.

    Order Of Compilation

    When a makefile is executed, we tell the make command to compile a specific target. A target isjust some name that appears at the beginning of a rule. It can be a name of a file to create, or just aname that is used as a starting point (such a target is also called a "phony" target).

    When make Is invoked, it first evaluates all variable assignments, from top to bottom, and when itencounters a rule "A" whose target matches the given target (or the first rule, if no target was

    supplied), it tries to evaluate this rule. First, it tries to recursively handle the dependencies thatappear in the rule. If a given dependency has no matching rule, but there is a file in the disk withthis name, the dependency is assumed to be up-to-date. After all the dependencies were checked,

    and any of them required handling, or refers to a file newer than the target, the command list forrule "A" is executed, one command at a time.

    This might appear a little complex, but the example in the next section will make things clear.

    Starting Small - A Single-Source Makefile Example

    Lets first see a simple example of a makefile that is used to compile a program made of a single

    source file:

    # top-level rule to create the program.

    all: main

    # compiling the source file.

    main.o: main.c

    gcc -g -Wall -c main.c

    # linking the program.

    main: main.o

    gcc -g main.o -o main

    # cleaning everything that can be automatically recreated with "make".

    clean:/bin/rm -f main main.o

    Few notes about this makefile:

    Page 2 of 6Automating Program Compilation - Writing Makefiles

    10/26/2013http://users.actcom.co.il/~choo/lupg/tutorials/writing-makefiles/writing-makefiles.html

  • 7/27/2019 Automating Program Compilation - Writing Makefiles.pdf

    3/6

    1. Not all rules have to be used in every invocation ofmake. The "clean" rule, for example, is

    not normally used when building the program, but it may be used to remove the object filescreated, to save disk space.

    2. A rule doesn't need to have any dependencies. This means if we tell make to handle its

    target, it will always execute its commands list, as in the "clean" rule above.3. A rule doesn't need to have any commands. For example, the "all" rule is just used to invoke

    other rules, but does not need any commands of its own. It is just convenient to make surethat if someone runs make without a target name, this rule will get executed, due to beingthe first rule encountered.

    4. We used the full path to the "rm" command, instead of just typing "rm", because many usershave this command aliased to something else (for example, "rm" aliased to "rm -i"). By

    using a full path, we avoid any aliases.

    Getting Bigger - A Multi-Source Makefile Example

    In anything but the simplest programs, we usually have more than one source file. This is where

    using a makefile starts to pay off. Making a change to one file requires re-compilation of only themodified file, and then re-linking the program. Here is an example of such a makefile:

    # top-level rule to compile the whole program.

    all: prog

    # program is made of several source files.

    prog: main.o file1.o file2.o

    gcc main.o file1.o file2.o -o prog

    # rule for file "main.o".

    main.o: main.c file1.h file2.hgcc -g -Wall -c main.c

    # rule for file "file1.o".

    file1.o: file1.c file1.h

    gcc -g -Wall -c file1.c

    # rule for file "file2.o".

    file2.o: file2.c file2.h

    gcc -g -Wall -c file2.c

    # rule for cleaning files generated during compilations.

    clean:

    /bin/rm -f prog main.o file1.o file2.o

    Few notes:

    There is one rule here for each source file. This causes some redundancy, but later on we'll

    see how to get rid of it. We add dependency on included files (file1.h, file2.h) where applicable. If one of these

    interface-definition files changes, the files that include it might need a re-compilation too.

    This is not always true, but it is better to make a redundant compilation, than to have objectfiles that are not synchronized with the source code.

    Using Compiler And Linker Flags

    Page 3 of 6Automating Program Compilation - Writing Makefiles

    10/26/2013http://users.actcom.co.il/~choo/lupg/tutorials/writing-makefiles/writing-makefiles.html

  • 7/27/2019 Automating Program Compilation - Writing Makefiles.pdf

    4/6

    As one could see, there are many repetitive patterns in the rules for our makefile. For example,

    what if we wanted to change the flags for compilation, to use optimization (-O), instead of adddebug info (-g)? we would need to go and change this flag for each rule. This might not look likemuch work with 3 source files, but it will be tedious when we have few tens of files, possibly

    spread over few directories.

    The solution to this problem is using variables to store various flags, and even command names.

    This is especially useful when trying to compile the same source code with different compilers, oron different platforms, where even a basic command such as "rm" might reside in a differentdirectory on each platform.

    Lets see the same makefile, but this time with the introduction of variables:

    # use "gcc" to compile source files.

    CC = gcc

    # the linker is also "gcc". It might be something else with other compilers.

    LD = gcc

    # Compiler flags go here.

    CFLAGS = -g -Wall

    # Linker flags go here. Currently there aren't any, but if we'll switch to

    # code optimization, we might add "-s" here to strip debug info and symbols.

    LDFLAGS =

    # use this command to erase files.

    RM = /bin/rm -f

    # list of generated object files.

    OBJS = main.o file1.o file2.o

    # program executable file name.

    PROG = prog

    # top-level rule, to compile everything.

    all: $(PROG)

    # rule to link the program

    $(PROG): $(OBJS)

    $(LD) $(LDFLAGS) $(OBJS) -o $(PROG)

    # rule for file "main.o".

    main.o: main.c file1.h file2.h

    $(CC) $(CFLAGS) -c main.c

    # rule for file "file1.o".

    file1.o: file1.c file1.h

    $(CC) $(CFLAGS) -c file1.c

    # rule for file "file2.o".file2.o: file2.c file2.h

    $(CC) $(CFLAGS) -c file2.c

    # rule for cleaning re-compilable files.

    clean:

    $(RM) $(PROG) $(OBJS)

    Few notes:

    We define many variables in this makefile. This will make it very easy to modify compile

    flags, compiler used, etc. It is good practice to define even things that might seem like

    they'll never change. In time - they will.

    Page 4 of 6Automating Program Compilation - Writing Makefiles

    10/26/2013http://users.actcom.co.il/~choo/lupg/tutorials/writing-makefiles/writing-makefiles.html

  • 7/27/2019 Automating Program Compilation - Writing Makefiles.pdf

    5/6

    We still have a problem with the fact that we define one rule for each source file. If we'll

    want to change this rule's format, it will be rather tedious. The next section will show ushow to avoid this problem.

    A Rule For Everyone - Using "File Type" Rules

    So, the next phase would be to eliminate the redundant rules, and try to use one rule for all sourcefiles. After all, they are all compiled in the same way. Here is a modified makefile:

    # we'll skip all the variable definitions, just take them from the previous

    # makefile

    .

    .

    # linking rule remains the same as before.

    $(PROG): $(OBJS)

    $(LD) $(LDFLAGS) $(OBJS) -o $(PROG)

    # now comes a meta-rule for compiling any "C" source file.

    %.o: %.c

    $(CC) $(CFLAGS) -c $