CS360 Lab #4 -- Fakemake


Now we get to a more fun assignment. The goal is to write a restricted and pared-down version of make(1). This will give you some practice using stat(2v) and system(3).

Description of fakemake

Your job is to write the program fakemake. Like make, fakemake's job is to help you automate compiling. Unlike make, fakemake limits itself to making one executable, and assumes that you are using gcc to do your compilation.

The syntax of fakemake is as follows:

fakemake [ description-file ]
If no description-file is specified, then it assumes that the description file is the file fmakefile. Obviously, if the description file doesn't exist, then the program should exit with an error.

Each line of the description file may be one of six things:

What fakemake does is compile all the .c files into .o files (using gcc -c), and then compile all the .o files into the final executable. Like make, it doesn't recompile a file if it is not necessary. It uses the following algorithm to decide whether or not to compile the .c files:

If the executable file exists, and is more recent than the .o files, and no .c file has been recompiled, then fakemake does not remake the executable. Otherwise, it does remake the executable (using gcc -o).

Obviously, if a .c or .h file is specified, and it does not exist, fakemake should exit with an error. If there are any compilation errors mid-stream, fakemake should exit immediately. The order of files should be the order specified (this is for compilation and for constructing the final executable).


Example

For example, get into a clean directory and then type
UNIX> cp ~plank/cs360/labs/lab4/* .
UNIX> ls
f.c             f.h             f2.c            makefile        mysort.fm
f.fm            f1.c            lab.html        mysort.c
UNIX> make
gcc -c -g f.c
gcc -c -g f1.c
gcc -c -g f2.c
gcc -g -o f f.o f1.o f2.o
gcc -c -g -I/home/plank/cs360/include mysort.c
gcc -g -o mysort mysort.o /home/plank/cs360/objs/libfdr.a 
UNIX> f
This is the procedure F1 -- in f1.c
This is the procedure F2 -- in f2.c
UNIX> mysort < f.c
  f1();
  f2();
main()
{
}
UNIX> make clean
rm -f core *.o f mysort
UNIX> ls
f.c             f.h             f2.c            makefile        mysort.fm
f.fm            f1.c            lab.html        mysort.c
UNIX>
So, this directory contains source code for two programs. The first, f, is made up of three C files: f.c, f1.c and f2.c, and one header file: f.h. The second is mysort.c from the Rbtree-1 lecture. The makefile contains a specification of how to make these programs using make. The file f.fm is the fakemake file for making f, and mysort.fm is the fakemake file for making mysort. Try it out, using the fakemake executable in /home/plank/cs360/labs/lab4/fakemake:
UNIX> ~plank/cs360/labs/lab4/fakemake
fakemake: fmakefile No such file or directory
UNIX> ~plank/cs360/labs/lab4/fakemake f.fm
gcc -c -g f.c
gcc -c -g f1.c
gcc -c -g f2.c
gcc -o f -g f.o f1.o f2.o
UNIX> touch f.c         
UNIX> ~plank/cs360/labs/lab4/fakemake f.fm
gcc -c -g f.c
gcc -o f -g f.o f1.o f2.o
UNIX> rm f
UNIX> ~plank/cs360/labs/lab4/fakemake f.fm
gcc -o f -g f.o f1.o f2.o
UNIX> touch f.h
UNIX> ~plank/cs360/labs/lab4/fakemake f.fm
gcc -c -g f.c
gcc -c -g f1.c
gcc -c -g f2.c
gcc -o f -g f.o f1.o f2.o
UNIX> touch f.h
UNIX> touch f.o f1.o
UNIX> ~plank/cs360/labs/lab4/fakemake f.fm
gcc -c -g f2.c
gcc -o f -g f.o f1.o f2.o
UNIX> ~plank/cs360/labs/lab4/fakemake f.fm
f up to date
UNIX> f
This is the procedure F1 -- in f1.c
This is the procedure F2 -- in f2.c
UNIX> ~plank/cs360/labs/lab4/fakemake mysort.fm
gcc -c -g -I/home/plank/cs360/include mysort.c
gcc -o mysort -g -I/home/plank/cs360/include mysort.o /home/plank/cs360/objs/libfdr.a
UNIX> mysort < f.c
  f1();
  f2();
main()
{
}
UNIX> rm f.h
UNIX> ~plank/cs360/labs/lab4/fakemake f.fm
fmakefile: f.h: No such file or directory
UNIX> 
As you can see, fm works according to the above specification. It only recompiles modules when it needs to. When you're in doubt about what your fakemake should do, see what /home/plank/cs360/labs/lab4/fakemake does and emulate its behavior.

The Grading Script

The grading script works by running two scripts -- your-script.sh and correct-script.sh Each script copies files to the current directory from the lab directory, compiles them, maybe deletes some files or modifies their times, then calls fakemake.

If your program isn't working like mine, modify your-script.sh to perform a "ls -l --full-time" listing right before the fakemake call. Here's an example. First, I run the gradescript on example number 3. It runs correctly, but I still want to see what your-script.sh does:

UNIX> ~plank/cs360/labs/lab4/gradescript 3
Problem 003 is correct.

Test: sh -c 'sh your-script.sh > tmp-003-test-stdout.txt 2> tmp-003-test-stderr.txt'

Correct output generated with : sh -c 'sh correct-script.sh > tmp-003-correct-stdout.txt 2> tmp-003-correct-stderr.txt'
UNIX> cat your-script.sh
ge=/home/plank/cs360/labs/lab4/Gradescript-Examples
cp $ge/onefile.c .
gcc -c onefile.c
sleep 1
touch onefile.c
rm -f onefile
cp $ge/001.fm fmakefile
if ./fakemake; then
  ./onefile
fi
UNIX> sh your-script.sh
gcc -c onefile.c
gcc -o onefile onefile.o
With a taste of your lips 
I'm on a ride 
You're toxic 
I'm slipping under 
With a taste of poison paradise 
UNIX> 
If you can read the script -- it copies /home/plank/cs360/labs/lab4/Gradescript-Examples/onefile.c to the current directory, compiles it to onefile.o, then sleeps for a second and makes sure that there is no file onefile in the current directory. It then copies the fake-makefile 001.fm from /home/plank/cs360/labs/lab4/Gradescript-Examples and runs fakemake. Since onefile.c should be one second newer than onefile.o, fakemake should recompile onefile.c to onefile.o, and then compile onefile.o to onefile. It should exit correctly, and then the script runs onefile, which, like all the example programs, prints some random lines from Toxic by Britney Spears (a surprisingly good song, if you ask me) in a random order.

Below, I modify your-script so that it does "ls -l --full_time" on the onefile programs:

UNIX> vi your-script.sh
UNIX> cat your-script.sh
ge=/home/plank/cs360/labs/lab4/Gradescript-Examples
cp $ge/onefile.c .
gcc -c onefile.c
sleep 1
touch onefile.c
rm -f onefile
cp $ge/001.fm fmakefile
ls -l --full-time onefile*                 # this is the new line
if ./fakemake; then
  ./onefile
fi
UNIX> sh your-script.sh
-rw-r--r-- 1 plank loci  246 2011-02-09 11:05:36.000000000 -0500 onefile.c
-rw-r--r-- 1 plank loci 1880 2011-02-09 11:05:35.000000000 -0500 onefile.o
gcc -c onefile.c
gcc -o onefile onefile.o
With a taste of your lips 
I'm on a ride 
You're toxic 
I'm slipping under 
With a taste of poison paradise 
UNIX> 
You can see from the long listing that onefile.o is older than oldfile.c by one second.

The test files from 6 through 25 test a two-file compilation, and the remainder test header files, libraries, etc.


Details

Obviously, you'll have to use stat() to test the ages of programs. The st_mtime field of the struct stat should be used as the age of the program.

To execute a string, you use the system() procedure. It executes the given string as if that string is a shell command (sh, not csh, although it shouldn't matter). If it returns -1, then it couldn't execute the command. Otherwise, it returns that exit() value of the command. You should assume that if a program exits with a non-zero value, then there was an error, and you should stop compiling (i.e. your fakemake should exit).


Strategy

It's my hope that you don't need these sections on strategy too much, but to help you out, here's how I wrote this program.