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.
- The first step is to build all of the dependencies.
Each dependency is examined, in the order written.
- If the dependency is a target (given by another rule) then
that target is built, following these rules for building a target.
- 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.
- If the dependency is neither a target nor a file, then an
error message is printed, stating that the dependency could not
be built.
- After all of the dependencies are built,
the next step is to determine whether the target is
out of date.
- 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.
- 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.
- If the target is not found to be out of date by the preceding
two rules, then the target is up to date.
- 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.
You must build the program yourself. It is not acceptable
just to exec the system make utility and let it do the work.
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.
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.
- Reading in the makefile. You will want a function that
reads the makefile into a data structure. Keep it simple, and
do not be excessively concerned with efficiency. You need an
array of rules, where each rule has a target, up to four
dependencies and up to four lines of commands. Do not be naive
about memory allocation.
- Parsing the commands.
You will want a function that takes a line
apart into its pieces, and puts the pieces into an array. Think about
how to do this. Do not be naive about memory allocation.
- Running the commands. You will want a function that runs
a given command. It should fork a new process (using fork) and
run the command in that process (using execv). It should wait for
the other process to finish before returning.
- Checking whether out of date. Write a function that
checks whether a particular target is out of date. If the
target is not the target in any rule, then check whether it
is the name of a file. If so, then it is up to date. If there
is no rule for it, and it is not a file, then it is an error. If there
is a rule, then check according to the description above.
- Finding a rule. You will want a function that finds a
rule with a given target, or says that there is no rule.
- Processing a dependency / building a target. You will
want a function that builds a target.
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.