CMPSC 311, Spring 2013, Project 8

Posted Apr. 11, 2013.  Due Apr. 24, 2013, by 11:55 pm on ANGEL.  50 points.

The goal of this project is to implement part of the make utility, as a continuation of Projects 4 and 5.  This is a two-person project.

Follow the instructions in Project 7 with regard to turning in the program.  Late submissions will not be accepted after 12 noon on Apr. 26.



Write a program that imitates make with the -n option.  See the Project 4 description for summary information and links to complete information about make.

You can start with the posted solution to Project 5, or from your own Project 5 solution.  Most of the extra output controlled by the -v option of Project 4 and 5 is not needed here.  The sample output given here, from Sun's make program on Solaris and the GNU make program on Linux and Mac OS X, can be different with your program, but try not to confuse the reader.

Here is a possible starting point for the program design, sketch.c .  The files cmpsc311.[ch] and names.[ch] from c2html will also be useful, either directly or as a guide to building lists of recipes, sources and targets.



Sample Makefile (or Hakefile)

# sample Makefile for CMPSC 311 Project 8

a : b c
        recipe 1

b : d
        recipe 2

c : e
        recipe 3



Sample test script (the file name is test-script)

# test script for CMPSC 311 Project 8

# Important - edit this file!
#    change "make -n" to the name of your program

while read in
do
  echo 'pr8 test:' $in
  $in
  echo
done <<EOF
  rm -f a b c d e
  make -n
  sleep 1
  touch d
  make -n
  sleep 1
  touch e
  make -n
  sleep 1
  touch c
  make -n
  sleep 1
  touch b
  make -n
  sleep 1
  touch a
  make -n
  sleep 1
  touch c
  make -n
  make -n b c
EOF




Sample test results, using "make -n" on Solaris, using Sun's make program

% sh test-script
pr8 test: rm -f a b c d e

pr8 test: make -n
make: Fatal error: Don't know how to make target `d'

pr8 test: sleep 1

pr8 test: touch d

pr8 test: make -n
recipe 2
make: Fatal error: Don't know how to make target `e'

pr8 test: sleep 1

pr8 test: touch e

pr8 test: make -n
recipe 2
recipe 3
recipe 1

pr8 test: sleep 1

pr8 test: touch c

pr8 test: make -n
recipe 2
recipe 1

pr8 test: sleep 1

pr8 test: touch b

pr8 test: make -n
recipe 1

pr8 test: sleep 1

pr8 test: touch a

pr8 test: make -n
`a' is up to date.

pr8 test: sleep 1

pr8 test: touch c

pr8 test: make -n
recipe 1

pr8 test: make -n b c
`b' is up to date.
`c' is up to date.




Sample test results, using "make -n" on Linux or Mac OS X, using the GNU make program

% sh test-script
pr8 test: rm -f a b c d e

pr8 test: make -n
make: *** No rule to make target `d', needed by `b'.  Stop.

pr8 test: sleep 1

pr8 test: touch d

pr8 test: make -n
recipe 2
make: *** No rule to make target `e', needed by `c'.  Stop.

pr8 test: sleep 1

pr8 test: touch e

pr8 test: make -n
recipe 2
recipe 3
recipe 1

pr8 test: sleep 1

pr8 test: touch c

pr8 test: make -n
recipe 2
recipe 1

pr8 test: sleep 1

pr8 test: touch b

pr8 test: make -n
recipe 1

pr8 test: sleep 1

pr8 test: touch a

pr8 test: make -n
make: `a' is up to date.

pr8 test: sleep 1

pr8 test: touch c

pr8 test: make -n
recipe 1

pr8 test: make -n b c
make: `b' is up to date.
make: `c' is up to date.




The touch command is used here to create a file that doesn't already exist, or to update the modification time of a file that already exists.  The new file has 0 bytes, and the modified file contents are unchanged.

The sleep command is used here to reduce problems associated with times that are "almost equal".

Recall that the command "ls -lt" will list files in time-order rather than name-order.



Here is a suggestion for organization of the source code, and a useful function for comparing file times.

The stat() function uses struct stat to hold the information obtained about a file.  See the Interprocess Communication notes for some details.  The time fields of this struct are sufficiently non-portable that it seems like a good idea to provide some code and compiler options.

Makefile (take care with the tabs if you copy-and-paste)

# CMPSC 311, Project 8, solution

SRC = pr8.4.c
LIB = cmpsc311.c names.c macro.c recipes.c sources.c targets.c
INC = cmpsc311.h names.h macro.h recipes.h sources.h targets.h

# select the version of mtime() appropriate for your OS

# Solaris, mtime() uses time_t st_mtime
# OPT = -D_XOPEN_SOURCE=600

# Linux, mtime() uses struct timespec st_mtim
OPT = -D_XOPEN_SOURCE=700 -DMTIME=1

# Mac OS X, mtime() uses struct timespec st_mtimespec
# OPT = -DMTIME=2

pr8 : ${SRC} ${LIB} ${INC}
        gcc -std=c99 -Wall -Wextra $(OPT) -o pr8 ${SRC} ${LIB}

clean:
        rm -f pr8 a.out *.o

In pr8.4.c, function mtime() is

// last-modification time of a file

// The appropriate field of struct stat varies according to the OS.
//   Posix, 2001        time_t st_mtime
//   Posix, 2004        time_t st_mtime
//   Posix, 2008        struct timespec st_mtim
//                      macro st_mtime is st_mtim.tv_sec
//   Solaris            time_t st_mtime
//   Linux              time_t st_mtime
//   Mac OS X           struct timespec st_mtimespec
//                      or, st_mtime

// The struct timespec fields are defined in <time.h>.
//   time_t tv_sec;     // seconds
//   long   tv_nsec;    // nanoseconds

// The type time_t (time in seconds) is defined in <time.h> and <sys/types.h>.
// The Posix Standard allows time_t to be an integer, or a real-floating type.
// If time_t isn't an integer, we would be surprised, but the code should
//   still work properly.

// A nonexistent file is treated as extremely old.

static struct timespec mtime(const char *file)
{
  struct stat s;
  struct timespec t = { 0, 0 };

  if (stat(file, &s) == 0)
#if   defined(MTIME) && MTIME == 1      // Linux
    { t = s.st_mtim; }
#elif defined(MTIME) && MTIME == 2      // Mac OS X
    { t = s.st_mtimespec; }
#elif defined(MTIME) && MTIME == 3      // Mac OS X, with some additional settings
    { t.tv_sec = s.st_mtime; t.tv_nsec = s.st_mtimensec; }
#else                                   // Solaris
    { t.tv_sec = s.st_mtime; }
#endif

  return t;
}

To compare the times of two files, for the purpose of deciding whether or not to print some recipes,

struct timespec t1 = mtime("file1");  // target
struct timespec t2 = mtime("file2");  // source

if (t1.tv_sec == 0                    // target does not exist
    || t1.tv_sec < t2.tv_sec          // target is older than source
    || ((t1.tv_sec == t2.tv_sec) && (t1.tv_nsec < t2.tv_nsec))
   )
  { /* print recipes */ }



Here's another file to try with your tests:
# sample Makefile for CMPSC 311 Project 8

a x y : b c
    recipe 1a
    recipe 1b

b : d
    recipe 2a
    recipe 2b

c : e
    recipe 3a
    recipe 3b
    recipe 3c

clean:
    recipe 4



Last revised, 10 Apr. 2013