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.
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 definitionsLines providing variable definitions are of the form:NAME = VALUEwhere 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.cC99 functions and OS system-calls that will help you implement variable definitions – calloc(), realloc(), strdup(), getpid(), getppid(), getcwd(), rand(), and getenv().Target linesLines 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 linesLines 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-sequenceIf 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 optionsbake 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().