Jump to:  OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long)
Index:  All • Variables • Functions • Objects • Targets • Options

The OMake user guide and reference manual

Jason Hickey, Aleksey Nogin, et. al.

20thAugust, 2007

All the documentation on a single page

OMake table of contents

Contents

Chapter 1  Guide

If you are new to OMake, you the omake-quickstart presents a short introduction that describes how to set up a project. The omake-build-examples gives larger examples of build projects, and omake-language-examples presents programming examples.

Quickstart 2
A quickstart guide to using omake.
Build examples 3
Advanced build examples.
The OMake language 4
The omake language, including a description of objects, expressions, and values.
Variables and naming 5
Variables, names, and environments.
Language discussion 6
Further discussion on the language, including scoping, evaluation, and objects.
Language examples 7
Additional language examples.
Build rules 8
Defining and using rules to build programs.
Base builtin functions 9
Functions and variables in the core standard library.
System functions 10
Functions on files, input/output, and system commands.
Shell commands 11
Using the omake shell for command-line interpretation.
The standard objects 12
Pervasives defines the built-in objects.
Standard build definitions 13
The build specifications for programming languages in the OMake standard library.
Standard autoconfiguration functions and variables 14
The utilities provoded by the OMake standard library to simplify programming of autoconfiguration tests.
The interactive command interpreter 15
The osh command-line interpreter.
Appendices
OMake command-line options A
Command-line options for omake.
The OMake language grammar B
A more precise specification of the OMake language.
All the documentation on a single page
All the OMake documentation in a single page.

Chapter 2  OMake quickstart guide

2.1  Description

omake is designed for building projects that might have source files in several directories. Projects are normally specified using an OMakefile in each of the project directories, and an OMakeroot file in the root directory of the project. The OMakeroot file specifies general build rules, and the OMakefiles specify the build parameters specific to each of the subdirectories. When omake runs, it walks the configuration tree, evaluating rules from all of the OMakefiles. The project is then built from the entire collection of build rules.

2.1.1  Automatic dependency analysis

Dependency analysis has always been problematic with the make(1) program. omake addresses this by adding the .SCANNER target, which specifies a command to produce dependencies. For example, the following rule

    .SCANNER: %.o: %.c
        $(CC) $(INCLUDE) -MM $<

is the standard way to generate dependencies for .c files. omake will automatically run the scanner when it needs to determine dependencies for a file.

2.1.2  Content-based dependency analysis

Dependency analysis in omake uses MD5 digests to determine whether files have changed. After each run, omake stores the dependency information in a file called .omakedb in the project root directory. When a rule is considered for execution, the command is not executed if the target, dependencies, and command sequence are unchanged since the last run of omake. As an optimization, omake does not recompute the digest for a file that has an unchanged modification time, size, and inode number.

2.2  For users already familiar with make

For users already familiar with the make(1) command, here is a list of differences to keep in mind when using omake.

2.3  Building a small C program

To start a new project, the easiest method is to change directories to the project root and use the command omake --install to install default OMakefiles.

    $ cd ~/newproject
    $ omake --install
    *** omake: creating OMakeroot
    *** omake: creating OMakefile
    *** omake: project files OMakefile and OMakeroot have been installed
    *** omake: you should edit these files before continuing

The default OMakefile contains sections for building C and OCaml programs. For now, we'll build a simple C project.

Suppose we have a C file called hello_code.c containing the following code:

    #include <stdio.h>

    int main(int argc, char **argv)
    {
        printf("Hello world\n");
        return 0;
    }

To build the program a program hello from this file, we can use the CProgram function. The OMakefile contains just one line that specifies that the program hello is to be built from the source code in the hello_code.c file (note that file suffixes are not passed to these functions).

    CProgram(hello, hello_code)

Now we can run omake to build the project. Note that the first time we run omake, it both scans the hello_code.c file for dependencies, and compiles it using the cc compiler. The status line printed at the end indicates how many files were scanned, how many were built, and how many MD5 digests were computed.

    $ omake hello
    *** omake: reading OMakefiles
    *** omake: finished reading OMakefiles (0.0 sec)
    - scan . hello_code.o
    + cc -I. -MM hello_code.c
    - build . hello_code.o
    + cc -I. -c -o hello_code.o hello_code.c
    - build . hello
    + cc -o hello hello_code.o
    *** omake: done (0.5 sec, 1/6 scans, 2/6 rules, 5/22 digests)
    $ omake
    *** omake: reading OMakefiles
    *** omake: finished reading OMakefiles (0.1 sec)
    *** omake: done (0.1 sec, 0/4 scans, 0/4 rules, 0/9 digests)

If we want to change the compile options, we can redefine the CC and CFLAGS variables before the CProgram line. In this example, we will use the gcc compiler with the -g option. In addition, we will specify a .DEFAULT target to be built by default. The EXE variable is defined to be .exe on Win32 systems; it is empty otherwise.

    CC = gcc
    CFLAGS += -g
    CProgram(hello, hello_code)
    .DEFAULT: hello$(EXE)

Here is the corresponding run for omake.

    $ omake
    *** omake: reading OMakefiles
    *** omake: finished reading OMakefiles (0.0 sec)
    - scan . hello_code.o
    + gcc -g -I. -MM hello_code.c
    - build . hello_code.o
    + gcc -g -I. -c -o hello_code.o hello_code.c
    - build . hello
    + gcc -g -o hello hello_code.o
    *** omake: done (0.4 sec, 1/7 scans, 2/7 rules, 3/22 digests)

We can, of course, include multiple files in the program. Suppose we write a new file hello_helper.c. We would include this in the project as follows.

    CC = gcc
    CFLAGS += -g
    CProgram(hello, hello_code hello_helper)
    .DEFAULT: hello$(EXE)

2.4  Larger projects

As the project grows it is likely that we will want to build libraries of code. Libraries can be built using the StaticCLibrary function. Here is an example of an OMakefile with two libraries.

    CC = gcc
    CFLAGS += -g

    FOO_FILES = foo_a foo_b
    BAR_FILES = bar_a bar_b bar_c

    StaticCLibrary(libfoo, $(FOO_FILES))
    StaticCLibrary(libbar, $(BAR_FILES))

    # The hello program is linked with both libraries
    LIBS = libfoo libbar
    CProgram(hello, hello_code hello_helper)

    .DEFAULT: hello$(EXE)

2.5  Subdirectories

As the project grows even further, it is a good idea to split it into several directories. Suppose we place the libfoo and libbar into subdirectories.

In each subdirectory, we define an OMakefile for that directory. For example, here is an example OMakefile for the foo subdirectory.

    INCLUDES += .. ../bar

    FOO_FILES = foo_a foo_b
    StaticCLibrary(libfoo, $(FOO_FILES))

Note the the INCLUDES variable is defined to include the other directories in the project.

Now, the next step is to link the subdirectories into the main project. The project OMakefile should be modified to include a .SUBDIRS: target.

    # Project configuration
    CC = gcc
    CFLAGS += -g

    # Subdirectories
    .SUBDIRS: foo bar

    # The libraries are now in subdirectories
    LIBS = foo/libfoo bar/libbar

    CProgram(hello, hello_code hello_helper)

    .DEFAULT: hello$(EXE)

Note that the variables CC and CFLAGS are defined before the .SUBDIRS target. These variables remain defined in the subdirectories, so that libfoo and libbar use gcc -g.

If the two directories are to be configured differently, we have two choices. The OMakefile in each subdirectory can be modified with its configuration (this is how it would normally be done). Alternatively, we can also place the change in the root OMakefile.

    # Default project configuration
    CC = gcc
    CFLAGS += -g

    # libfoo uses the default configuration
    .SUBDIRS: foo

    # libbar uses the optimizing compiler
    CFLAGS += -O3
    .SUBDIRS: bar

    # Main program
    LIBS = foo/libfoo bar/libbar
    CProgram(hello, hello_code hello_helper)

    .DEFAULT: hello$(EXE)

Note that the way we have specified it, the CFLAGS variable also contains the -O3 option for the CProgram, and hello_code.c and hello_helper.c file will both be compiled with the -O3 option. If we want to make the change truly local to libbar, we can put the bar subdirectory in its own scope using the section form.

    # Default project configuration
    CC = gcc
    CFLAGS += -g

    # libfoo uses the default configuration
    .SUBDIRS: foo

    # libbar uses the optimizing compiler
    section
        CFLAGS += -O3
        .SUBDIRS: bar

    # Main program does not use the optimizing compiler
    LIBS = foo/libfoo bar/libbar
    CProgram(hello, hello_code hello_helper)

    .DEFAULT: hello$(EXE)

Later, suppose we decide to port this project to Win32, and we discover that we need different compiler flags and an additional library.

    # Default project configuration
    if $(equal $(OSTYPE), Win32)
        CC = cl /nologo
        CFLAGS += /DWIN32 /MT
        export
    else
        CC = gcc
        CFLAGS += -g
        export

    # libfoo uses the default configuration
    .SUBDIRS: foo

    # libbar uses the optimizing compiler
    section
        CFLAGS += $(if $(equal $(OSTYPE), Win32), $(EMPTY), -O3)
        .SUBDIRS: bar

    # Default libraries
    LIBS = foo/libfoo bar/libbar

    # We need libwin32 only on Win32
    if $(equal $(OSTYPE), Win32)
       LIBS += win32/libwin32

       .SUBDIRS: win32
       export

    # Main program does not use the optimizing compiler
    CProgram(hello, hello_code hello_helper)

    .DEFAULT: hello$(EXE)

Note the use of the export directives to export the variable definitions from the if-statements. Variables in omake are scoped—variables in nested blocks (blocks with greater indentation), are not normally defined in outer blocks. The export directive specifies that the variable definitions in the nested blocks should be exported to their parent block.

Finally, for this example, we decide to copy all libraries into a common lib directory. We first define a directory variable, and replace occurrences of the lib string with the variable.

    # The common lib directory
    LIB = $(dir lib)

    # phony target to build just the libraries
    .PHONY: makelibs

    # Default project configuration
    if $(equal $(OSTYPE), Win32)
        CC = cl /nologo
        CFLAGS += /DWIN32 /MT
        export
    else
        CC = gcc
        CFLAGS += -g
        export

    # libfoo uses the default configuration
    .SUBDIRS: foo

    # libbar uses the optimizing compiler
    section
        CFLAGS += $(if $(equal $(OSTYPE), Win32), $(EMPTY), -O3)
        .SUBDIRS: bar

    # Default libraries
    LIBS = $(LIB)/libfoo $(LIB)/libbar

    # We need libwin32 only on Win32
    if $(equal $(OSTYPE), Win32)
       LIBS += $(LIB)/libwin32

       .SUBDIRS: win32
       export

    # Main program does not use the optimizing compiler
    CProgram(hello, hello_code hello_helper)

    .DEFAULT: hello$(EXE)

In each subdirectory, we modify the OMakefiles in the library directories to install them into the $(LIB) directory. Here is the relevant change to foo/OMakefile.

    INCLUDES += .. ../bar

    FOO_FILES = foo_a foo_b
    StaticCLibraryInstall(makelib, $(LIB), libfoo, $(FOO_FILES))

Directory (and file names) evaluate to relative pathnames. Within the foo directory, the $(LIB) variable evaluates to ../lib.

As another example, instead of defining the INCLUDES variable separately in each subdirectory, we can define it in the toplevel as follows.

    INCLUDES = $(ROOT) $(dir foo bar win32)

In the foo directory, the INCLUDES variable will evaluate to the string .. . ../bar ../win32. In the bar directory, it would be .. ../foo . ../win32. In the root directory it would be . foo bar win32.

2.6  Other things to consider

omake also handles recursive subdirectories. For example, suppose the foo directory itself contains several subdirectories. The foo/OMakefile would then contain its own .SUBDIRS target, and each of its subdirectories would contain its own OMakefile.

2.7  Building OCaml programs

By default, omake is also configured with functions for building OCaml programs. The functions for OCaml program use the OCaml prefix. For example, suppose we reconstruct the previous example in OCaml, and we have a file called hello_code.ml that contains the following code.

   open Printf

   let () = printf "Hello world\n"

An example OMakefile for this simple project would contain the following.

    # Use the byte-code compiler
    BYTE_ENABLED = true
    NATIVE_ENABLED = false
    OCAMLCFLAGS += -g

    # Build the program
    OCamlProgram(hello, hello_code)
    .DEFAULT: hello.run

Next, suppose the we have two library subdirectories: the foo subdirectory is written in C, the bar directory is written in OCaml, and we need to use the standard OCaml Unix module.

    # Default project configuration
    if $(equal $(OSTYPE), Win32)
        CC = cl /nologo
        CFLAGS += /DWIN32 /MT
        export
    else
        CC = gcc
        CFLAGS += -g
        export

    # Use the byte-code compiler
    BYTE_ENABLED = true
    NATIVE_ENABLED = false
    OCAMLCFLAGS += -g

    # library subdirectories
    INCLUDES += $(dir foo bar)
    OCAMLINCLUDES += $(dir foo bar)
    .SUBDIRS: foo bar

    # C libraries
    LIBS = foo/libfoo

    # OCaml libraries
    OCAML_LIBS = bar/libbar

    # Also use the Unix module
    OCAML_OTHER_LIBS = unix

    # The main program
    OCamlProgram(hello, hello_code hello_helper)

    .DEFAULT: hello

The foo/OMakefile would be configured as a C library.

    FOO_FILES = foo_a foo_b
    StaticCLibrary(libfoo, $(FOO_FILES))

The bar/OMakefile would build an ML library.

   BAR_FILES = bar_a bar_b bar_c
   OCamlLibrary(libbar, $(BAR_FILES))

2.8  The OMakefile and OMakeroot files

OMake uses the OMakefile and OMakeroot files for configuring a project. The syntax of these files is the same, but their role is slightly different. For one thing, every project must have exactly one OMakeroot file in the project root directory. This file serves to identify the project root, and it contains code that sets up the project. In contrast, a multi-directory project will often have an OMakefile in each of the project subdirectories, specifying how to build the files in that subdirectory.

Normally, the OMakeroot file is boilerplate. The following listing is a typical example.

    include $(STDLIB)/build/Common
    include $(STDLIB)/build/C
    include $(STDLIB)/build/OCaml
    include $(STDLIB)/build/LaTeX

    # Redefine the command-line variables
    DefineCommandVars(.)

    # The current directory is part of the project
    .SUBDIRS: .

The include lines include the standard configuration files needed for the project. The $(STDLIB) represents the omake library directory. The only required configuration file is Common. The others are optional; for example, the $(STDLIB)/build/OCaml file is needed only when the project contains programs written in OCaml.

The DefineCommandVars function defines any variables specified on the command line (as arguments of the form VAR=<value>). The .SUBDIRS line specifies that the current directory is part of the project (so the OMakefile should be read).

Normally, the OMakeroot file should be small and project-independent. Any project-specific configuration should be placed in the OMakefiles of the project.

2.9  Multiple version support

OMake version 0.9.6 introduced preliminary support for multiple, simultaneous versions of a project. Versioning uses the vmount(dir1, dir2) function, which defines a “virtual mount” of directory dir1 over directory dir2. A “virtual mount” is like a transparent mount in Unix, where the files from dir1 appear in the dir2 namespace, but new files are created in dir2. More precisely, the filename dir2/foo refers to: a) the file dir1/foo if it exists, or b) dir2/foo otherwise.

The vmount function makes it easy to specify multiple versions of a project. Suppose we have a project where the source files are in the directory src/, and we want to compile two versions, one with debugging support and one optimized. We create two directories, debug and opt, and mount the src directory over them.

    section
        CFLAGS += -g
        vmount(-l, src, debug)
        .SUBDIRS: debug

    section
        CFLAGS += -O3
        vmount(-l, src, opt)
        .SUBDIRS: opt

Here, we are using section blocks to define the scope of the vmount—you may not need them in your project.

The -l option is optional. It specifies that files form the src directory should be linked into the target directories (or copied, if the system is Win32). The links are added as files are referenced. If no options are given, then files are not copied or linked, but filenames are translated to refer directly to the src/ files.

Now, when a file is referenced in the debug directory, it is linked from the src directory if it exists. For example, when the file debug/OMakefile is read, the src/OMakefile is linked into the debug/ directory.

The vmount model is fairly transparent. The OMakefiles can be written as if referring to files in the src/ directory—they need not be aware of mounting. However, there are a few points to keep in mind.

2.10  Notes

Chapter 3  Additional build examples

Let's explain the OMake build model a bit more. One issue that dominates this discussion is that OMake is based on global project analysis. That means you define a configuration for the entire project, and you run one instance of omake.

For single-directory projects this doesn't mean much. For multi-directory projects it means a lot. With GNU make, you would usually invoke the make program recursively for each directory in the project. For example, suppose you had a project with some project root directory, containing a directory of sources src, which in turn contains subdirectories lib and main. So your project looks like this nice piece of ASCII art.

    my_project/
    |--> Makefile
    `--> src/
         |---> Makefile
         |---> lib/
         |     |---> Makefile
         |     `---> source files...
         `---> main/
               |---> Makefile
               `---> source files...

Typically, with GNU make, you would start an instance of make in my_project/; this would in term start an instance of make in the src/ directory; and this would start new instances in lib/ and main/. Basically, you count up the number of Makefiles in the project, and that is the number of instances of make processes that will be created.

The number of processes is no big deal with today's machines (sometimes contrary the the author's opinion, we no longer live in the 1970s). The problem with the scheme was that each make process had a separate configuration, and it took a lot of work to make sure that everything was consistent. Furthermore, suppose the programmer runs make in the main/ directory, but the lib/ is out-of-date. In this case, make would happily crank away, perhaps trying to rebuild files in lib/, perhaps just giving up.

With OMake this changes entirely. Well, not entirely. The source structure is quite similar, we merely add some Os to the ASCII art.

    my_project/
    |--> OMakeroot   (or Root.om)
    |--> OMakefile
    `--> src/
         |---> OMakefile
         |---> lib/
         |     |---> OMakefile
         |     `---> source files...
         `---> main/
               |---> OMakefile
               `---> source files...

The role of each <dir>/OMakefile plays the same role as each <dir>/Makefile: it describes how to build the source files in <dir>. The OMakefile retains much of syntax and structure of the Makefile, but in most cases it is much simpler.

One minor difference is the presence of the OMakeroot in the project root. The main purpose of this file is to indicate where the project root is in the first place (in case omake is invoked from a subdirectory). The OMakeroot serves as the bootstrap file; omake starts by reading this file first. Otherwise, the syntax and evaluation of OMakeroot is no different from any other OMakefile.

The big difference is that OMake performs a global analysis. Here is what happens when omake starts.

  1. omake locates that OMakeroot file, and reads it.
  2. Each OMakefile points to its subdirectory OMakefiles using the .SUBDIRS target. For example, my_project/OMakefile has a rule,
        .SUBDIRS: src
    

    and the my_project/src/OMakefile has a rule,

        .SUBDIRS: lib main
    

    omake uses these rules to read and evaluate every OMakefile in the project. Reading and evaluation is fast. This part of the process is cheap.

  3. Now that the entire configuration is read, omake determines which files are out-of-date (using a global analysis), and starts the build process. This may take a while, depending on what exactly needs to be done.

There are several advantages to this model. First, since analysis is global, it is much easier to ensure that the build configuration is consistent–after all, there is only one configuration. Another benefit is that the build configuration is inherited, and can be re-used, down the hierarchy. Typically, the root OMakefile defines some standard boilerplate and configuration, and this is inherited by subdirectories that tweak and modify it (but do not need to restate it entirely). The disadvantage of course is space, since this is global analysis after all. In practice rarely seems to be a concern; omake takes up much less space than your web browser even on large projects.

Some notes to the GNU/BSD make user.

3.1  OMakeroot vs. OMakefile

Before we begin with examples, let's ask the first question, “What is the difference between the project root OMakeroot and OMakefile?” A short answer is, there is no difference, but you must have an OMakeroot file (or Root.om file).

However, the normal style is that OMakeroot is boilerplate and is more-or-less the same for all projects. The OMakefile is where you put all your project-specific stuff.

To get started, you don't have to do this yourself. In most cases you just perform the following step in your project root directory.

This will create the initial OMakeroot and OMakefile files that you can edit to get started.

3.2  An example C project

To begin, let's start with a simple example. Let's say that we have a full directory tree, containing the following files.

    my_project/
    |--> OMakeroot
    |--> OMakefile
    `--> src/
         |---> OMakefile
         |---> lib/
         |     |---> OMakefile
         |     |---> ouch.c
         |     |---> ouch.h
         |     `---> bandaid.c
         `---> main/
               |---> OMakefile
               |---> horsefly.c
               |---> horsefly.h
               `---> main.c

Here is an example listing.

my_project/OMakeroot:
    # Include the standard configuration for C applications
    open build/C
    
    # Process the command-line vars
    DefineCommandVars()
    
    # Include the OMakefile in this directory.
    .SUBDIRS: .

my_project/OMakefile:
    # Set up the standard configuration
    CFLAGS += -g

    # Include the src subdirectory
    .SUBDIRS: src

my_project/src/OMakefile:
    # Add any extra options you like
    CFLAGS += -O2

    # Include the subdirectories
    .SUBDIRS: lib main

my_project/src/lib/OMakefile:
    # Build the library as a static library.
    # This builds libbug.a on Unix/OSX, or libbug.lib on Win32.
    # Note that the source files are listed _without_ suffix.
    StaticCLibrary(libbug, ouch bandaid)

my_project/src/main/OMakefile:
    # Some files include the .h files in ../lib
    INCLUDES += ../lib

    # Indicate which libraries we want to link against.
    LIBS[] +=
        ../lib/libbug

    # Build the program.
    # Builds horsefly.exe on Win32, and horsefly on Unix.
    # The first argument is the name of the executable.
    # The second argument is an array of object files (without suffix)
    # that are part of the program.
    CProgram(horsefly, horsefly main)

    # Build the program by default (in case omake is called
    # without any arguments).  EXE is defined as .exe on Win32,
    # otherwise it is empty.
    .DEFAULT: horsefly$(EXE)

Most of the configuration here is defined in the file build/C.om (which is part of the OMake distribution). This file takes care of a lot of work, including:

Variables are inherited down the hierarchy, so for example, the value of CFLAGS in src/main/OMakefile is “-g -O2”.

3.3  An example OCaml project

Let's repeat the example, assuming we are using OCaml instead of C. This time, the directory tree looks like this.

    my_project/
    |--> OMakeroot
    |--> OMakefile
    `--> src/
         |---> OMakefile
         |---> lib/
         |     |---> OMakefile
         |     |---> ouch.ml
         |     |---> ouch.mli
         |     `---> bandaid.ml
         `---> main/
               |---> OMakefile
               |---> horsefly.ml
               |---> horsefly.mli
               `---> main.ml

The listing is only a bit different.

my_project/OMakeroot:
    # Include the standard configuration for OCaml applications
    open build/OCaml
    
    # Process the command-line vars
    DefineCommandVars()
    
    # Include the OMakefile in this directory.
    .SUBDIRS: .

my_project/OMakefile:
    # Set up the standard configuration
    OCAMLFLAGS += -Wa

    # Do we want to use the bytecode compiler,
    # or the native-code one?  Let's use both for
    # this example.
    NATIVE_ENABLED = true
    BYTE_ENABLED = true

    # Include the src subdirectory
    .SUBDIRS: src

my_project/src/OMakefile:
    # Include the subdirectories
    .SUBDIRS: lib main

my_project/src/lib/OMakefile:
    # Let's do aggressive inlining on native code
    OCAMLOPTFLAGS += -inline 10

    # Build the library as a static library.
    # This builds libbug.a on Unix/OSX, or libbug.lib on Win32.
    # Note that the source files are listed _without_ suffix.
    OCamlLibrary(libbug, ouch bandaid)

my_project/src/main/OMakefile:
    # These files depend on the interfaces in ../lib
    OCAMLINCLUDES += ../lib

    # Indicate which libraries we want to link against.
    OCAML_LIBS[] +=
        ../lib/libbug

    # Build the program.
    # Builds horsefly.exe on Win32, and horsefly on Unix.
    # The first argument is the name of the executable.
    # The second argument is an array of object files (without suffix)
    # that are part of the program.
    OCamlProgram(horsefly, horsefly main)

    # Build the program by default (in case omake is called
    # without any arguments).  EXE is defined as .exe on Win32,
    # otherwise it is empty.
    .DEFAULT: horsefly$(EXE)

In this case, most of the configuration here is defined in the file build/OCaml.om. In this particular configuration, files in my_project/src/lib are compiled aggressively with the option -inline 10, but files in my_project/src/lib are compiled normally.

3.4  Handling new languages

The previous two examples seem to be easy enough, but they rely on the OMake standard library (the files build/C and build/OCaml) to do all the work. What happens if we want to write a build configuration for a language that is not already supported in the OMake standard library?

For this example, let's suppose we are adopting a new language. The language uses the standard compile/link model, but is not in the OMake standard library. Specifically, let's say we have the following setup.

To define a build configuration, we have to do three things.

  1. Define a .SCANNER rule for discovering dependency information for the source files.
  2. Define a generic rule for compiling a .cat file to a .woof file.
  3. Define a rule (as a function) for linking .woof files to produce a .dog executable.

Initially, these definitions will be placed in the project root OMakefile.

3.4.1  Defining a default compilation rule

Let's start with part 2, defining a generic compilation rule. We'll define the build rule as an implicit rule. To handle the include path, we'll define a variable CAT_INCLUDES that specifies the include path. This will be an array of directories. To define the options, we'll use a lazy variable (Section 7.5). In case there are any other standard flags, we'll define a CAT_FLAGS variable.

   # Define the catc command, in case we ever want to override it
   CATC = catc

   # The default flags are empty
   CAT_FLAGS =
   
   # The directories in the include path (empty by default)
   INCLUDES[] =

   # Compute the include options from the include path
   PREFIXED_INCLUDES[] = $`(mapprefix -I, $(INCLUDES))

   # The default way to build a .woof file
   %.woof: %.cat
       $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) -c $<

The final part is the build rule itself, where we call the catc compiler with the include path, and the CAT_FLAGS that have been defined. The $< variable represents the source file.

3.4.2  Defining a rule for linking

For linking, we'll define another rule describing how to perform linking. Instead of defining an implicit rule, we'll define a function that describes the linking step. The function will take two arguments; the first is the name of the executable (without suffix), and the second is the files to link (also without suffixes). Here is the code fragment.

    # Optional link options
    CAT_LINK_FLAGS =

    # The function that defines how to build a .dog program
    CatProgram(program, files) =
        # Add the suffixes
        file_names = $(addsuffix .woof, $(files))
        prog_name = $(addsuffix .dog, $(files))

        # The build rule
        $(prog_name): $(file_names)
            $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) $(CAT_LINK_FLAGS) -o $@ $+

        # Return the program name
        value $(prog_name)

The CAT_LINK_FLAGS variable is defined just in case we want to pass additional flags specific to the link step. Now that this function is defined, whenever we want to define a rule for building a program, we simply call the rule. The previous implicit rule specifies how to compile each source file, and the CatProgram function specifies how to build the executable.

    # Build a rover.dog program from the source
    # files neko.cat and chat.cat.
    # Compile it by default.
    .DEFAULT: $(CatProgram rover, neko chat)

3.4.3  Dependency scanning

That's it, almost. The part we left out was automated dependency scanning. This is one of the nicer features of OMake, and one that makes build specifications easier to write and more robust. Strictly speaking, it isn't required, but you definitely want to do it.

The mechanism is to define a .SCANNER rule, which is like a normal rule, but it specifies how to compute dependencies, not the target itself. In this case, we want to define a .SCANNER rule of the following form.

    .SCANNER: %.woof: %.cat
        <commands>

This rule specifies that a .woof file may have additional dependencies that can be extracted from the corresponding .cat file by executing the <commands>. The result of executing the <commands> should be a sequence of dependencies in OMake format, printed to the standard output.

As we mentioned, each .cat file specifies dependencies on .woof files with an open directive. For example, if the neko.cat file contains a line open chat, then neko.woof depends on chat.woof. In this case, the <commands> should print the following line.

    neko.woof: chat.woof

For an analogy that might make this clearer, consider the C programming language, where a .o file is produced by compiling a .c file. If a file foo.c contains a line like #include "fum.h", then foo.c should be recompiled whenever fum.h changes. That is, the file foo.o depends on the file fum.h. In the OMake parlance, this is called an implicit dependency, and the .SCANNER <commands> would print a line like the following.

    foo.o: fum.h

Now, returning to the animal world, to compute the dependencies of neko.woof, we should scan neko.cat, line-by-line, looking for lines of the form open <name>. We could do this by writing a program, but it is easy enough to do it in omake itself. We can use the builtin awk function to scan the source file. One slight complication is that the dependencies depend on the INCLUDE path. We'll use the find-in-path function to find them. Here we go.

    .SCANNER: %.woof: %.cat
        section
            # Scan the file
            deps[] =
            awk($<)
            case $'^open'
                deps[] += $2
                export

            # Remove duplicates, and find the files in the include path
            deps = $(find-in-path $(INCLUDES), $(set $(deps)))

            # Print the dependencies
            println($"$@: $(deps)")

Let's look at the parts. First, the entire body is defined in a section because we are computing it internally, not as a sequence of shell commands.

We use the deps variable to collect all the dependencies. The awk function scans the source file ($<) line-by-line. For lines that match the regular expression ^open (meaning that the line begins with the word open), we add the second word on the line to the deps variable. For example, if the input line is open chat, then we would add the chat string to the deps array. All other lines in the source file are ignored.

Next, the $(set $(deps)) expression removes any duplicate values in the deps array (sorting the array alphabetically in the process). The find-in-path function then finds the actual location of each file in the include path.

The final step is print the result as the string $"$@: $(deps)" The quotations are added to flatten the deps array to a simple string.

3.4.4  Pulling it all together

To complete the example, let's pull it all together into a single project, much like our previous example.

    my_project/
    |--> OMakeroot
    |--> OMakefile
    `--> src/
         |---> OMakefile
         |---> lib/
         |     |---> OMakefile
         |     |---> neko.cat
         |     `---> chat.cat
         `---> main/
               |---> OMakefile
               `---> main.cat

The listing for the entire project is as follows. Here, we also include a function CatLibrary to link several .woof files into a library.

my_project/OMakeroot:
    # Process the command-line vars
    DefineCommandVars()
    
    # Include the OMakefile in this directory.
    .SUBDIRS: .

my_project/OMakefile:
   ########################################################################
   # Standard config for compiling .cat files
   #

   # Define the catc command, in case we ever want to override it
   CATC = catc

   # The default flags are empty
   CAT_FLAGS =
   
   # The directories in the include path (empty by default)
   INCLUDES[] =

   # Compute the include options from the include path
   PREFIXED_INCLUDES[] = $`(mapprefix -I, $(INCLUDES))

   # Dependency scanner for .cat files
   .SCANNER: %.woof: %.cat
        section
            # Scan the file
            deps[] =
            awk($<)
            case $'^open'
                deps[] += $2
                export

            # Remove duplicates, and find the files in the include path
            deps = $(find-in-path $(INCLUDES), $(set $(deps)))

            # Print the dependencies
            println($"$@: $(deps)")

   # The default way to compile a .cat file
   %.woof: %.cat
       $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) -c $<

   # Optional link options
   CAT_LINK_FLAGS =

   # Build a library for several .woof files
   CatLibrary(lib, files) =
       # Add the suffixes
       file_names = $(addsuffix .woof, $(files))
       lib_name = $(addsuffix .woof, $(lib))

       # The build rule
       $(lib_name): $(file_names)
           $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) $(CAT_LINK_FLAGS) -a $@ $+

       # Return the program name
       value $(lib_name)

   # The function that defines how to build a .dog program
   CatProgram(program, files) =
       # Add the suffixes
       file_names = $(addsuffix .woof, $(files))
       prog_name = $(addsuffix .dog, $(program))

       # The build rule
       $(prog_name): $(file_names)
           $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) $(CAT_LINK_FLAGS) -o $@ $+

       # Return the program name
       value $(prog_name)

   ########################################################################
   # Now the program proper
   #

   # Include the src subdirectory
   .SUBDIRS: src

my_project/src/OMakefile:
   .SUBDIRS: lib main

my_project/src/lib/OMakefile:
   CatLibrary(cats, neko chat)

my_project/src/main/OMakefile:
   # Allow includes from the ../lib directory
   INCLUDES[] += ../lib

   # Build the program
   .DEFAULT: $(CatProgram main, main ../cats)

Some notes. The configuration in the project OMakeroot defines the standard configuration, including the dependency scanner, the default rule for compiling source files, and functions for building libraries and programs.

These rules and functions are inherited by subdirectories, so the .SCANNER and build rules are used automatically in each subdirectory, so you don't need to repeat them.

3.4.5  Finishing up

At this point we are done, but there are a few things we can consider.

First, the rules for building cat programs is defined in the project OMakefile. If you had another cat project somewhere, you would need to copy the OMakeroot (and modify it as needed). Instead of that, you should consider moving the configuration to a shared library directory, in a file like Cat.om. That way, instead of copying the code, you could include the shared copy with an OMake command open Cat. The share directory should be added to your OMAKEPATH environment variable to ensure that omake knows how to find it.

Better yet, if you are happy with your work, consider submitting it as a standard configuration (by sending a request to omake@metaprl.org) so that others can make use of it too.

3.5  Collapsing the hierarchy, .SUBDIRS bodies

Some projects have many subdirectories that all have the same configuration. For instance, suppose you have a project with many subdirectories, each containing a set of images that are to be composed into a web page. Apart from the specific images, the configuration of each file is the same.

To make this more concrete, suppose the project has four subdirectories page1, page2, page3, and page4. Each contains two files image1.jpg and image2.jpg that are part of a web page generated by a program genhtml.

Instead of of defining a OMakefile in each directory, we can define it as a body to the .SUBDIRS command.

    .SUBDIRS: page1 page2 page3 page4
        index.html: image1.jpg image2jpg
            genhtml $+ > $@

The body of the .SUBDIRS is interpreted exactly as if it were the OMakefile, and it can contain any of the normal statements. The body is evaluated in the subdirectory for each of the subdirectories. We can see this if we add a statement that prints the current directory ($(CWD)).

    .SUBDIRS: page1 page2 page3 page4
        println($(absname $(CWD)))
        index.html: image1.jpg image2jpg
            genhtml $+ > $@
  # prints
    /home/jyh/.../page1
    /home/jyh/.../page2
    /home/jyh/.../page3
    /home/jyh/.../page4

3.5.1  Using glob patterns

Of course, this specification is quite rigid. In practice, it is likely that each subdirectory will have a different set of images, and all should be included in the web page. One of the easier solutions is to use one of the directory-listing functions, like glob or ls. The glob function takes a shell pattern, and returns an array of file with matching filenames in the current directory.

    .SUBDIRS: page1 page2 page3 page4
        IMAGES = $(glob *.jpg)
        index.html: $(IMAGES)
            genhtml $+ > $@

3.5.2  Simplified sub-configurations

Another option is to add a configuration file in each of the subdirectories that defines directory-specific information. For this example, we might define a file BuildInfo.om in each of the subdirectories that defines a list of images in that directory. The .SUBDIRS line is similar, but we include the BuildInfo file.

    .SUBDIRS: page1 page2 page3 page4
        include BuildInfo   # Defines the IMAGES variable

        index.html: $(IMAGES)
            genhtml $+ > $@

Where we might have the following configurations.

   page1/BuildInfo.om:
       IMAGES[] = image.jpg
   page2/BuildInfo.om:
       IMAGES[] = ../common/header.jpg winlogo.jpg
   page3/BuildInfo.om:
       IMAGES[] = ../common/header.jpg unixlogo.jpg daemon.jpg
   page4/BuildInfo.om:
       IMAGES[] = fee.jpg fi.jpg foo.jpg fum.jpg

3.5.3  Computing the subdirectory list

The other hardcoded specification is the list of subdirectories page1, ..., page4. Rather than editing the project OMakefile each time a directory is added, we could compute it (again with glob).

    .SUBDIRS: $(glob page*)
        index.html: $(glob *.jpg)
            genhtml $+ > $@

Alternately, the directory structure may be hierarchical. Instead of using glob, we could use the subdirs function, returns each of the directories in a hierarchy. For example, this is the result of evaluating the subdirs function in the omake project root. The P option, passed as the first argument, specifies that the listing is “proper,” it should not include the omake directory itself.

    osh> subdirs(P, .)
    - : <array
            /home/jyh/.../omake/mk : Dir
            /home/jyh/.../omake/RPM : Dir
            ...
            /home/jyh/.../omake/osx_resources : Dir>

Using subdirs, our example is now as follows.

    .SUBDIRS: $(subdirs P, .)
        index.html: $(glob *.jpg)
            genhtml $+ > $@

In this case, every subdirectory will be included in the project.

If we are using the BuildInfo.om option. Instead of including every subdirectory, we could include only those that contain a BuildInfo.om file. For this purpose, we can use the find function, which traverses the directory hierarchy looking for files that match a test expression. In our case, we want to search for files with the name BuildInfo.om. Here is an example call.

    osh> FILES = $(find . -name BuildInfo.om)
    - : <array
            /home/jyh/.../omake/doc/html/BuildInfo.om : File
            /home/jyh/.../omake/src/BuildInfo.om : File
            /home/jyh/.../omake/tests/simple/BuildInfo.om : File>
    osh> DIRS = $(dirof $(FILES))
    - : <array
            /home/jyh/.../omake/doc/html : Dir
            /home/jyh/.../omake/src : Dir
            /home/jyh/.../omake/tests/simple : Dir>

In this example, there are three BuildInfo.om files, in the doc/html, src, and tests/simple directories. The dirof function returns the directories for each of the files.

Returning to our original example, we modify it as follows.

    .SUBDIRS: $(dirof $(find . -name BuildInfo.om))
        include BuildInfo   # Defines the IMAGES variable

        index.html: $(IMAGES)
            genhtml $+ > $@

3.5.4  Temporary directories

Sometimes, your project may include temporary directories–directories where you place intermediate results. these directories are deleted whenever the project is cleanup up. This means, in particular, that you can't place an OMakefile in a temporary directory, because it will be removed when the directory is removed.

Instead, if you need to define a configuration for any of these directories, you will need to define it using a .SUBDIRS body.

    section
        CREATE_SUBDIRS = true

        .SUBDIRS: tmp
            # Compute an MD5 digest
            %.digest: %.comments
               echo $(digest $<) > $@

            # Extract comments from the source files
            %.comments: ../src/%.src
               grep '^#' $< > $@

            .DEFAULT: foo.digest

    .PHONY: clean

    clean:
        rm -rf tmp        

In this example, we define the CREATE_SUBDIRS variable as true, so that the tmp directory will be created if it does not exist. The .SUBDIRS body in this example is a bit contrived, but it illustrates the kind of specification you might expect. The clean phony-target indicates that the tmp directory should be removed when the project is cleaned up.

Chapter 4  OMake concepts and syntax

Projects are specified to omake with OMakefiles. The OMakefile has a format similar to a Makefile. An OMakefile has three main kinds of syntactic objects: variable definitions, function definitions, and rule definitions.

4.1  Variables

Variables are defined with the following syntax. The name is any sequence of alphanumeric characters, underscore _, and hyphen -.

   <name> = <value>

Values are defined as a sequence of literal characters and variable expansions. A variable expansion has the form $(<name>), which represents the value of the <name> variable in the current environment. Some examples are shown below.

   CC = gcc
   CFLAGS = -Wall -g
   COMMAND = $(CC) $(CFLAGS) -O2

In this example, the value of the COMMAND variable is the string gcc -Wall -g -O2.

Unlike make(1), variable expansion is eager and pure (see also the section on Scoping). That is, variable values are expanded immediately and new variable definitions do not affect old ones. For example, suppose we extend the previous example with following variable definitions.

   X = $(COMMAND)
   COMMAND = $(COMMAND) -O3
   Y = $(COMMAND)

In this example, the value of the X variable is the string gcc -Wall -g -O2 as before, and the value of the Y variable is gcc -Wall -g -O2 -O3.

4.2  Adding to a variable definition

Variables definitions may also use the += operator, which adds the new text to an existing definition. The following two definitions are equivalent.

   # Add options to the CFLAGS variable
   CFLAGS = $(CFLAGS) -Wall -g

   # The following definition is equivalent
   CFLAGS += -Wall -g

4.3  Arrays

Arrays can be defined by appending the [] sequence to the variable name and defining initial values for the elements as separate lines. Whitespace is significant on each line. The following code sequence prints c d e.

    X[] =
        a b
        c d e
        f

    println($(nth 2, $(X)))

4.4  Special characters and quoting

The following characters are special to omake: $():,=#\. To treat any of these characters as normal text, they should be escaped with the backslash character \.

    DOLLAR = \$

Newlines may also be escaped with a backslash to concatenate several lines.

    FILES = a.c\
            b.c\
            c.c

Note that the backslash is not an escape for any other character, so the following works as expected (that is, it preserves the backslashes in the string).

    DOSTARGET = C:\WINDOWS\control.ini

An alternative mechanism for quoting special text is the use $"..." escapes. The number of double-quotations is arbitrary. The outermost quotations are not included in the text.

    A = $""String containing "quoted text" ""
    B = $"""Multi-line
        text.
        The # character is not special"""

4.5  Function definitions

Functions are defined using the following syntax.

   <name>(<params>) =
      <indented-body>

The parameters are a comma-separated list of identifiers, and the body must be placed on a separate set of lines that are indented from the function definition itself. For example, the following text defines a function that concatenates its arguments, separating them with a colon.

    ColonFun(a, b) =
        return($(a):$(b))

The return expression can be used to return a value from the function. A return statement is not required; if it is omitted, the returned value is the value of the last expression in the body to be evaluated. NOTE: as of version 0.9.6, return is a control operation, causing the function to immediately return. In the following example, when the argument a is true, the function f immediately returns the value 1 without evaluating the print statement.

    f(a) =
       if $(a)
          return 1
       println(The argument is false)
       return 0

In many cases, you may wish to return a value from a section or code block without returning from the function. In this case, you would use the value operator. In fact, the value operator is not limited to functions, it can be used any place where a value is required. In the following definition, the variable X is defined as 1 or 2, depending on the value of a, then result is printed, and returned from the function.

    f_value(a) =
       X =
          if $(a)
             value 1
          else
             value 2
       println(The value of X is $(X))
       value $(X)

Functions are called using the GNU-make syntax, $(<name> <args)), where <args> is a comma-separated list of values. For example, in the following program, the variable X contains the value foo:bar.

   X = $(ColonFun foo, bar)

If the value of a function is not needed, the function may also be called using standard function call notation. For example, the following program prints the string “She says: Hello world”.

    Printer(name) =
        println($(name) says: Hello world)

    Printer(She)

4.6  Comments

Comments begin with the # character and continue to the end of the line.

4.7  File inclusion

Files may be included with the include or open form. The included file must use the same syntax as an OMakefile.

    include $(Config_file)

The open operation is similar to an include, but the file is included at most once.

    open Config

    # Repeated opens are ignored, so this
    # line has no effect.
    open Config

If the file specified is not an absolute filenmame, both include and open operations search for the file based on the OMAKEPATH variable. In case of the open directive, the search is performed at parse time, and the argument to open may not contain any expressions.

4.8  Scoping, sections

Scopes in omake are defined by indentation level. When indentation is increased, such as in the body of a function, a new scope is introduced.

The section form can also be used to define a new scope. For example, the following code prints the line X = 2, followed by the line X = 1.

    X = 1
    section
        X = 2
        println(X = $(X))

    println(X = $(X))

This result may seem surprising–the variable definition within the section is not visible outside the scope of the section.

The export form, which will be described in detail in Section 6.3, can be used to circumvent this restriction by exporting variable values from an inner scope. For example, if we modify the previous example by adding an export expression, the new value for the X variable is retained, and the code prints the line X = 2 twice.

    X = 1
    section
        X = 2
        println(X = $(X))
        export

    println(X = $(X))

There are also cases where separate scoping is quite important. For example, each OMakefile is evaluated in its own scope. Since each part of a project may have its own configuration, it is important that variable definitions in one OMakefile do not affect the definitions in another.

To give another example, in some cases it is convenient to specify a separate set of variables for different build targets. A frequent idiom in this case is to use the section command to define a separate scope.

   section
      CFLAGS += -g
      %.c: %.y
          $(YACC) $<
      .SUBDIRS: foo

   .SUBDIRS: bar baz

In this example, the -g option is added to the CFLAGS variable by the foo subdirectory, but not by the bar and baz directories. The implicit rules are scoped as well and in this example, the newly added yacc rule will be inherited by the foo subdirectory, but not by the bar and baz ones; furthermore this implicit rule will not be in scope in the current directory.

4.9  Conditionals

Top level conditionals have the following form.

    if <test>
       <true-clause>
    elseif <text>
       <elseif-clause>
    else
       <else-clause>

The <test> expression is evaluated, and if it evaluates to a true value (see Section 9.2 for more information on logical values, and Boolean functions), the code for the <true-clause> is evaluated; otherwise the remaining clauses are evaluated. There may be multiple elseif clauses; both the elseif and else clauses are optional. Note that the clauses are indented, so they introduce new scopes.

When viewed as a predicate, a value corresponds to the Boolean false, if its string representation is the empty string, or one of the strings false, no, nil, undefined, or 0. All other values are true.

The following example illustrates a typical use of a conditional. The OSTYPE variable is the current machine architecture.

    # Common suffixes for files
    if $(equal $(OSTYPE), Win32)
       EXT_LIB = .lib
       EXT_OBJ = .obj
       EXT_ASM = .asm
       EXE = .exe
       export
    elseif $(mem $(OSTYPE), Unix Cygwin)
       EXT_LIB = .a
       EXT_OBJ = .o
       EXT_ASM = .s
       EXE =
       export
    else
       # Abort on other architectures
       eprintln(OS type $(OSTYPE) is not recognized)
       exit(1)

4.10  Matching

Pattern matching is performed with the switch and match forms.

    switch <string>
    case <pattern1>
        <clause1>
    case <pattern2>
        <clause2>
    ...
    default
       <default-clause>

The number of cases is arbitrary. The default clause is optional; however, if it is used it should be the last clause in the pattern match.

For switch, the string is compared with the patterns literally.

    switch $(HOST)
    case mymachine
        println(Building on mymachine)
    default
        println(Building on some other machine)

Patterns need not be constant strings. The following function tests for a literal match against pattern1, and a match against pattern2 with ## delimiters.

   Switch2(s, pattern1, pattern2) =
      switch $(s)
      case $(pattern1)
          println(Pattern1)
      case $"##$(pattern2)##"
          println(Pattern2)
      default
          println(Neither pattern matched)

For match the patterns are egrep(1)-style regular expressions. The numeric variables $1, $2, ... can be used to retrieve values that are matched by \(...\) expressions.

    match $(NODENAME)@$(SYSNAME)@$(RELEASE)
    case $"mymachine.*@\(.*\)@\(.*\)"
        println(Compiling on mymachine; sysname $1 and release $2 are ignored)

    case $".*@Linux@.*2\.4\.\(.*\)"
        println(Compiling on a Linux 2.4 system; subrelease is $1)

    default
        eprintln(Machine configuration not implemented)
        exit(1)

4.11  Objects

OMake is an object-oriented language. Generally speaking, an object is a value that contains fields and methods. An object is defined with a . suffix for a variable. For example, the following object might be used to specify a point (1, 5) on the two-dimensional plane.

    Coord. =
        x = 1
        y = 5
        print(message) =
           println($"$(message): the point is ($(x), $(y)")

    # Define X to be 5
    X = $(Coord.x)

    # This prints the string, "Hi: the point is (1, 5)"
    Coord.print(Hi)

The fields x and y represent the coordinates of the point. The method print prints out the position of the point.

4.12  Classes

We can also define classes. For example, suppose we wish to define a generic Point class with some methods to create, move, and print a point. A class is really just an object with a name, defined with the class directive.

    Point. =
        class Point

        # Default values for the fields
        x = 0
        y = 0

        # Create a new point from the coordinates
        new(x, y) =
           this.x = $(x)
           this.y = $(y)
           return $(this)

        # Move the point to the right
        move-right() =
           x = $(add $(x), 1)
           return $(this)

        # Print the point
        print() =
           println($"The point is ($(x), $(y)")

    p1 = $(Point.new 1, 5)
    p2 = $(p1.move-right)

    # Prints "The point is (1, 5)"
    p1.print()

    # Prints "The point is (2, 5)"
    p2.print()

Note that the variable $(this) is used to refer to the current object. Also, classes and objects are functional—the new and move-right methods return new objects. In this example, the object p2 is a different object from p1, which retains the original (1, 5) coordinates.

4.13  Inheritance

Classes and objects support inheritance (including multiple inheritance) with the extends directive. The following definition of Point3D defines a point with x, y, and z fields. The new object inherits all of the methods and fields of the parent classes/objects.

    Z. =
       z = 0

    Point3D. =
       extends $(Point)
       extends $(Z)
       class Point3D

       print() =
          println($"The 3D point is ($(x), $(y), $(z))")

    # The "new" method was not redefined, so this
    # defines a new point (1, 5, 0).
    p = $(Point3D.new 1, 5)

4.14  static.

The static. object is used to specify values that are persistent across runs of OMake. They are frequently used for configuring a project. Configuring a project can be expensive, so the static. object ensure that the configuration is performed just once. In the following (somewhat trivial) example, a static section is used to determine if the LATEX command is available. The $(where latex) function returns the full pathname for latex, or false if the command is not found.

   static. =
      LATEX_ENABLED = false
      print(--- Determining if LaTeX is installed )
      if $(where latex)
          LATEX_ENABLED = true
          export

      if $(LATEX_ENABLED)
         println($'(enabled)')
      else
         println($'(disabled)')

The OMake standard library provides a number of useful functions for programming the static. tests, as described in Chapter 14. Using the standard library, the above can be rewritten as

   open configure/Configure
   static. =
      LATEX_ENABLED = $(CheckProg latex)

As a matter of style, a static. section that is used for configuration should print what it is doing using the ConfMsgChecking and ConfMsgResult functions (of course, most of helper functions in the standard library would do that automatically).

4.14.1  .STATIC

This feature was introduced in version 0.9.8.5.

There is also a rule form of static section. The syntax can be any of the following three forms.

    # Export all variables defined by the body
    .STATIC:
        <body>

    # Specify file-dependencies
    .STATIC: <dependencies>
        <body>

    # Specify which variables to export, as well as file dependencies
    .STATIC: <vars>: <dependencies>
        <body>

The <vars> are the variable names to be defined, the <dependencies> are file dependencies—the rule is re-evaluated if one of the dependencies is changed. The <vars> and <dependencies> can be omitted; if so, all variables defined in the <body> are exported.

For example, the final example of the previous section can also be implemented as follows.

    open configure/Configure
    .STATIC:
        LATEX_ENABLED = $(CheckProg latex)

The effect is much the same as using static. (instead of .STATIC). However, in most cases .STATIC is preferred, for two reasons.

First, a .STATIC section is lazy, meaning that it is not evaluated until one of its variables is resolved. In this example, if $(LATEX_ENABLED) is never evaluated, the section need never be evaluated either. This is in contrast to the static. section, which always evaluates its body at least once.

A second reason is that a .STATIC section allows for file dependencies, which are useful when the .STATIC section is used for memoization. For example, suppose we wish to create a dictionary from a table that has key-value pairs. By using a .STATIC section, we can perform this computation only when the input file changes (not on every fun of omake). In the following example the awk function is used to parse the file table-file. When a line is encountered with the form key = value, the key/value pair is added the the TABLE.

    .STATIC: table-file
        TABLE = $(Map)
        awk(table-file)
        case $'^\([[:alnum:]]+\) *= *\(.*\)'
            TABLE = $(TABLE.add $1, $2)
            export

It is appropriate to think of a .STATIC section as a rule that must be recomputed whenever the dependencies of the rule change. The targets of the rule are the variables it exports (in this case, the TABLE variable).

4.14.1.1  .MEMO

A .MEMO rule is just like a .STATIC rule, except that the results are not saved between independent runs of omake.

4.14.1.2  :key:

The .STATIC and .MEMO rules also accept a :key: value, which specifies a “key” associated with the values being computed. It is useful to think of a .STATIC rule as a dictionary that associates keys with their values. When a .STATIC rule is evaluated, the result is saved in the table with the :key: defined by the rule (if a :key: is not specified, a default key is used instead). In other words, a rule is like a function. The :key: specifies the function “argument”, and the rule body computes the result.

To illustrate, let's use a .MEMO rule to implement a Fibonacci function.

    fib(i) =
        i = $(int $i)