jmk - Make in Java

Maintain a group files in a consistent state

SYNOPSIS

jmk [ options ] [ target ]*
java -jar jmk.jar [ options ] [ target ]*

DESCRIPTION

[Rules] [File Names] [Assignments] [Functions] [Commands] [Rule Patterns] [Special Targets] [File Inclusion] [Conditionals] [Expressions] [Example] [Grammar]

jmk is an application which is used to ensure that a set of files is in a consistent state. If jmk detects an inconsistency, it executes commands that correct the inconsistency. The rules used to detect and correct inconsistencies are given in a makefile.

jmk is designed to support the task of writing platform independent makefiles. File names and path lists are written in a canonical form, and translated by jmk into native form.

The rules given in a jmk makefile can invoke programs as separate processes. To produce a machine independent makefile, a makefile should only invoke programs that use the same command line argument syntax across platforms. Examples of such programs include Sun's Java compiler (javac) and their Java Archive Tool (jar).

Rules

A makefile contains a sequence of statements that are used to build a database of rules. The elements of a makefile rule are targets, prerequisites, and commands. Typically, targets and prerequisites are names of files. A makefile rule is a nonempty list of targets terminated by a colon, a list of prerequisites terminated by a semicolon, and optionally, a sequence of commands delimited by curly braces. A rule that keeps file A.class consistent with A.java follows.

"A.class": "A.java"; {
  exec "javac" "-g" "A.java";
}

This rule runs the Java compiler when A.class is older than A.java or nonexistent.

A makefile rule without commands adds prerequisites to the end of a target's list of prerequisites. For each target, only one makefile rule can contain commands.

Internally, there is one rule for each target. The interpretation of a makefile rule depends on whether its target exists. When the target does not exist, each prerequisite is checked for consistency in textual order, and then the rule's commands are executed sequentially. The rule fails if an error is detected while running a command or if the rule has no prerequisites and no commands. A target with no rule implied by the makefile behaves as if it is the target of a rule with no prerequisites and no commands.

When the target exists, the rule's commands are executed only if the target is in an inconsistent state. Each prerequisite is checked for consistency in textual order. The target's state is consistent if the consistency check of the prerequisites found they were all consistent, each prerequisite exists, and no prerequisite is younger than the target. When the target is in an inconsistent state, the rule's commands are executed sequentially. The rule fails if an error is detected while running a command.

Errors generated by commands are ignored when a command is preceded by the '-' character.

File Names

File names are given by strings, which are sequences of characters delimited by double quotes. The backslash and double quote character may be included in a string by preceding each character with a backslash.

The slash character separates the directory and file components in a file name. When running commands and testing for the existence of files, the slash character is replaced by the character appropriate for the host, which is the first character of the system property file.separator.

The semicolon character separates file names in a path list. When running commands and testing for the existence of files, the semicolon character is replaced by the character appropriate for the host, which is the first character of the system property path.separator.

To enhance portability, file names should contain only the alphanumeric, the period, the underscore, and the separator characters described above. jmk treats the asterisk and the percent sign characters specially in some contexts, so these should be avoided in file names.

Assignments

There are two types of values used to specify rules: lists of strings and functions. An assignment associates a value with a variable. An assignment is a identifier and an equal sign, followed by a semicolon terminated expression sequence that denotes a value.

javac = "javac" "-g";

After the assignment, a reference to the variable is replaced by its value. A reference to an unassigned variable produces the empty list. In the following example, javac is replaced by two strings.

exec javac "A.java";

Assignments occur while reading the makefile and before any rules are interpreted.

In common usage, variables are assigned lists of strings and the elements of the right-hand-side of an assignment are string constants, variable references, and calls of predefined functions. A complete description of expressions occurs later in this document.

Functions

A list of strings can be transformed by a function. A function call is delimited by parenthesis. The first element of the call is an expression, which is usually the variable of a predefined function. The parameters to the call are comma separated expression sequences. A description of each predefined function follows.

(subst pattern, replacement, list)
The function subst produces a list of the same length as list. Each string in the list is the result of replacing every non-overlapping occurrence of pattern by replacement in the corresponding element of list.

For example,

(subst "a", "A", "java" "class")
produces the value "jAvA" "clAss".

(patsubst pattern, replacement, list)
The function patsubst produces a list of the same length as list. Each string in list that matches pattern is replaced with replacement. The pattern may contain a percent sign which acts as a wild card, matching any number of any characters within a string. A pattern without the percent sign behaves as if it starts with a percent sign.

If replacement contains any number of percent signs, each is replaced by the text that matched the percent sign in pattern. A replacement without the percent sign behaves as if it starts with a percent sign.

For example,

(patsubst ".c", ".o", "a.c" "b.c")
produces the value "a.o" "b.o".

(cat list)
The cat function concatenates all the strings in list. When there are no strings in list, it returns a list with a single null string.

For example,

(cat "a/b" ":" "c/d" ":" "e/f")
produces the value "a/b:c/d:e/f".

(glob list)
The glob function expands file names that contain the wild card character asterisk. Each string in list is translated into a file name which uses the host's separators. All file separators in the file name must precede every wild card. A file name with wild cards is expanded into a list of file names from the given directory that match the pattern. A file name is added to the output after replacing the host's separator characters by the generic ones.

For example, if the directory pkg contains two files, "A.java" and "B.java",

(glob "pkg/*.java")
produces the value "pkg/A.java" "pkg/B.java".

(dirs list)
The function dirs produces a list of all directories in the files named in list.

For example, if the directory a contains b and c, and c contains directory d,

(dirs "a")
produces the value "a" "a/b" "a/c" "a/c/d".

(getprop list)
The function getprop uses each string in list as a key into the system properties. If the system has a property associated with the key, that string is added to the output after replacing the host's separator characters with the generic ones.

For example,

(getprop "user.dir")
produces the user's current directory using the generic separators.

(join prefixes, suffixes)
The function join concatenates each string in prefixes with every string in suffixes.

For example,

(join "A" "B", ".java" ".class")
produces "A.java" "A.class" "B.java" "B.class".

(equal list1, list2)
The function equal returns a list containing the string "true" if list1 and list2 are the same, otherwise, it returns the empty list. This function is most often used in the test of a conditional statement or a conditional expression.

For example,

(equal "A" "B", "A" "B")
produces "true".

(first list)
The function first returns the first string in list or the empty list if list is empty.

For example,

(first "A" "B" "C")
produces "A".

(rest list)
The function rest returns all but the first string in list or the empty list if list is empty.

For example,

(rest "A" "B" "C")
produces "B" "C".

(load class)
The function load dynamically loads a class that implements the Function interface, and returns a new instance of the class.
package edu.neu.ccs.jmk;
public interface Function
extends Value
{
  String getName();

  /**
   * Invoke this function.
   * @param args parameters to the function
   * @param list a string list 
   * @return result of invocation appended to the list
   * (if list is non-null, result must be a string list)
   * @exception Exception if invocation failed
   */
  Value invoke(Value[] args, StringList list) throws Exception;
}

An example of a loadable function is in edu.neu.ccs.jmk.ReverseFunction.

Commands

Commands are executed when a target is inconsistent. A command is an operator followed by a semicolon terminated expression sequence. When the command is executed, the operands of the command are produced by evaluating the expression sequence, which must produce a list of strings. The evaluation occurs in an environment in which the following variables are bound.

@
The @ variable is bound to the target of the current rule.
<
The < variable is bound to the first prerequisite of the rule if there is one, otherwise, it is bound to the null list.
?
The ? variable is bound to the list of prerequisites that are out of date with respect to current target.
%
In rules that receive their commands from a rule pattern, the % variable is bound to the characters in the target that match the wild card in the rule pattern's target. Otherwise, the % variable is bound to the null list.

The generic separators in each operand are replaced by the host's separators before they are given to the operator.

The operator determines the method invoked on the operands used to implement a command.

exec
The exec operator executes a command in a separate process using a Java runtime exec method.
delete
The delete file operator deletes the named files.
delall
The delall file operator deletes the named files. If the file names a directory, it recusively deletes its contents.
mkdir
The mkdir file operator creates the named directories.
mkdirs
The mkdirs file operator creates the named directories, including any necessary parent directories.
copy
The copy file operator copies a file.
rename
The rename file operator renames a file.
create
The create operator creates a file given by its first operand and than writes any remaining operands into the file as separate lines of text.
note
The note operator does nothing. It is used to print notes during a make run.
forname
The forname operator dynamically loads and executes a Java object provided with the makefile. The first operand must name a class that implements the Operator interface given below.
package edu.neu.ccs.jmk;
public interface Operator
{
  String getName();

  /**
   * Execute the operation specified by the operator.
   * @param args parameters to the operation
   * @param out place to write messages
   * @exception CommandFailedException if operation failed
   */
  void exec(String[] args, java.io.PrintWriter out)
       throws CommandFailedException;
}

An example of a dynamically loadable operator is in edu.neu.ccs.jmk.ReverseOperator.

The class that starts jmk is edu.neu.ccs.jmk.Make. It implements the Operator interface allowing the invocation of jmk from commands in a makefile.

make = "edu.neu.ccs.jmk.Make";

"all":;
{
  forname make "-d" "-f" "edu/neu/ccs/jmk/tester.jmk";
}

The five file operators expand a file name with asterisks into a list of file names from the given directory that match the pattern. The restrictions on the pattern are the same as the ones for the glob function. The expansion occurs when the command executes.

If the operator of a command is preceded by the '-' character, errors that occur as the command executes are ignored.

Rule Patterns

A target with no commands explicitly given in the makefile may infer commands from rule patterns. Syntactically, a rule pattern is the same as a rule except that the target contains one occurrence of the wild card character, the percent sign. Prerequisites may also contain a wild card character. A rule pattern for Java classes follows.

"%.class": "%.java"; {
  exec "javac" "-g" <;
}

The list of rule patterns are searched in reverse textual order after a makefile has been read. A rule pattern is applicable to a target if the pattern's target is equal to the target after a matching string is substituted for the wild card character. The match is substituted for the wild card characters in the pattern's prerequisites and the prerequisites are added to the front of the target's list of prerequisites. The match is also the value of the percent sign variable in commands inferred by the pattern.

Special Targets

A makefile rule with the target ".PHONY" declares that its prerequisites are not the names of files. When one of the prerequisites appears as a target, the interpretation of its associated rule always assumes the target does not exist.

File Inclusion

The include statement tells jmk to suspend reading the current makefile and read one or more other makefiles before continuing. The statement is a line in the makefile that looks like this:

include List ;

where List evaluates to a list of strings, each is used as a file name.

Conditionals

A conditional statement causes part of a makefile to be obeyed or ignored depending on the value of a list of strings. An empty list counts as false, and a nonempty list of strings counts as true. The syntax is the traditional if-then-else-end syntax, where the else part is optional:

if List then Statements end

or

if List then Statements else Statements end

For example,

if (equal "Solaris", (getprop "os.name")) then
  include "sun.jmk";
end

includes the file "sun.jmk" when running on a Solaris machine.

Expressions

An expression evaluates to a value or fails to terminate. A value is either a list of strings or a function.

There are three types of expressions. An expression is either an item, a list, or a block. A list is a sequence of zero or more items. If a list contains no items, it evaluates to the empty list of strings. If the list contains one item, the value of the list is the value of the item. Otherwise, each item in the list must produce a list of strings, all of which are concatenated to produce the value of the list.

Blocks are described later, and the various categories of items are described next. In what follows, Item stands for an expression that is an item, List for a list, and Identifier stands for a Java Identifier or one of the four characters @, <, ?, or %. The pattern Thing... indicates zero or more Things, and Thing,,, indicates one or more Things separated by commas.

String Constant

Syntax: " Characters... "

A string constant evaluates to a list of strings of length one.

Variable Reference

Syntax: Identifier

The language of expressions is lexically scoped. There are two types of variables, lexical and global.

Every lexical variable is introduced by a binding construct, which is either a block expression, a function constant, or a do loop. A binding construct binds lexical variables to values. A reference to the variable evaluates to the value.

Each global variable is bound to a location. An assignment statement overwrites the value in the variable's location. A reference to a global variable evaluates to the value in its location. The location of a variable which is not predefined initially contains the empty list of strings.

Function Call

Syntax: (Item List,,,)

The operands in List,,, are evaluated in order, and the operator Item is evaluated. The operator must evaluate to a function which is invoked with the values of the operands.

Function Constant

Syntax: function (Identifier,,,) Block end

A function constant evaluates to a function. When the value is invoked with some arguments, the lexical variables named by Identifier,,, are bound to the arguments and the block is evaluated.

Block

Syntax: { Block }
where Block -> Assignment... List

A block is a sequence of zero or more lexical assignments followed by a list. When the block is a list, the block evaluates to the value of the list. Otherwise the block has the form

Identifier = List ; Block

where Identifier names a lexical variable. When List is not a function constant, the value is equivalent to evaluating List, binding the variable to value of the list, and then evaluating Block.

For example,

z = "a"; z z

Evaluates to "a" "a".

When List is a function constant, the scope of the variable includes List in addition to Block.

For example,

g = function(x, y) 
      if y then
        (g x y,) 
      else 
        x
      end 
    end;
(g "a", "b")

Evaluates to "a" "b".

Conditional Expression

Syntax: if List then Block1 else Block2 end

If List evaluates to a nonempty list of strings, the conditional evaluates to the value of Block1. If List evaluates to the empty list strings, the conditional evaluates to the value of Block2.

For example, to compute the packages in directories under edu that contain source files, invoke find_pkgs defined as follows.

# Predicate for filter
dir_has_src = function(dir) 
  (glob (cat dir "/*.java")) 
end;

# Filter out elements of list 
# that do not satisfy pred
filter = function(pred, list)
  if list then
    if (pred (first list)) then
      (first list) (filter pred, (rest list))
    else
      (filter pred, (rest list))
    end
  else
  end
end;

# (find_pkgs dir_list) returns the packages in
# the directory list by looking for source files

# Get directories that contain Java sources
# and convert them into package names
find_pkgs = function(dir_list) 
  (subst "/", ".", 
    (filter dir_has_src, (dirs dir_list)))
end;

pkgs_in_dir = (find_pkgs "edu");

Do Loop

Syntax: do Identifier0 (Initializer,,,) Block end
where Initializer -> Identifier1 = List1

A do loop is an abbreviation for

{ Identifier0 = function(Identifier1,,,) Block end; (Identifier0 List1,,,) }

For example,

do h(x = "a", y = "b")
  if y then 
    (h x y,) 
  else
    x
  end 
end

Evaluates to "a" "b".

Example

# $Id: jmk.html,v 1.4 2002/02/08 14:51:21 ramsdell Exp $

# To make the system, run the command:
# $ java -jar jmk.jar
# in the root directory.
# jmk can be found at http://jmk.sourceforge.net.

# Other make targets: all clean doc

# Edit the following three assignments to fit your system.
# Names to invoke Sun's tools:
javac = "javac";
jar = "jar";
javadoc = "javadoc";

# javac flags
classpath = "." ;
javaflags = "-O" "-deprecation"
	    "-sourcepath" "src" 
            "-classpath" classpath
            "-d" ".";

# sources file names
srcs = (glob (join (dirs "src"), "/*.java"));
resource = "org/mitre/treemap/treemap.properties";
src_resource = (cat "src/" resource);
files = "makefile.jmk" "dutmv.html" "license.txt" "dutmv.sh" "pack.sh";
target = "dutmv.jar";
mf="src/dutmv.mf";
timestamp = "timestamp";

"all": timestamp target;

timestamp: srcs; {
  exec javac javaflags ?;
  create timestamp;
}

resource: src_resource; {
  copy src_resource resource;
}

target: srcs resource files; {
  exec jar "cfm" @ mf "org" "src" files;
}

"clean": ; {
  delall timestamp "org" target;
}

"api": ; {
  mkdir @;
}

"doc": "api"; {
  exec javadoc 
       "-sourcepath" "src" 
       "-classpath" classpath
       "-d" "api" 
       (patsubst "src.%", "%", (find_pkgs "src"));
}

# For find_pkgs

# Predicate for filter
dir_has_src = function(dir) 
  (glob (cat dir "/*.java")) 
end;

# Filter out elements of list 
# that do not satisfy pred
filter = function(pred, list)
  if list then
    if (pred (first list)) then
      (first list) (filter pred, (rest list))
    else
      (filter pred, (rest list))
    end
  else
  end
end;

# (find_pkgs dir_list) returns the packages in
# the directory list by looking for source files

# Get directories that contain Java sources
# and convert them into package names
find_pkgs = function(dir_list) 
  (subst "/", ".", 
    (filter dir_has_src, (dirs dir_list)))
end;

".PHONY": "all" "clean" "doc";

The Makefile used by jmk

# A makefile file for jmk -- Make in Java

javaflags = "-O";

# jmk is in one package.
pkg = "edu.neu.ccs.jmk";

srcdir = (subst ".", "/", pkg);
# srcs contains all the Java source files.
srcs = (glob (join srcdir, "/*.java"));

jar = "jmk.jar";

"all":	jar;    # all is the default target.

# The following rule only compiles out of date 
# source files.  Sometimes the clean target
# must be used to force the compilation of the 
# entire system, such as when an inherited class
# is changed.
jar:	srcs;
{
  exec "javac" javaflags ?;
  exec "jar" "cf" @ "edu";
}

# doc is the target for running javadoc.
"doc": "docs" "docs/index.html";

"docs":;
{
  mkdir @;
}

"docs/index.html": srcs;
{
  exec "javadoc" "-package" "-d" "docs" pkg;
}

"clean":;
{
  delete (join srcdir, "/*.class") jar;
}

".PHONY": "clean" "doc" "all";

Grammar

The makefile loader expects input to conform to the grammar given below. Comments start with the sharp sign character (#) and continue to the end of the line. In the grammar, syntactic categories begin with an uppercase letter. The pattern Thing... indicates zero or more Things, Thing,,, indicates one or more Things separated by commas, and vertical bar indicates alternatives.

Makefile -> Statement Statement...
Statement -> Assignment | Rule | Inclusion | Conditional
Assignment -> Identifier = List ;
List -> Item...
Item -> String | Identifier | (Item List,,,) | { Block } | if List then Block else Block end | function (Identifier,,,) Block end | do Identifier (Initializer,,,) Block end
Block -> Assignment... List
Initializer -> Identifier = List
Rule -> Item List : List ; Commands
Commands -> | { Command... }
Command -> Ignore Operator List ;
Ignore -> | -
Operator -> exec | delete | delall | mkdir | mkdirs | copy | rename | create | note | forname
Inclusion -> include List ;
Conditional -> if List then Statement... Alternative end
Alternative -> | else Statement...
String -> " Characters... "
Identifier -> JavaIdentifier | @ | < | ? | %

The JavaIdentifiers that are reserved and not available as Identifiers are include, if, then, else, end, function, and do.

OPTIONS

-f filename
Use filename for the makefile. The default is makefile.jmk.
-d
Print additional information during a jmk run.
-n
Print but do not run commands during a jmk run.
-w
Use an AWT window for jmk output.
-s
Use a Swing window for jmk output.
-v
Print the version number.

PROPERTIES

Window properties are set by placing the file jmk.properties in the class path. The following properties can be set in the properties file.

jmk.columns: columns
Create a transcript window that displays columns characters on a line of text.
jmk.rows: rows
Create a transcript window that displays rows lines of text.
jmk.font: font
Use font as the default font.

ACKNOWLEDGMENTS

Make in Java was inspired by and is loosely based on the Unix make utility. Thanks go to Richard Stallman and Roland McGrath for making the sources and documentation for GNU make available to all.

REFERENCES

[POSIX92]
IEEE Computer Society. IEEE Standard for Information Technology--Portable Operating System Interface (POSIX)--Part 2: Shell and Utilities, volume 1. Institute of Electrical and Electronics Engineers, Inc., New York, NY, USA, 1992. Std 1003.2-1992. pp. 666-679.
[StallmanMcGrath96]
Richard M. Stallman and Roland McGrath. GNU Make. Free Software Foundation, 0.51 edition, 1996.

LICENSE

The jmk program including its sources and documentation is covered by the GNU Lesser General Public License.

AUTHOR

John D. Ramsdell

[Top]