2.8 Inference Rules

Inference rules generalize the build process, which eliminates the need to give omake an explicit rule for each target. For example, compiling C source (.c files) into object files (.obj files) is a common occurrence. Rather than require a statement that each .obj file depends on a like-named .c file, omake uses an inference rule to infer that dependency. The dependency determined by an inference rule is called the inferred dependency.

Inference rules also provide build scripts to update the target from the inferred dependency. The target inherits these build scripts if it does not have its own.

omake predefines several inference rules, and you can change their definitions or define your own rules.

Defining Inference Rules

Inference rules (also called metarules) are identified by the use of the rule character (%) in the dependency line. This character is a wildcard, matching zero or more characters. For example, here is an inference rule for building .obj files from .c files:

%.obj : %.c
$(CC) $(CFLAGS) -c $(.SOURCE)

This rule states that a .obj file can be built from a corresponding .c file with the build-script line $(CC) $(CFLAGS) -c $(.SOURCE). The .c and .obj files share the same root of the file name.

When the dependency and target have the same file name except for their extensions, this rule can be specified in an alternative way:

.c.obj :
$(CC) $(CFLAGS) -c $(.SOURCE)

The alternative form is compatible with other make utilities.

omake predefines the %.obj : %.c inference rule as listed above so the example now becomes much simpler:

OBJS = main.obj io.obj
CC = cc
CFLAGS = /Zi

project.exe : $(OBJS)
link /out:$(.TARGET) $(OBJS)

The General Inference Rule Definition

[ Tp ] % [ Ts ] [ attribute ... ] : [ Sp ] % [ Ss ]
build script
.
.
.

On the target side of the dependency line is a pattern: a target prefix Tp, a %, and a target suffix Ts. Any target attributes (see Target Attributes) appear next. On the dependency side of the line is a dependency prefix Sp, a %, and a dependency suffix Ss. The prefixes can contain any character except % and, in particular, can be a directory. The suffixes can contain any character (including %, which is taken literally).

In order for a rule to match a target, Tp and Ts must match the first and last parts of the target name, with % matching everything in between. The inferred dependency name is Sp followed by the characters matched by %, followed by Ss.

The Target Inherits Build Scripts and Attributes

Following the dependency line are the build-script lines that update the target from the inferred dependency. If a target doesn't have its own build scripts, it inherits the build scripts and attributes of the inference rule. Target attributes take precedence over rule attributes.

Alternative (Suffix-Only) Form

Inference rules can also be defined in this form:

.source_extension.target_extension :
build script
.
.
.

The source_extension is the extension of the dependency. The target_extension is the extension of the target. This alternative form is compatible with other make utilities and is discussed in Compatibility with Suffix Rules (.SUFFIXES). The suffix-only form is converted by omake to the equivalent metarule form:

%.target_extension : %.source_extension
build script
.
.
.

Automatic Use of Inference Rules

omake uses inference rules when building a target that has no build scripts. Even when targets have build scripts, you can cause omake to use inference rules by giving the target the .INFER target attribute.

In either case, omake first builds the target's explicit dependencies (those listed on dependency lines), and then uses its inference rules to search for an inferred dependency. The search proceeds by finding all rules that match the target, building each possible inferred dependency name in turn, and checking whether the inferred dependency exists as a file. If it exists, omake proceeds as follows:

  1. If the target has no build scripts, the target inherits the inference-rule build scripts and attributes. When this is a conflict between the attributes, the target's attributes take precedence.

  2. The inferred dependency is added to the target as a dependency.

  3. The inferred dependency is built.

Inference Rules and Target Groups

Inference rules can build several targets (a target group) from a single dependency. To do this put the targets on the target side of the rule, with a plus sign (+) between them. (There must be white space between the + and the target names.) When omake executes the rule's build scripts to update any target in the group, all targets are updated.

For example, here is a rule for building both .c and .h files with a yacc program, which takes a .y file as input:

%.h + %.c : %.y
$(YACC) -d $(YFLAGS) $(.SOURCE)
copy y.tab.h $(.TARGET,1)
copy y.tab.c $(.TARGET,2)
del ytab.*

Note the use of $(.TARGET,num), which evaluates to the names of both the .h and .c files. The num macro modifier selects the numth element of the macro. So, for example, $(.TARGET,1) is parse.h when the inferred dependency is parse.y.

Multiple-Step Inference Rules

When omake tries to find the inferred dependency of a target, it first tries all rules that directly produce the target. If the inferred dependency cannot be found with one rule, omake chains rules into twos, threes, and so on. omake always chooses the smallest number of rules (the shortest path) between a target and its inferred dependency.

If a multiple-rule path is found, omake creates the targets between the inferred dependency and the target, and chains them. Chained targets have special properties:

NOTE: Although the characters matched by % are the same within a single inference rule, the % can match different characters for the different rules of a multiple-rule path.

Chained Targets Are Deleted Automatically

To illustrate how the deletion of chained targets works, here are rules for extracting a .obj file from a .c file and for compiling the .obj file into a .exe file:

%.obj : %.c
$(CC) $(CFLAGS) -c $(.SOURCE)

%.exe : %.obj
$(LINK) /out:$(.TARGET) $(LINKFLAGS) $(.SOURCE)

Assume that omake is trying to build main.exe and that main.c exists, but main.obj does not. omake finds the two-rule inference that uses the %.obj : %.c rule to produce main.obj, and the %.exe : %.obj rule to produce main.exe. omake retrieves main.c and compiles it to produce main.obj. It then links main.obj to produce main.exe. After running the %.exe and %.obj build-script lines, omake deletes main.obj because it was marked as being chained.

To prevent this deletion, write a rule that combines the rules in the multiple-rule path but that has a shorter path. A one-step rule between main.exe and main.c that leaves behind main.obj is

%.exe : %.c
$(CC) $(CFLAGS) -c $(.SOURCE)
$(LINK) /out:$(.TARGET) $(LINKFLAGS) $(.SOURCE)

A second way to prevent this deletion is to use the .PRECIOUS attribute. omake does not delete targets with this attribute. Modify the %.obj : %.c rule as follows:

%.obj .PRECIOUS : %.c
$(CC) $(CFLAGS) -c $(.SOURCE)

Preventing Multiple-Step Rules

A target with the .NOCHAIN attribute instructs omake to try only the one-step paths for finding the inferred dependency.

A rule with the .NOCHAIN attribute cannot be used in the middle of a multiple-step rule. That is, it means this rule is terminal.

Inference Rule Search Order

To determine which inference rules to use, omake gathers all rules that can build the current target and sorts them from best score to worst score. For each rule in this sorted list, omake constructs the dependency name and tests whether it exists as a file. If so, omake chooses this rule as the inference rule, and this file as the inferred dependency.

The score is defined as the number of characters in the target name that % matches; the best score is zero. Often, many rules may have the same score (for example, all rules that build %.obj targets), and omake puts these rules in creation order. For identical scoring rules, the rule created first (usually built in to omake or in make.ini) is first on the list.

Overriding the Rule Ordering

Sometimes the rule ordering is inappropriate. For example, you may have both video.c and video.cpp that can be used to build video.obj, and you want omake to use video.c. By making video.obj depend on video.c, omake chooses the %.obj : %.c rule to update video.obj:

video.obj : video.c

If you find yourself doing this, you can prevent omake from doing the inference rule search by supplying your own build scripts. For example:

video.obj : video.c
$(CC) $(CFLAGS) -c $(.SOURCE)

One problem with supplying the build scripts explicitly is that they must change if the %.obj : %.c rule changes. To avoid this problem, use the %do directive to execute the build scripts of the %.obj : %.c rule:

video.obj : video.c
%do "%.obj : %.c"

Rule Finding for Target Names with a Directory Component

When the target name has a directory component (for example, objs\video.obj), omake uses the following methods to find a rule:

  1. The rules with directory components in their targets are matched against the complete target name.

  2. If no rules match in Step #1, the rules without directory components in their targets are matched against the file name of the target, by default. The .UNIXPATHS directive changes this behavior. When you use .UNIXPATHS, the rules without directory components are matched against the full target name.

Common Operations on Inference Rules

These operations on inference rules are performed most often:

Built-In Inference Rules

omake predefines several rules. You can reject them by using the -r (reject rules) command-line option or the .REJECT_RULES directive. The built-in rules are listed in Inference Rules.

Compatibility with Suffix Rules (.SUFFIXES)

Many make utilities, such as PM/CB, NMAKE, and Borland Make, have a simple style of inference rule, called suffix rules. These rules use only the suffix (extension) of the file name. Here is a suffix rule:

.c.obj :
$(CC) $(CFLAGS) -c $<

Some make utilities also use a .SUFFIXES directive to control the search order of the suffix rules. The .SUFFIXES directive lists the suffixes in the order that inference rules are to be attempted. Each appearance of .SUFFIXES prepends to the current list of suffixes, but a .SUFFIXES directive with nothing on the dependency side clears the list:

.SUFFIXES :

(clears list)

.SUFFIXES : .lib

(list is: .for .lib)

.SUFFIXES : .exe .obj

(list is: .exe .obj .lib)

When looking for an inference rule to build a target, the list is traversed from left to right with each extension being combined with the target extension to form the name of a suffix rule. If that suffix rule exists, omake forms the name of the corresponding inferred dependency and, if it exists, the inference search is complete.

Handling of Suffix Rules and .SUFFIXES

omake converts suffix rules into equivalent omake inference rules. If .SUFFIXES is used, omake uses the suffixes order to sequence only its suffix rules. Nonsuffix rules are left in creation order. By default, all inference rules are in creation order. Note that a .SUFFIXES directive with no suffixes on the dependency side effectively disables all suffix rules, but nonsuffix rules remain enabled.