CSCI 4630
Summer 2001
Programming assignment 1


The make utility is used to construct and install software and other files according to predefined scripts. This exercise has you write a simplified version of make.

Due date: Monday, May 28


Makefiles

The instructions for how to do a make are contained in a makefile. The makefile contains a sequence of rules of the following form.

    target: dependency dependency ... dependency
             command
             ...
             command
where the target and dependencies are strings not containing any white-space characters or colons, and each command is a command to be executed. For your version, there can be from 0 to 10 dependencies, separated by white space, and 0 to 10 commands, one per line. However, it must be possible to change each of those limits by changing a single line of your program.

Each command is preceded by a tab. The rule ends at the first line that does not begin with a tab. For example, here is a sample makefile.

  foo.o: foo.cc
       g++ -c foo.cc
  bar.o: bar.cc
       g++ -c bar.cc
  foo: foo.o bar.o
       g++ -o foo foo.o bar.o

A rule tells how to build a given target. A rule is processed as follows.

  1. The first step is to build all of the dependencies. Each dependency is examined, in the order written.
    1. If the dependency is a target (given by another rule) then that target is built, following these rules for building a target.
    2. If the dependency is not a target in the makefile, but is the name of a file in the current directory, then there is nothing to do to build the dependency.
    3. If the dependency is neither a target nor a file, then an error message is printed, stating that the dependency could not be built.
  2. After all of the dependencies are built, the next step is to determine whether the target is out of date.
    1. Start by examining the target and all of the dependencies. If any of them are not the name of a file that exists in the current directory, then by definition the target is out of date.
    2. Suppose that the target and all of the dependencies are the names of files. Get the modification times of all of those files. If any of the dependency files has a more recent modification time than the target, then the target is out of date.
    3. If the target is not found to be out of date by the preceding two rules, then the target is up to date.
  3. If the target is found to be out of date, then each of the commands that is part of the rule must be performed. The commands are done in the order written. Each command is performed by a separate process.

Program requirements

Write a C++ program for the Unix environment that processes makefiles.

Your make utility is run with a single argument, which is the target to be built. For example, the command might be

  make foo
The make utility should read in the entire makefile from a file called "makefile" in the current directory, then locate the target and build it. Treat building the main target just like building one of the dependencies. For example, if it is not a target of any of the rules, but is a file, then there is nothing to do.

When a command is run, echo the command to the standard output so that you can see what is being done.

  1. You must build the program yourself. It is not acceptable just to exec the system make utility and let it do the work.

  2. There is a standard function called system that will run a command and wait for it to finish. Do not use it. I want you to do your own fork, exec and wait to manage the processes.

  3. The commands should be parsed in a very simple way. Just break them at white space. Do not pay attention to special syntactic things such as quotes.

Keep your program simple. Write it in reasonably short functions. Do not try to write it in one large main program. A single main program is unacceptable, regardless of whether it solves the problem.

You are required to include a clear and correct contract with every function. (And do not pretend that you did not see this requirement, because you did.) The contract should say exactly what the function accomplishes, and how its actions depend on its parameters.


Hints

Break the problem down into small subproblems, and solve each of those subproblems. Here are some subproblems that you might encounter.

Functions that you might want to use

The following system functions will be useful. Each is described by the man command. For wait, use
  man -s 2 wait
to get the description of wait in section 2 of the manual.
execv
Change to another program.
fork
Fork a child process.
stat
Get information about a file, including whether the file exists and its modification time. (The modification time is an integer number of seconds since the beginning of 1970. This is called UTC time. Type time_t is the same as long int.)
wait
Wait for a process to terminate.

You can get the argument to the program using the parameters of main. The operating system calls main(argc,argv,envp) where argc is the number of parts in the command line (including the command itself), argv is an array of null-terminated strings (type char**) that holds the components of the command line, and envp is a pointer to the environment, which gives values of environment variables. The target that you are asked to make is argv[1]. But be sure to check that argc is 2.