make Tutorial This is excerpted from an article I wrote on Usenet, in the rec.games.roguelike.development newsgroup. I wrote and tested it under Debian GNU/Linux, but it was written *for* a DJGPP user. Nonetheless, I think it's a good all-purpose introduction to make; the same basic principles and commands should apply to other compilers and platforms with relatively little change. ========================================================================== First, note that you will need a utility called "make". This is typically GNU make, and should be available in binary format wherever you got DJGPP. Start small. Put together a two-module example program and a Makefile to guide the build process. Here's a brief make tutorial which works under Unix. I'm pretty sure DJGPP uses the same syntax for everything (the .o on object files), since Angband's Makefile.ibm uses .o's. So I think it will work as is under DJGPP, but corrections are welcome. (Of course, you don't have to put the silly ".exe" on the end of a program in Unix, but it doesn't hurt.) ------------- foo.c -------------------------- #include "bar.h" int main (void) { bar(); return 0; } ---------- end foo.c ------------------------- ------------- bar.h -------------------------- void bar (void); ---------- end bar.h ------------------------- ------------- bar.c -------------------------- #include #include "bar.h" void bar (void) { printf ("hello, world\n"); } ---------- end bar.c ------------------------- ------------- Makefile ----------------------- CC = gcc CFLAGS = -g LDFLAGS = LIBS = OBJS = foo.o bar.o hello.exe : $(OBJS) $(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) foo.o : bar.h bar.o : bar.h ---------- end Makefile ---------------------- There is one *important* thing you must know first: the blank space at the beginning of the indented line of the Makefile is a tab character, not a bunch of spaces. If your editor silently converts tabs to spaces, look for a different editor. OK, with all these files in one directory you're ready to build your program (hello.exe). You can do this by typing the command "make". If instead you'd rather see what make *would* do if you were to type "make", you can type the command "make -n". make will then print out the commands: gcc -g -c foo.c -o foo.o gcc -g -c bar.c -o bar.o gcc -o hello.exe foo.o bar.o (These are also the commands you will see if you type "make". When you type "make", make actually runs these commands in addition to printing them.) Now, here's how it works. A Makefile defines rules which describe the relationships between *targets* and *dependencies*. This line: foo.o : bar.h tells make that the file foo.o is a *target* which *depends* on the file bar.h. What this means is that, if foo.o exists but is older than bar.h, then foo.o is out of date and must be rebuilt. If we had a tab-indented list of commands below the dependency line, they would tell make how to rebuild foo.o. But we don't; therefore, make uses its default rules to figure out how to perform this build, should it become necessary. The first dependency line (usually, the first line with a colon) defines the *default target*. When you type "make" with no arguments, it attempts to build the default target. If you want to build some other target, you can specify the target name as an argument to make; for example, if you just wanted to build bar.o for some reason, you could type "make bar.o". In our case the default target is called "hello.exe". This means that when you just type "make" it will try to build the file "hello.exe". We've specified the following dependency rule for this file: hello.exe : $(OBJS) $(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) Now, in case you hadn't already guessed, the "$(NAME)" thing is a variable substitution. $(OBJS) is a variable which we define above to mean the list of files "foo.o bar.o". Since we're referring to this list of files multiple times, it makes sense to use a variable to represent them. So, this rule tells us that hello.exe depends on the files foo.o and bar.o, and that in order to build hello.exe (once we've built all the dependencies) we must execute the command beginning with $(CC). (The $@ is a special variable which make handles internally. It stands for the target of the current rule -- in this case, hello.exe.) Now, if you have only the source code files in your working directory, you won't have the files foo.o and bar.o which hello.exe depends on. So make searches the rest of the file to see if it can figure out how to build them, since it knows it will need them for hello.exe. We don't have any rules which tell make *how* to build foo.o and bar.o, but we *do* have files called foo.c and bar.c. make is pretty well-informed, and it knows that it can build a .o file from a .c file by running the command "$(CC) $(CFLAGS) -c file.c" for whatever file.c is being compiled. This is called an *internal rule*. make has lots of these internal rules, and you can define more or override the existing ones, but that's outside the scope of this tutorial. Notice that I used the variables $(CC) and $(CFLAGS) when I talked about make's internal rule for building foo.o from foo.c. These two variables have a special meaning for make when it uses its internal rule for .c->.o conversion. That's why I used them in the sample Makefile I wrote up above, rather than some other variables like $(C_COMPILER) or $(C_FLAGS). That's also why, when you type "make -n" in the absence of foo.o, you get (among other things) the command "gcc -g -c foo.c -o foo.o". make is using the $(CC) and $(CFLAGS) settings we put in the Makefile. (If you change $(CFLAGS) to "-g -Wall", or something else, make will use that instead when it compiles .c files. You can also use a different C compiler by changing the value of $(CC).) Now, suppose that after building hello.exe, we make a change to our project. For example, suppose that the function bar() is changed so that it returns an int (to provide a value for error checking). So, we edit the files bar.h and bar.c, and change them: ------------- new bar.h -------------------------- int bar (void); ---------- end new bar.h ------------------------- ------------- bar.c v2 -------------------------- #include #include "bar.h" int bar (void) { printf ("hello, world\n"); return 0; } ---------- end bar.c v2 ------------------------- Yes, it's still a silly example. :-) Now, after making this change, we might have the following files in our working directory: phoenix:~/tmp/1$ ls -l total 18 -rw-rw-r-- 1 greg greg 129 Jul 30 19:41 Makefile -rw-rw-r-- 1 greg greg 100 Jul 30 20:07 bar.c -rw-rw-r-- 1 greg greg 17 Jul 30 20:07 bar.h -rw-rw-r-- 1 greg greg 4064 Jul 30 20:06 bar.o -rw-rw-r-- 1 greg greg 72 Jul 30 19:41 foo.c -rw-rw-r-- 1 greg greg 1996 Jul 30 20:06 foo.o -rwxrwxr-x 1 greg greg 7722 Jul 30 20:06 hello.exe Now suppose we type "make -n" again. What does make need to do? Well, the default target is hello.exe, which depends on foo.o and bar.o. hello.exe is newer than foo.o and bar.o (by less than a minute; you can't see it from ls -l unfortunately). However, as we continue reading the Makefile, we see that foo.o depends on bar.h -- but bar.h is newer than foo.o! So we must rebuild foo.o. Also, bar.o depends on bar.h and bar.c. (It depends on bar.h because we said so in the Makefile; it depends on bar.c because of make's internal rules.) Both bar.h and bar.c are newer than bar.o, so we must also rebuild bar.o. Therefore, "make -n" prints the following commands: gcc -g -c foo.c -o foo.o gcc -g -c bar.c -o bar.o This is proof that make (in this case, GNU make 3.76.1) is not terribly smart, since it will also have to rebuild hello.exe once it's rebuilt foo.o or bar.o. In fact, if we now type "make" we get the following commands: gcc -g -c foo.c -o foo.o gcc -g -c bar.c -o bar.o gcc -o hello.exe foo.o bar.o Now, in this case, we had to rebuild the whole project, because we modified a header file that every object file depends on. This is not always the case (although it can be quite common -- consider a modification to Angband's "angband.h" file, for example, which is #include'd by all the .c files). Given a properly written Makefile, make is intelligent enough to rebuild *only* the files that need to rebuild, and no others. To demonstrate this, suppose we change the file bar.c (to correct the grammar in the message): ------------- bar.c v3 -------------------------- #include #include "bar.h" int bar (void) { printf ("Hello, world!\n"); return 0; } ---------- end bar.c v3 ------------------------- This time, when we type "make", we get the commands: gcc -g -c bar.c -o bar.o gcc -o hello.exe foo.o bar.o Notice that this time make only rebuilt bar.o and hello.exe; it did not rebuild foo.o, because we didn't modify any of the files that foo.o depends on (namely, foo.c and foo.h). This should give you enough information to get started using make with multiple-module projects. There's obviously a *lot* of ground that this tutorial doesn't cover, but that's why we have documentation....