Make File Tutorial
Make File Tutorial
Contents:
Chapter Introduction 1 The most basic of Makefiles 2 Syntax so far 3 Making Makefiles Modular 4 Multi-file projects 5 Some more tidbits Conclusion Page 2 3 5 7 11 13 16
Introduction:
The purpose of this tutorial is to introduce new students to the wonders and terrors of Makefiles. When I was first introduced to these things, I didn't have a clue what was going on. I'd have to look over my friend's shoulder and copy his Makefile or copy and paste an old Makefile and hope I could just tweak it to run with my latest project. Even in 4th year, I had trouble with these things and I was always embarrassed to ask for help seeing as it is something I should have learned early on in my career as a student. My hope is that this tutorial will keep other students from suffering my fate. The idea behind having a Makefile is that you can save yourself a lot of typing of compiler commands. Rather than typing some crazy long string of flags and files, just type 'make' or 'make projectName' and away it goes.
4 <--- in the file all: project1 project2 ... <--- end of file If you add this line to the very top of your Makefile, now when you type: > make it will run your 'all:' target, which points to both of your 'project_' targets. Of course, you could add the 'all:' target anywhere in the file and you could compile all of your targets by typing: > make all but having it as the first target makes a certain amount of sense. Another option is to create a 'default:' target at the top of the file which you could leave empty if you wanted the default action to do nothing. <--- in the file default: ... <--- end of file In this case: > make would do nothing.
6 Assuming 'myfile1.c' includes both 'extra.h' and 'myfile2.c', the above lines will do the same thing as the previous incarnation but gives you more information about the structure of the project. Comments can be added to a Makefile by using the '#' character. For example: <--- in the file # this is a comment <--- end of file
8 Notice how the macro is declared and then called. To declare a macro, simple write 'macroname=value'. Most people seem to leave out white space, but it will be just as correct to leave spaces in (ie, 'macroname = value'). Here's another example: <--- in the file CC=gcc # use the macro to name a target EXE=project1 # then use that macro to add the target to a list of # targets TARGETS=$(EXE) project2 # let's use the macro to declare the target now $(EXE): myfile1.c $(CC) -o myfile1.c ... # let's use our TARGETS macro to list all of the possible # executable files we may have created. # this will delete 'project1' and 'project2' clean: rm -f *.o $(TARGETS) <--- end of file
9 Another example of a macro would be to create one called 'CFLAGS', or some such, and use it to define compiler flags you call quite often. For instance, your executables may always need the '-Wall' flag (this just changes setting relating to warning messages): <--- in the file CC=gcc CFLAGS=-Wall project1: myfile1.c $(CC) $(CFLAGS) project1 myfile1.c <--- end of file On to the next thing. There are a number of built in macros which can simplify the copy-and-paste method of making a Makefile: $@ -> this will copy the current target name. $< -> this will copy the FIRST file name in the dependency list. $^ -> this will copy ALL of the file names in the dependency list. There are also other built-in macros, but these are the three I actually find useful at this level. Here's an example: <--- in the file CC=gcc # example 1: project1: myfile1.c extra.h $(CC) -o project1 myfile1.c # same as project1: myfile1.c extra.h $(CC) -o $@ $^ # or project1: myfile1.c extra.h $(CC) -o $@ $<
10 # example 2: # this will not work. It will give a whole bunch of errors # since it tries to compile both files and myfile1.c already # includes myfile2.c. We effectively get a redefinition of # everything in myfile2.c. Note: it is possible to write # your code so this error won't actually happen and this # line will work. project1: myfile1.c myfile2.c $(CC) -o $@ $^ # this will work. It only takes the first file and compiles # it, linking in the other file automatically as appropriate. project1: myfile1.c myfile2.c $(CC) -o $@ $< <--- end of file
11
12 program, but you can also see where one section of code is dependant on another. If you wanted, you could remove the 'queue.h' from 'main.o' and 'list.h' from 'queue.o' and it would make no difference; however it would not be as clear the link between each code set. So what exactly is going on? How does this work? What are those '.o' files? Basically, we've split the program up into a bunch of pieces called object files (the '.o' files). These files make up the entirety of the program but they don't actually do anything on their own because they are not complete programs. They depend on the other files to work. So now what we have to do is link them together. That's what happens when we make the 'project' target. When you make the 'project' it runs make on each of the '.o' file targets. If those '.o' files don't exist or are not up-to-date, they are recompiled and then linked together as a final step.
13
14 $(ODIR)/list.o: $(HDIR)/list.h $(SDIR)/list.c $(CC) -c $^ <--- end of file Condensing your targets You may find all those '.o' targets really annoying. We can get rid of them, but we'll lose a lot of readability. You can do this using the '%' character as below: <--- in the file project: main.o queue.o list.o $(CC) -o $@ $^ %.o: %.c $(CC) -c $^ <--- end of file What this does is say that every .o file is compiled from a .c file of the same name. The COMPILEOBJ macro I thought this little macro would be handy for replacing all of those nasty strings of characters you get with every object target. Mind you, this isn't really as useful if you're using the previous tid-bit; however, it does still move all of the things you're likely to change to the top of the file. This is a good technique since you don't want to go digging through the file every time you make a change. You can make a COMPILEEXE macro in the same manner: <--- in the file CC=gcc COMPILEOBJ=$(CC) -c $^ CREATEEXE=$(CC) -o $@ $^ project: main.o queue.o list.o $(CREATEEXE) %.o: %.c $(CREATEOBJ) <--- end of file
15 The OBJS macro This one is probably pretty obvious, but I'll mention it anyway. Rather than writing all of the object files directly in the main project's dependency list, build another macro at the top of the file: <--- in the file CC=gcc COMPILEOBJ=$(CC) -c $^ CREATEEXE=$(CC) -o $@ $^ OBJS=main.o queue.o list.o project: $(OBJS) $(CREATEEXE) %.o: %.c $(CREATEOBJ) <--- end of file LFLAGS vs CFLAGS We've seen the CFLAGS macro used before to define the flags used in relation to the compiler. But if you've noticed, we use different flags to compile the object files than we do to link them together. You may also want to consider a DEBUG macro that contains flags relating to any debugging you are doing: <--- in the file CC=gcc DEBUG=-g # as an example. You can look up what this does CFLAGS=-Wall -c $(DEBUG) LFLAGS=-Wall $(DEBUG) -o # the -o doesn't actually do # anything # other than rename the executable # and could be left out of LFLAGS but # I've added it in to illustrate my # point. COMPILEOBJ=$(CC) $(CFLAGS) $^ CREATEEXE=$(CC) $(LFLAGS) $@ $^ OBJS=main.o queue.o list.o project: $(OBJS) $(CREATEEXE) %.o: %.c $(CREATEOBJ) <--- end of file
16
Conclusion:
So by now you should have a fair understanding of how a Makefile works. I've tried to simplify things and give lots of examples while still giving you a lot to work with. I hope I've succeeded, but if I haven't then below are a few websites which I used as references. Also, a websearch for "Makefile Tutorial" will turn up lots of helpful information. 1. http://palantir.swarthmore.edu/maxwell/classes/tutorials/maketutor/ 2. http://www.hsrl.rutgers.edu/ug/make_help.html 3. http://www.cs.umd.edu/class/spring2002/cmsc214/Tutorial/makefile.html