And of course there are a million versions of make with different options and functionalities. As you should know by now, I try not to use fancy options because they're almost never portable, so this will be a very basic make lecture.
The syntax of make is roughly:
make [ -f makefile ] [ targets ]If you omit the -f, then it will use ./makefile or ./Makefile. If it can't find these, some make's will then look for a SCCS makefile (and I'm not going to cover SCCS, preferring rcs), then for makefile in your home directory, and then a system makefile. So if you type make and you see it doing things that you don't understand, chances are that it's using a bizarre makefile.
I'm sure you've all seen makefiles in the context of C. We'll use that as an example. Go into the directory make1, and look at the makefile. Look at all the source files. This is a simple program that is broken up into three C source files (printname.c, first.c and last.c) and two header files (fn.h and ln.h). If you type make in the directory, you'll see it constructs printname.
UNIX> cd make1 UNIX> ls -l total 6 -rw-r--r-- 1 plank 58 Jul 7 10:19 first.c -rw-r--r-- 1 plank 21 Jul 7 10:26 fn.h -rw-r--r-- 1 plank 57 Jul 7 10:19 last.c -rw-r--r-- 1 plank 22 Jul 7 10:21 ln.h -rw-r--r-- 1 plank 352 Jul 7 10:21 makefile -rw-r--r-- 1 plank 42 Jul 7 10:20 printname.c UNIX> make cc -c printname.c cc -c first.c cc -c last.c cc -o printname printname.o first.o last.o UNIX> ls -l total 33 -rw-r--r-- 1 plank 58 Jul 7 10:19 first.c -rw-r--r-- 1 plank 236 Jul 7 10:28 first.o -rw-r--r-- 1 plank 21 Jul 7 10:26 fn.h -rw-r--r-- 1 plank 57 Jul 7 10:19 last.c -rw-r--r-- 1 plank 244 Jul 7 10:28 last.o -rw-r--r-- 1 plank 22 Jul 7 10:21 ln.h -rw-r--r-- 1 plank 352 Jul 7 10:21 makefile -rwxr-xr-x 1 plank 24576 Jul 7 10:28 printname -rw-r--r-- 1 plank 42 Jul 7 10:20 printname.c -rw-r--r-- 1 plank 168 Jul 7 10:28 printname.o UNIX> printname Jim Plank UNIX>Now, if you modify last.c (say to print out LAST twice), then type make again, it will only recompile what is necessary:
UNIX> ed last.c 57 ed: 1,$n 1 #include "ln.h" 2 3 printlast() 4 { 5 printf("%s\n", LAST); 6 } ed: 5t5 ed: 5s/\\n/ / ed: 5,6p printf("%s ", LAST); printf("%s\n", LAST); ed: w 80 ed: q UNIX> make cc -c last.c cc -o printname printname.o first.o last.o UNIX> printname Jim Plank Plank UNIX>If you modify just one header file, then again, make will only recompile what is necessary. This is extremely convenient and efficient, especially when you are dealing with long compilation times, and many files.
UNIX> ed fn.h 21 ed: s/Jim/Dikembe char *FIRST = "Dikembe"; ed: w 25 ed: q UNIX> make cc -c first.c cc -o printname printname.o first.o last.o UNIX> printname Dikembe Plank Plank UNIX>Notice that there is a target called clean. This is not a file that gets made, but a way to specify for make to clean up your directory:
UNIX> ed makefile 352 ed: /clean/,/clean/+1p clean: rm -f a.out core printname *.o ed: q UNIX> make clean rm -f a.out core printname *.o UNIX>
If any command that make executes exits with a non-zero value, then make will exit instantly.
name = stringAnd you use them with the $(name) construction.
You may use any shell environment variable like a normal variable in a makefile. Of course you can override the environment variable setting in the makefile if you want.
Look at the makefile in the make3 directory. This does two common things. First, it bundles up all the object files for the executable into a variable called OBJS. Second, it assumes that you have set the environment variable CC to point to your C compiler. Try it out:
UNIX> cd make3 UNIX> setenv CC cc UNIX> make cc -c printname.c cc -c first.c cc -c last.c cc -o printname printname.o first.o last.o UNIX> printname Jim Plank UNIX> make clean rm -f a.out core printname *.o UNIX> setenv CC gcc UNIX> make gcc -c printname.c gcc -c first.c gcc -c last.c gcc -o printname printname.o first.o last.o UNIX> printname Jim Plank UNIX>To get a dollar sign passed to the shell, precede it with a backslash.
.s1.s2: commandsThis says that you build a file with suffix .s2 out of a file with a suffix of .s1 using the commands. In the command you may use the variable $* to stand for the part of the name before the suffix.
For example, look at the makefile in the make4 directory. This is very typical of makefiles that you'll see for simple programs:
UNIX> cd make4 UNIX> make cc -O4 -c printname.c cc -O4 -c first.c cc -O4 -c last.c cc -O4 -o printname printname.o first.o last.o UNIX> printname Jim Plank UNIX>Note that it's easy to specify that first.o depends on fn.h while letting the default commands take care of making first.o.
UNIX> alias mk make -f /mahogany/homes/plank/makefile UNIX>The first useful thing in there is the line that makes an object file out of a c file:
.c: $(CC) -o $* -g -D$(ARCH) -I$(INCLUDE) $*.c $(LIBS) -lmIt assumes that the ARCH environment variable is set to reflect what machine I'm on (this was stolen from PVM's archtype command). Then it makes sure to get include files from my home include directory, plus object libraries from the correct lib directory. In this way, I can write a quick C program that uses anything from my fields, dlist, rbtree, socketfun and dataproc libraries (see CS360 lecture notes for descriptions of all of these except the dataproc one), then make it with mk. Also it lets me make quick jgraph files, octal dumps, and tex-to-postscript files.
The second alias I have is the mkd alias:
UNIX> alias mkd pushd !* ; make all ; popd UNIX>This will cd into the specified directory, execute ``make all'', and then cd back. It's something that can be quite convenient.
Finally, mkmake is a simple shell script that creates a generic makefile for a directory. That makefile assumes that each .c file is a separate program, and creates the commands to make each program. This is pretty simple, but often useful. There are similar utilities on some Unix systems that make fancy makefiles. If you're interested try the man page on makedepend and go from there.
Rcs was developed after sccs, which is another revision control system. I have never really used sccs so I can't comment on which one is really better. That's something you'll have to do on your own if it is important to you.
Make the directory RCS. This is where rcs will look to find its files. If there is no such directory, then rcs will put its files into the current directory, which makes things cumbersome.
So create your first draft in the file letter:
UNIX> vi letter ... UNIX> cat letter Dearly beloved, Your eyes are like fried eggs, with blue yolks instead of yellow. Yours poetically, XXXXX UNIX>Now, you ``check in'' a file using the ci command:
UNIX> ci letter RCS/letter,v <-- letter enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >>It asks you to provide a description for the file. This will be kept with the file because sometimes it's handy to have information about a file kept with the file. Type something in and end it with a period at the beginning of a line, or with a control-d:
>> A love letter >> . initial revision: 1.1 done UNIX>Now, ci has done several things. First, it has created the file RCS/letter,v, and tagged it with revision number 1.1. It has also deleted letter:
UNIX> ls letter letter not found UNIX> ls -l RCS total 1 -r--r--r-- 1 plank 312 Jul 7 12:09 letter,v UNIX>You can get letter back by ``checking it out'' with the co command. It's best to use the -l option with co -- this specifies that you're going to want to modify the file:
UNIX> co -l letter CS/letter,v --> letter revision 1.1 (locked) done UNIX> cat letter Dearly beloved, Your eyes are like fried eggs, with blue yolks instead of yellow. Yours poetically, XXXXX UNIX>Now, suppose you modify the letter, and want to save the modification. You do this by another call to ci. If you use -l with ci, its the same as following ci with ``co -l''. This time you'll be prompted for a log message to go with the new revision:
UNIX> vi letter UNIX> cat letter Dearly beloved, Your skin is like lunchmeat, specifically boiled ham. Yours poetically, XXXXX UNIX> ci -l letter RCS/letter,v <-- letter new revision: 1.2; previous revision: 1.1 enter log message, terminated with single '.' or end of file: >> My second draft -- lunchmeat instead of eggs >> . done UNIX>Now, the file has been checked in and out, so it will exist.
UNIX> ls -l letter -rw-r--r-- 1 plank 97 Jul 7 12:14 letter UNIX>You can view the status of an rcs file using the rlog command. This has lots of information about the file and revisions:
UNIX> rlog letter RCS file: RCS/letter,v Working file: letter head: 1.2 branch: locks: strict plank: 1.2 access list: symbolic names: keyword substitution: kv total revisions: 2; selected revisions: 2 description: A love letter ---------------------------- revision 1.2 locked by: plank; date: 1997/07/07 16:15:52; author: plank; state: Exp; lines: +1 -1 My second draft -- lunchmeat instead of eggs ---------------------------- revision 1.1 date: 1997/07/07 16:11:57; author: plank; state: Exp; Initial revision ============================================================================= UNIX>If you want to see the first revision, you can use co -r1.1:
UNIX> co -r1.1 letter RCS/letter,v --> letter revision 1.1 writable letter exists; remove it? [ny](n): y done UNIX> cat letter Dearly beloved, Your eyes are like fried eggs, with blue yolks instead of yellow. Yours poetically, XXXXX UNIX> ls -l letter -r--r--r-- 1 plank 109 Jul 7 12:20 letter UNIX>Note that co makes sure that you want to trash the current version of letter. Note also that it creates letter read-only. That's because you didn't use the -l option to ``lock'' it. Locking is sometimes a pain. When it doubt, try the -l option with a command. If it doesn't work, you can lock and unlock revisions using the ``rcs -lrevision'' and ``rcs -urevision'' commands.
If you just want to look at a revision, but you don't want to mess with the current revision, its often easiest to have co print the revision on standard output with the -p option and you redirect it to a temporary file. First, let's get revision 1.2 back:
UNIX> co -l1.2 letter RCS/letter,v --> letter revision 1.2 (locked) done UNIX> cat letter Dearly beloved, Your skin is like lunchmeat, specifically boiled ham. Yours poetically, XXXXXNow let's get revision 1.1 and put it in the file tmp:
UNIX> co -p1.1 letter > tmp RCS/letter,v --> standard output revision 1.1 UNIX> cat tmp Dearly beloved, Your eyes are like fried eggs, with blue yolks instead of yellow. Yours poetically, XXXXX UNIX>
For example, we'll modify letter one more time, and check it in as revision 5.5:
UNIX> vi letter UNIX> cat letter Dearly beloved, Your nose is epic, like works of Homer Yours poetically, XXXXX UNIX> ci -l5.5 letter RCS/letter,v <-- letter new revision: 5.5; previous revision: 1.2 enter log message, terminated with single '.' or end of file: >> Trying the nose this time >> . done UNIX> rlog letter RCS file: RCS/letter,v Working file: letter head: 5.5 branch: locks: strict plank: 5.5 access list: symbolic names: keyword substitution: kv total revisions: 3; selected revisions: 3 description: A love letter ---------------------------- revision 5.5 locked by: plank; date: 1997/07/08 13:35:23; author: plank; state: Exp; lines: +1 -1 Trying the nose this time ---------------------------- revision 1.2 date: 1997/07/07 16:15:52; author: plank; state: Exp; lines: +1 -1 My second draft -- lunchmeat instead of eggs ---------------------------- revision 1.1 date: 1997/07/07 16:11:57; author: plank; state: Exp; Initial revision ============================================================================= UNIX>The initial revision number does not have to be 1.1 -- you may start with any number (again subject to some constraints) you want.
Here's an example with the love letter:
UNIX> vi letter UNIX> cat letter Dearly beloved, Your hair is like wheat with the chaff removed Yours poetically, XXXXX UNIX> ci -m"Trying the hair\ You know, I'm not sure that this letter is going to be appreciated" letter RCS/letter,v <-- letter new revision: 5.6; previous revision: 5.5 done UNIX> ls RCS lecture.html make2 make4 tmp homemakefile make1 make3 mkmake UNIX> rlog letter RCS file: RCS/letter,v Working file: letter head: 5.6 branch: locks: strict access list: symbolic names: keyword substitution: kv total revisions: 4; selected revisions: 4 description: A love letter ---------------------------- revision 5.6 date: 1997/07/08 13:43:18; author: plank; state: Exp; lines: +1 -1 Trying the hair You know, I'm not sure that this letter is going to be appreciated ---------------------------- revision 5.5 date: 1997/07/08 13:35:23; author: plank; state: Exp; lines: +1 -1 Trying the nose this time ---------------------------- revision 1.2 date: 1997/07/07 16:15:52; author: plank; state: Exp; lines: +1 -1 My second draft -- lunchmeat instead of eggs ---------------------------- revision 1.1 date: 1997/07/07 16:11:57; author: plank; state: Exp; Initial revision ============================================================================= UNIX>
Again, an example should suffice to show how these work. The file hw.c is a ``Hello world'' program in C. It has some rcs keywords in an initial comment:
UNIX> cat hw.c /* $Header$ $Author$ $Date$ $Id$ $Revision$ */ main() { printf("Hello world\n"); } UNIX> ci -l1.0 -m'Initial version to test rcs' hw.c RCS/hw.c,v <-- hw.c enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Hello world program >> . initial revision: 1.0 done UNIX> rlog hw.c RCS file: RCS/hw.c,v Working file: hw.c head: 1.0 branch: locks: strict plank: 1.0 access list: symbolic names: keyword substitution: kv total revisions: 1; selected revisions: 1 description: Hello world program ---------------------------- revision 1.0 locked by: plank; date: 1997/07/08 13:52:50; author: plank; state: Exp; Initial version to test rcs ============================================================================= UNIX> cat hw.c /* $Header: /mahogany/homes/plank/cs494/notes/Make/RCS/hw.c,v 1.0 1997/07/08 13:52:50 plank Exp plank $ $Author: plank $ $Date: 1997/07/08 13:52:50 $ $Id: hw.c,v 1.0 1997/07/08 13:52:50 plank Exp plank $ $Revision: 1.0 $ */ main() { printf("Hello world\n"); } UNIX>Now, if I change hw.c and check it in again, the proper values will change:
UNIX> ed hw.c 276 ed: /world/s/Hello/Hello cruel printf("Hello cruel world\n"); ed: w 282 ed: q UNIX> ci -l -m'Adding cruel' hw.c RCS/hw.c,v <-- hw.c new revision: 1.1; previous revision: 1.0 done UNIX> cat hw.c /* $Header: /mahogany/homes/plank/cs494/notes/Make/RCS/hw.c,v 1.1 1997/07/08 14:06:24 plank Exp plank $ $Author: plank $ $Date: 1997/07/08 14:06:24 $ $Id: hw.c,v 1.1 1997/07/08 14:06:24 plank Exp plank $ $Revision: 1.1 $ */ main() { printf("Hello cruel world\n"); } UNIX>
UNIX> cat pj.c /* * $Log$ */ main() { printf("J\n"); } UNIX> ci -l1.0 -m'First pass' pj.c RCS/pj.c,v <-- pj.c enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Program that prints lots of J's >> ^D initial revision: 1.0 done UNIX> cat pj.c /* * $Log: pj.c,v $ * Revision 1.0 1997/07/08 14:12:30 plank * First pass * */ main() { printf("J\n"); } UNIX>Now, see what happens when you change pj.c and check it in with a new message:
UNIX> ed pj.c 115 ed: /print/s/J/JJ/ ed: w 116 ed: q UNIX> ci -l -m"Printing two J's this time" pj.c RCS/pj.c,v <-- pj.c new revision: 1.1; previous revision: 1.0 done UNIX> cat pj.c /* * $Log: pj.c,v $ * Revision 1.1 1997/07/08 14:14:18 plank * Printing two J's this time * * Revision 1.0 1997/07/08 14:12:30 plank * First pass * */ main() { printf("JJ\n"); }And so on.
UNIX> mkdir ecc UNIX> cd ecc UNIX> cp /home/cs494/notes/Make/ecc1/* . UNIX> make ... lots of junk... UNIX>(Do this on a solaris machine)
You should end up with paper.ps, which is the formatted paper. You can view it with gs or gv.
Ok, now suppose I want to check this in. What I do first is clean up the directory, and make an RCS directory:
UNIX> make clean rm -f paper.dvi *.aux rm -f *.blg *.log *.dlog paper.ps UNIX> mkdir RCS UNIX>Then I make a file called files that contains all of my source files (I delete RCS because it's a directory, and I delete the files file since I'll be checking it in by hand):
UNIX> ls > files UNIX> ed files 93 ed: /RCS/d ed: /files/d ed: w 83 qed: UNIX>Then I check in files and all the files using one big ci command that uses one message for all files. Note that ci will ask for description messages for each file because this is the initial revision:
UNIX> ci -l1.0 -m'Initial draft: intro, conclusions, bibliography' files `cat files` RCS/files,v <-- files enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> List of files >> . initial revision: 1.0 done RCS/README,v <-- README enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Readme file. >> . initial revision: 1.0 done RCS/abstract.tex,v <-- abstract.tex enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Abstract >> . initial revision: 1.0 done RCS/cover.tex,v <-- cover.tex enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Cover letter >> . initial revision: 1.0 done RCS/keywords.tex,v <-- keywords.tex enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Keyword file. >> . initial revision: 1.0 done RCS/makefile,v <-- makefile enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> . initial revision: 1.0 done RCS/paper.bbl,v <-- paper.bbl enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Bibliography file. >> . initial revision: 1.0 done RCS/paper.tex,v <-- paper.tex enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> The paper itself. >> . initial revision: 1.0 done RCS/tsheet.tex,v <-- tsheet.tex enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Title sheet. >> . initial revision: 1.0 done UNIX>I keep the revision keyword in the README file so that I know the revision number of the copy that I'm working on:
UNIX> cat README $Revision: 1.0 $ This is a paper on Reed-Solomon coding for RAID-like systems. UNIX>Now, delete all the files except for the RCS directory:
UNIX> rm * rm: RCS is a directory UNIX> ls RCS UNIX>You can get everything back by checking out the files file, and then checking out `cat files`:
UNIX> co -l files RCS/files,v --> files revision 1.0 (locked) done UNIX> co -l `cat files` RCS/README,v --> README revision 1.0 (locked) done RCS/abstract.tex,v --> abstract.tex revision 1.0 (locked) done RCS/cover.tex,v --> cover.tex revision 1.0 (locked) done RCS/keywords.tex,v --> keywords.tex revision 1.0 (locked) done RCS/makefile,v --> makefile revision 1.0 (locked) done RCS/paper.bbl,v --> paper.bbl revision 1.0 (locked) done RCS/paper.tex,v --> paper.tex revision 1.0 (locked) done RCS/tsheet.tex,v --> tsheet.tex revision 1.0 (locked) done UNIX> make .... UNIX> gs paper.psNow, suppose you make another draft. Pretend you've done this by deleting everything except the RCS directory, and copying all files from the ecc2 directory:
UNIX> cp /home/cs494/notes/Make/ecc2/* . UNIX> make jgraph < 3dots.jgr > 3dots.eps jgraph < broad.jgr > broad.eps ... UNIX> gs paper.psSo, we've added some figures to the paper, which means that many of the files have changed, and some (e.g. the jgr files) have been added. When we want to check in the revision, we do the same thing as before:
UNIX> ls | sed '/RCS/d' | sed '/files/d' > files UNIX> ci -l1.1 -f -m'Second draft -- added figures' files `cat files` RCS/files,v <-- files new revision: 1.1; previous revision: 1.0 done RCS/2dp.jgr,v <-- 2dp.jgr enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Picture of two-dimensional parity >> . initial revision: 1.1 done RCS/3dots.jgr,v <-- 3dots.jgr enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Jgraph of three dots >> . initial revision: 1.1 done RCS/README,v <-- README new revision: 1.1; previous revision: 1.0 done RCS/abstract.tex,v <-- abstract.tex new revision: 1.1; previous revision: 1.0 done RCS/arc,v <-- arc enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Shell script to draw arcs in jgraph >> . initial revision: 1.1 done RCS/basic.jgr,v <-- basic.jgr enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Stem for message sending drawings >> . initial revision: 1.1 done RCS/broad.jgr,v <-- broad.jgr enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Broadcast algorithm drawing >> . initial revision: 1.1 done RCS/cornerbez,v <-- cornerbez enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Program to draw smooth corners in jgraph >> . initial revision: 1.1 done RCS/cover.tex,v <-- cover.tex new revision: 1.1; previous revision: 1.0 done RCS/fanin.jgr,v <-- fanin.jgr enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Fanin algorithm drawing. >> . initial revision: 1.1 done RCS/keywords.tex,v <-- keywords.tex new revision: 1.1; previous revision: 1.0 done RCS/makefile,v <-- makefile new revision: 1.1; previous revision: 1.0 done RCS/manycpu.jgr,v <-- manycpu.jgr enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Picture of multiple cpus on a bus >> . initial revision: 1.1 done RCS/onecpu.jgr,v <-- onecpu.jgr enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Picture of one cpu controlling multiple devices >> . initial revision: 1.1 done RCS/paper.bbl,v <-- paper.bbl new revision: 1.1; previous revision: 1.0 done RCS/paper.tex,v <-- paper.tex new revision: 1.1; previous revision: 1.0 done RCS/tsheet.tex,v <-- tsheet.tex new revision: 1.1; previous revision: 1.0 done UNIX>Note a few things. First, I used the -f option to "force" the check in to occur, even if the file hasn't changed since the previous revision. Otherwise, ci asks you if you really want to do it. Second, ci prompts for description messages for the new files.
Now, again, I can delete all the files but the RCS directory:
UNIX> rm * rm: RCS is a directory UNIX> ls RCS UNIX>And I can get either revision by doing a co -lrevision files and then co -lrevision `cat files`.
I've found this scheme very useful. The only time that it's a drag is when you need to use multiple directories. Then you might want to put the commands into a makefile.
Second, keyword substitution only occurs on checkout. You can call co in such a way that it doesn't do the substitution. This is often useful for when you want to use rcs to check in tar files or shar files.
Third, you can use rcs on any kind of file -- binary, ascii, whatever. Just beware keyword substitution on binary files.