
| Jump to: | | OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long) |
| Index: | | All • Variables • Functions • Objects • Targets • Options |
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.
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.
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.
For users already familiar with the make(1) command, here is a list of differences to keep in mind when using omake.
StaticCLibrary and CProgram),
described in Chapter 13, to specify these builds more simply.
.SUFFIXES and the .suf1.suf2: are not supported.
You should use wildcard patterns instead %.suf2: %.suf1.
.PHONY
targets (see Section 8.10) before they are used.
.SUBDIRS: target (see Section 8.8).
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)
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)
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.
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.
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))
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.
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.
vmount function for versioning, it wise to keep the source files
distinct from the compiled versions. For example, suppose the source directory contained a file
src/foo.o. When mounted, the foo.o file will be the same in all versions, which is
probably not what you want. It is better to keep the src/ directory pristine, containing no
compiled code.vmount -l option, files are linked into the version directory only if
they are referenced in the project. Functions that examine the filesystem (like $(ls ...))
may produce unexpected results.
| Jump to: | | OMake Home • Guide Home • Guide (single-page) • Contents (short) • Contents (long) |
| Index: | | All • Variables • Functions • Objects • Targets • Options |