Maintaining Files on Plan 9 with Mk

Andrew G. Hume

andrew@research.att.com

Bob Flandrena

bobf@plan9.bell-labs.com

ABSTRACT

Mk is a tool for describing and maintaining dependencies between files. It is similar to the UNIX program make, but provides several extensions. Mks flexible rule specifications, implied dependency derivation, and parallel execution of maintenance actions are well-suited to the Plan 9 environment. Almost all Plan 9 maintenance procedures are automated using mk.

1. Introduction

This document describes how mk, a program functionally similar to make [Feld79], is used to maintain dependencies between files in Plan 9. Mk provides several extensions to the capabilities of its predecessor that work well in Plan 9’s distributed, multi-architecture environment. It exploits the power of multiprocessors by executing maintenance actions in parallel and interacts with the Plan 9 command interpreter rc to provide a powerful set of maintenance tools. It accepts pattern-based dependency specifications that are not limited to describing rules for program construction. The result is a tool that is flexible enough to perform many maintenance tasks including database maintenance, hardware design, and document production.

This document begins by discussing the syntax of the control file, the pattern matching capabilities, and the special rules for maintaining archives. A brief description of mks algorithm for deriving dependencies is followed by a discussion of the conventions used to resolve ambiguous specifications. The final sections describe parallel execution and special features.

An earlier paper [Hume87] provides a detailed discussion of mks design and an appendix summarizes the differences between mk and make.

2. The Mkfile

Mk reads a file describing relationships among files and executes commands to bring the files up to date. The specification file, called a mkfile, contains three types of statements: assignments, includes, and rules. Assignment and include statements are similar to those in C. Rules specify dependencies between a target and its prerequisites. When the target and prerequisites are files, their modification times determine if they are out of date. Rules often contain a recipe, an rc(1) script that produces the target from the prerequisites.

This simple mkfile produces an executable from a C source file:

CC=pcc

f1: f1.c

    $CC -o f1 f1.c

The first line assigns the name of the portable ANSI/POSIX compiler to the mk variable CC; subsequent references of the form $CC select this compiler. The only rule specifies a dependence between the target file f1 and the prerequisite file f1.c. If the target does not exist or if the prerequisite has been modified more recently than the target, mk passes the recipe to rc for execution. Here, f1.c is compiled and loaded to produce f1.

The native Plan 9 environment requires executables for all architectures, not only the current one. The Plan 9 version of the same mkfile looks like:

</$objtype/mkfile

f1: f1.$O

    $LD $LDFLAGS -o f1 f1.$O

f1.$O:  f1.c

    $CC $CFLAGS f1.c

The first line is an include statement that replaces itself with the contents of the file /$objtype/mkfile. The variable $objtype is inherited from the environment and contains the name of the target architecture. The prototype mkfile for that architecture defines architecture-specific variables: CC and LD are the names of the compiler and loader, O is the code character of the architecture. The rules compile the source file into an object file and invoke the loader to produce f1. Invoking mk from the command line as follows

% objtype=mips mk

vc -w f1.c

vl $LDFLAGS -o f1 f1.k

%

produces the mips executable of program f1 regardless of the current architecture type.

We can extend the mkfile to build two programs:

</$objtype/mkfile

ALL=f1 f2

all:V:  $ALL

f1: f1.$O

    $LD $LDFLAGS -o f1 f1.$O

f1.$O:  f1.c

    $CC $CFLAGS f1.c

f2: f2.$O

    $LD $LDFLAGS -o f2 f2.$O

f2.$O:  f2.c

    $CC $CFLAGS f2.c

The target all, modified by the attribute V, builds both programs. The attribute identifies all as a dummy target that is not related to a file of the same name; its precise effect is explained later. This example describes cascading dependencies: the first target depends on another which depends on a third and so on. Here, individual rules build each program; later we’ll see how to do this with a general rule.

3. Variables and the environment

Mk does not distinguish between its internal variables and rc variables in the environment. When mk starts, it imports each environment variable into a mk variable of the same name. Before executing a recipe, mk exports all variables, including those inherited from the environment, to the environment in which rc executes the recipe.

There are several ways for a variable to take a value. It can be set with an assignment statement, inherited from the environment, or specified on the command line. Mk also maintains several special internal variables that are described in mk(1). Assignments have the following decreasing order of precedence:

1) Command line assignment

2) Assignment statement

3) Imported from the environment

4) Implicitly set by mk

For example, a command line assignment overrides a value imported from the environment.

All variable values are strings. They can be used for pattern matching and comparison but not for arithmetic. A list is a string containing several values separated by white space. Each member is handled individually during pattern matching, target selection, and prerequisite evaluation.

A namelist is a list produced by transforming the members of an existing list. The transform applies a pattern to each member, replacing each matched string with a new string, much as in the substitute command in sam(1) or ed(1). The syntax is

${var:A%B=C%D}

vl $LDFLAGS -o f1 f1.k

%

produces the mips executable of program f1 regardless of the current architecture type.

We can extend the mkfile to build two programs:

</$objtype/mkfile