CSCI 3000
Spring 2017
Programming Assignment 1

Due date: Monday, February 13.

The Unix/Linux 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 in C.

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 single line beginning with a tab. For the simplified version of make, there can be from 0 to 10 dependencies, separated by white space, and 0 to 10 commands, one per line.

Each command is preceded by a tab. The rule ends at the first line that does not begin with a tab.

There can be blank lines, but they are not required. A line whose first nonblank character is # is a comment. There can be spaces at the beginning of a rule.

Here is a sample makefile. Tabs are shown in blue.

# This is a makefile.

foo.o: foo.cpp
	g++ -c foo.cpp

  bar.o: bar.cpp
	g++ -c bar.cpp
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.

    (The real make utility forks a separate shell to process each line. But your simplified make utility should run each line directly. If the line asks to run g++, then exec g++, not a shell.)

Program requirements

Write a C program for the Unix environment that processes simplified makefiles according to the above outline.

Your make utility must take 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, and store its contents. Then it should locate the target given in the command line (foo in the example) 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. Call your program mymake.c.

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

  3. There is a standard function called system that will run a command and wait for it to finish. Do not use it. Do your own forks, execs and waits to manage the processes.

  4. Parse the commands 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 function. A monolithic main function that does the entire job 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. This is not an exhaustive list. Add any function that implements a sensible concept that you need to handle.

System functions that you might want to use

The following system functions will be useful. Each is described by the man command. Command
  man -s 2 wait
shows a description of wait in section 2 of the manual.

execv or execve

Cause the current process to run 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.)

wait or waitpid

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. If not, just report a usage error and stop.

Asking questions

To ask a question, submit your current work using the following command.

~abrahamsonk/3000/bin/submit q1 mymake.c

Then send me an email stating your questions about your program.

Submitting your work

Log into xlogin.cs.ecu.edu. Make sure your current working directory contains your program, which I assume here is called mymake.c. Do the following command.

~abrahamsonk/3000/bin/submit 1 mymake.c