Scripts and Utilities -- Make/Rcs lecture


  • Jim Plank
  • Directory: /home/cs494/notes/Make
  • This file: http://www.cs.utk.edu/~plank/plank/classes/cs494/494/notes/Make/lecture.html
  • Lecture links: http://www.cs.utk.edu/~plank/plank/classes/cs494/494/notes/Make/links.html
  • Email questions and answers

    Make

    Make is a configuration program that is both wonderful and frustrating. It's wonderful because it enables you to succinctly specify the configuration of the construction of a program, and then perform this construction whenever a part of the program is modified. It's frustrating because there are things like multiple machines, multiple directories, and automation that you wish make could handle as cleanly, but it doesn't, so you end up writing hacks, unreadable makefiles, and makefile generators that make porting your code very difficult.

    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.

    Dependencies

    In your makefile, you list dependencies. What a dependency means is that the construction of the specified target depends on the listed files. For each target, make will recursively treat each dependency as a target. If a target has no dependencies and it exists as a file, then it will be considered ``made''. If a target does not exist, if any of its dependencies are files that are more recent than than it, or if any of its dependencies had to be ``made'', then the target will be ``made''. To make a target, you specify shell commands under the dependency line. You must begin those lines with a tab (use ``cat -v -t -e'' if you want to see the tabs in a makefile). That's the basic gist.

    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> 
    

    Little things: default target, multiple lines, errors

    As a default, make makes the first target in the makefile. If you want to spread a dependency or a command over multiple lines, you must use a backslash as a line continuation. Often it's good form to put an ``all'' target as the first target so that you can always go into a directory and type ``make all'' to construct your executables. The makefile in the make2 directory is the same as the one in the make1 directory, only it has an ``all'' target, and some of its dependencies and commands are spread over multiple lines.

    If any command that make executes exits with a non-zero value, then make will exit instantly.

    Variables

    You can have variables in makefiles. You set them with a line like the following:
    name = string
    
    And 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.

    .SUFFIXES

    It seems irritating to have to specify how to build each object file when all you're doing is calling ``cc -c''. To address this problem, you can specify in general how to build a file with one kind of suffix out of another. This has two parts. First there is a target line called .SUFFIXES which specifies the order in which suffixes should be processed. Second is a line of the form:
    .s1.s2:
    	commands
    
    This 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.

    More complex things

    There are many more things that you can do with make and all of its variants. Read the man page, and don't send me email that I've omitted something -- just consider yourself an advanced make user. I'm not sure if I've ever used more features of make than what I just told you. Granted, this means that I'm not using the full functionality of make, but it also means that my makefiles are portable, and that's important.

    Handy make things

    There are a few things that I do with make that make life easier. The first is my default makefile, which is in my home directory. I've aliased mk to always use that makefile:
    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) -lm
    
    It 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

    Rcs is a suite of programs that help you control versions of a file. Like make, rcs does many things extremely well, but often doesn't do everything you'd like. I'm going to go over how rcs makes my life useful in maintaining versions of directories. I am not going to go over using rcs as a tool for multiple authoring, which is something for which it can be quite useful. As always, read the man page to get a full flavor.

    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.

    A simple example

    It's perhaps easiest to illustrate rcs use with an example. Suppose you're writing a love letter, and you know it's going to take several passes, and you want to keep track of what you've done. Rcs is ideal for this.

    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,
    
    XXXXX
    
    Now 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> 
    

    Revision numbers

    Rcs starts with revision number 1.1, and increases the numbers after the decimal point. If you want to check in a document with a different numbering, just specify a new number as the revision. There are constraints on revision numbers -- you'll have to read the man page or play with rcs for a bit to find out what they are.

    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.

    Setting the message on the command line

    You can use the -mmessage argument to set the log message from the command line. If you want the message to be more than one line, you should use the Bourne shell and put the message in quotes, or do the same with the csh, using backslashes for line continuation. The -m option is the only way that you can associate a log message besides ``Initial revision'' with the initial revision.

    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> 
    

    Efficiency

    In case you're wondering, rcs does not store whole copies of each file. Instead, it stores differences from one version to the next, maintaining a complete copy of the most recent version. That way, checking out the most recent copy is efficient.

    Simple keywords

    Rcs has a feature called ``keyword substitution'' that can often be helpful. If you put a special ``keyword'' into your file, surrounded by dollar signs, then when you check out the file, values will be associated with the keywords. The ones I've found useful are Author, Header, Date, Id, Revision, and Log. I'll discuss Log in the next section.

    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> 
    

    The Log keyword

    The Log keyword inserts the log messages into the file -- it's useful because it allows you to maintain a trail of revisions inside the file. Unfortunately, the logs get pretty big quite quickly, so the feature can become annoying. It is especially useful when you're writing code with other people and checking in multi-authored code. Anyway, here's an example.
    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.

    How I usually use rcs

    I use rcs most religiously when writing papers. This is because papers go through set revisions, and it is often very useful to keep back revisions. What I do is keep all the pertinent files in one directory, and use make to coordinate formatting the file. An example is in the ecc1 directory. Make yourself a clean directory and copy everything from the ecc1 directory:
    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.ps
    
    Now, 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.ps
    
    So, 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.

    Misc things

    Three other things you should know. First, to delete a revision from the RCS log, do rcs -orevision.

    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.