|
Project 2018 - the program bake
The goal of this project
is to implement a program named bake supporting a
small subset of the features of the standard Unix-based utility
name make.
Successful completion of the project will develop your understanding of some
advanced features of the C99 programming language,
and your understanding of the role of a number of OS system-calls
responsible for process invocation and determining file attributes.
This is a systems-specific project.
You will find this project significantly easier to complete on macOS or Linux.
You will likely have greater difficulty completing it on Windows,
and it is recommended that you don't try.
Your submission will be tested and marked on CSSE macOS laboratory machines.
|
Project Description
In
Lecture 12
the standard Unix utility make,
frequently used to maintain programming projects through knowledge of the
projects' dependencies,
was introduced.
For this project we'll develop a program named bake,
providing a small, but useable, subset of make's facilities,
with the addition that a project's dependencies may include resources
accessible over the Internet.
Similar to make,
our program bake will read the specification of a project's
dependencies from a line-oriented textfile.
By default,
bake will first attempt to open the file Bakefile or
(if unsuccessful) the file bakefile
from the current working directory.
Sequences of characters on each line that are separated by one or more
space or tab characters are termed words and are used to
identify targets and dependencies (see later).
The text lines within bake's specification file are of four forms -
- lines beginning with a '#' character are comment lines
and may be ignored;
- lines providing variable definitions which may be expanded within
later lines of the file;
- lines defining targets and optional dependencies used to
determine if a target needs 'rebuilding'; and
- lines defining actions, immediately following target lines,
providing shell commands to be executed to 'rebuild' each target.
Note that action lines do not necessarily create or rebuild a (physical)
file named after the target,
and a target might never exist as a file.
Lines within the specification file that end with the '\' character
(actually, the last character before the line's newline sequence)
indicate that the following line is a continuation of the current line,
and should be appended to form a 'combined' line.
C99 functions and OS system-calls that will help you implement
the reading of the specification file -
access(),
fopen(),
fgets(),
strdup(), and
fclose().
- Variable definitions
-
Lines providing variable definitions are of the form:
NAME = VALUE
where zero-or-more whitespace characters (spaces and tabs) may appear
before and after the '=' character.
If the pattern $(NAME),
known as a variable expansion,
appears anywhere on any following text line,
the pattern $(NAME) is replaced at that point by the characters
of VALUE.
If a variable expansion requests a variable NAME that has not been
previously defined in the file,
then the value of NAME is sought from the bake process's
environment.
Thus the variable expansion $(HOME) might be expanded with the value
/User/home/chris at that point.
If no value of NAME can be found anywhere,
the empty string is 'inserted' at the current point.
As special cases, the variable expansions
$(PID),
$(PPID),
$(PWD),
and
$(RAND)
are replaced by the results of the function calls
getpid(),
getppid(),
getcwd(),
and
rand() formatted as strings, respectively.
Any line may have multiple variable expansions.
Note that a variable expansion may occur at any point in the text file,
including in the NAME and VALUE parts of a new variable
definition line itself.
A typical example is:
C99 = cc -std=c99
-Wall -pedantic -Werror
....
$(C99) -o try try.c
C99 functions and OS system-calls that will help you implement
variable definitions -
calloc(),
realloc(),
strdup(),
getpid(),
getppid(),
getcwd(),
rand(), and
getenv().
- Target lines
-
Lines defining a target are of the form:
targetname :
[optional dependencies]
where zero-or-more whitespace characters (spaces and tabs) may appear
before and after the ':' character.
The first targetname found in a file is the default target,
and is the one to be rebuilt if no other targetname is provided on
bake's command-line (see later).
If the targetname represents a file in the current working directory,
then the file's modification date is used as the date of the target.
Otherwise, the target is assumed to be created by the target's actions,
if they are successfully executed.
Dependencies: Each target line may provide zero-or-more dependencies.
If there are no dependencies, then the target requires rebuilding.
There are three types of dependencies -
ones that identify existing files on disk,
ones that identify other targets,
and
ones that identify a web-based URL (a Uniform Resource Locator).
If any dependency does not exist, or has been modified more recently
that its target, then the target requires rebuilding (see Action lines).
URL dependencies: if a dependency looks like an simple URL
(it commences with the pattern
file://, http://, or https://)
it is assumed to define a URL to
be requested to determine if the associated file exists and has been
modified more recently that the current target.
bake must use the external utility curl
to determine if the file (indicated by the URL)
has been modified more recently than the current target.
curl is usually installed on macOS or Linux machines,
and is available from curl.haxx.se.
Note that bake should not download the indicated file,
just use curl's --head option to find the modification date.
However,
any target's action may choose to explicitly download a file.
It is an error if curl reports if
a URL-based dependency is not found;
it is not sought as a target.
C99 functions and OS system-calls that will help you implement
target lines -
calloc(),
realloc(),
strdup(),
stat(),
fork(),
execl(),
wait(),
fopen(),
fgets(),
fclose(),
mktime(),
gettimeofday(), and
strptime().
- Action lines
-
Lines defining actions must immediately follow a target line,
or another action line.
We say that the sequence of one-or-more action lines are associated
with a given target.
Action lines must begin with a tab-character (providing a visual indentation).
They are of the form:
\t[optional
modifier] shell-command-sequence
If a target requires 'rebuilding', then each of its associated actions
are passed, in turn, to the external command defined
by the value of SHELL from the bake process's environment.
In the absence of an environment variable named SHELL,
the default value /bin/bash is to be used.
Each action's command sequence is to be executed by the external program
provided by the value of SHELL
by passing the command sequence to the shell's -c command-line option.
Note that none of the characters forming the shell-command-sequence are
interpreted by bake.
For example, if a shell-command-sequence
rm -f tempfile needs be executed,
then bake may invoke and wait for the command
/bin/bash -c "rm -f tempfile".
Each associated action is executed in turn (top-to-bottom) until either
all actions are executed or until one of them terminates with failure
(with a non-zero exit status).
If all associated actions execute successfully,
then the target is considered 'rebuilt'
(even if it wasn't actually created or modified).
Action line modifiers:
A modifier is a single character that appears between an action's leading
tab character and its shell-command-sequence:
'@' - By default bake prints each command sequence to stdout
just before it is executed.
If the '@' modifier appears, the command sequence should not be printed.
'-' - By default bake uses the exit status of an action to
determine if following actions should be executed.
If the '-' modifier appears, the exit status of an action is always
considered to be a success (even if the command sequence actually failed).
C99 functions and OS system-calls that will help you implement
action lines -
getenv(),
calloc(),
realloc(),
strdup(),
fork(),
execl(), and
wait().
- Invocation and command-line options
-
bake is to be invoked with:
bake [command-line-options] [targetname]
The default execution of bake may be modified through a small
number of command-line options.
If any option is provided multiple times,
only the last occurence of the option is used.
If the optional targetname is present,
then it is the target to be (possibly) rebuilt instead
of the default (first) target.
The options are:
-C directoryname : before opening and reading the specification file,
bake is to change directory to the indicated directory.
-f filename : instead of reading its specification from
the default Bakefile or bakefile,
bake reads its specification from the indicated file.
-i : ignore the unsuccessful termination of actions;
continue executing a target's actions even if any fail.
-n : print (to stdout) each shell-command-sequence
before it is to be executed,
but do not actually execute the commands.
Assume that each shell-command-sequence executes successfully.
This option enables bake to simply report what it would do,
without doing it.
-p : after reading in the specification file,
print its information (from bake's internal representation)
to stdout
with all variable expansions performed.
Then simply exit with success.
Only the targets, dependencies, and actions need be printed
(though you may wish to also print the variables (names and values).
-s : execute silently, do not print each shell-command-sequence
before it is executed.
C99 functions and OS system-calls that will help you implement
command-line options
getopt(),
chdir(),
and
exit().
Good luck!
Chris McDonald.
|