[Prev] [Next] [TOC] [Chapters]

9 Building Simulation Programs

9.1 Overview

This chapter describes the process and tools for building executable simulation models from their source code.

As described in the the previous chapters, the source of an OMNeT++ model usually contains the following files:

The process to turn the source into an executable form is this, in nutshell:

  1. Message files are translated into C++ using the message compiler, opp_msgc
  2. C++ sources are compiled into object form (.o files)
  3. Object files are linked with the simulation kernel and other libraries to get an executable or a shared library

Note that apart from the first step, the process is the same as building any C/C++ program. Also note that NED and ini files do not play a part in this process, as they are loaded by the simulation program at runtime.

One needs to link with the following libraries:

The exact files names of libraries depend on the platform and a number of additional factors.

Figure below shows an overview of the process of building (and running) simulation programs.

Figure: Building and running simulation

You can see that the build process is not complicated. Tools such as make and opp_makemake, to be described in the rest of the chapter, are primarily needed to optimize rebuilds (if a message file has been translated already, there is no need to repeat the translation for every build unless the file has changed), and for automation.

9.2 Using opp_makemake and Makefiles

There are several tools available for managing the build of C/C++ programs. OMNeT++ uses the traditional way, Makefiles. Writing a Makefile is usually a tedious task. However, OMNeT++ provides a tool that can generate the Makefile for the user, saving manual labour.

opp_makemake can automatically generate a Makefile for simulation programs, based on the source files in the current directory and (optionally) in subdirectories.

9.2.1 Command-line Options

The most important options accepted by opp_makemake are:

There are several other options; run opp_makemake -h to see the complete list.

9.2.2 Basic Use

Assuming the source files (*.ned, *.msg, *.cc, *.h) are located in a single directory, one can change to that directory and type:

$ opp_makemake

This will create a file named Makefile. Now, running the make program will build a simulation executable.

$ make

To regenerate an existing Makefile, add the -f option to the command line, otherwise opp_makemake will refuse overwriting it.

$ opp_makemake -f

The name of the output file will be derived from the name of the project directory (see later). It can be overridden with the -o option:

$ opp_makemake -f -o aloha

The generated Makefile supports the following targets:

9.2.3 Debug and Release Builds

opp_makemake generates a Makefile that can create both release and debug builds. By default it creates release version, but it is easy to override this behavior by defining the MODE variable on the make command line.

$ make MODE=debug

It is also possible to generate a Makefile that defaults to debug builds. This can be achieved by adding the --mode option to the opp_makemake command line.

$ opp_makemake --mode debug

9.2.4 Debugging the Makefile

opp_makemake generates a Makefile that prints only minimal information during the build process (only the name of the compiled file.) To see the full compiler commands executed by the Makefile, add the V=1 parameter to the make command line.

$ make V=1

9.2.5 Using External C/C++ Libraries

If the simulation model relies on an external library, the following opp_makemake options can be used to make the simulation link with the library.

For example, linking with a hypothetical Foo library installed under opt/ might require the following additional opp_makemake options: -I/opt/foo/include -L/opt/foo/lib -lfoo.

9.2.6 Building Directory Trees

It is possible to build a whole source directory tree with a single Makefile. A source tree will generate a single output file (executable or library). A source directory tree will always have a Makefile in its root, and source files may be placed anywhere in the tree.

To turn on this option, use the opp_makemake --deep option. opp_makemake will collect all .cc and .msg files from the whole subdirectory tree, and generate a Makefile that covers all. To exclude a specific directory, use the -X exclude/dir/path option. (Multiple -X options are accepted.)

An example:

$ opp_makemake -f --deep -X experimental -X obsolete

In the C++ code, include statements should contain the location of the file relative to the Makefile's location.

For example, if Foo.h is under utils/common/ in the source tree, it needs to be included as
#include "utils/common/Foo.h"

9.2.7 Dependency Handling

The make program can utilize dependency information in the Makefile to shorten build times by omitting build steps whose input has not changed since the last build. Dependency information is automatically created and kept up-to-date during the build process.

Dependency information is kept in .d files in the output directory.

9.2.8 Out-of-Directory Build

The build system creates object and executable files in a separate directory, called the output directory. By default, the output directory is out/<configname>, where the <configname> part depends on the compiler toolchain and build mode settings. (For example, the result of a debug build with GCC will be placed in out/gcc-debug.) The subdirectory tree inside the output directory will mirror the source directory structure.

By default, the out directory is placed in the project root directory. This location can be changed with opp_makemake's -O option.

$ opp_makemake -O ../tmp/obj

9.2.9 Building Shared and Static Libraries

By default the Makefile will create an executable file, but it is also possible to build shared or static libraries. Shared libraries are usually a better choice.

Use --make-so to create shared libraries, and --make-lib to build static libraries. The --nolink option completely omits the linking step, which is useful for top-level Makefiles that only invoke other Makefiles, or when custom linking commands are needed.

9.2.10 Recursive Builds

The --recurse option enables recursive make; when you build the simulation, make descends into the subdirectories and runs make in them too. By default, --recurse decends into all subdirectories; the -X <dir> option can be used to make it ignore certain subdirectories. This option is especially useful for top level Makefiles.

The --recurse option automatically discovers subdirectories, but this is sometimes inconvenient. Your source directory tree may contain parts which need their own hand written Makefile. This can happen if you include source files from an other non OMNeT++ project. With the -d <dir> or --subdir <dir> option, you can explicitly specify which directories to recurse into, and also, the directories need not be direct children of the current directory.

The recursive make options (--recurse, -d, --subdir) imply -X, that is, the directories recursed into will be automatically excluded from deep Makefiles.

You can control the order of traversal by adding dependencies into the makefrag file (see [9.2.11])

Motivation for recursive builds:

9.2.11 Customizing the Makefile

It is possible to add rules or otherwise customize the generated Makefile by providing a makefrag file. When you run opp_makemake, it will automatically insert the content of the makefrag file into the resulting Makefile. With the -i option, you can also name other files to be included into the Makefile.

makefrag will be inserted after the definitions but before the first rule, so it is possible to override existing definitions and add new ones, and also to override the default target.

makefrag can be useful if some of your source files are generated from other files (for example, you use generated NED files), or you need additional targets in your Makefile or just simply want to override the default target in the Makefile.

9.2.12 Projects with Multiple Source Trees

In the case of a large project, your source files may be spread across several directories and your project may generate more than one executable file (i.e. several shared libraries, examples etc.).

Once you have created your Makefiles with opp_makemake in every source directory tree, you will need a toplevel Makefile. The toplevel Makefile usually calls only the Makefiles recursively in the source directory trees.

9.2.13 A Multi-Directory Example

For a complex example of using opp_makemake, we will show how to create the Makefiles for a large project. First, take a look at the project's directory structure and find the directories that should be used as source trees:

project/
    doc/
    images/
    simulations/
    contrib/ <-- source tree (build libmfcontrib.so from this dir)
    core/ <-- source tree (build libmfcore.so from this dir)
    test/ <-- source tree (build testSuite executable from this dir)

Additionally, there are dependencies between these output files: mfcontrib requires mfcore and testSuite requires mfcontrib (and indirectly mfcore).

First, we create the Makefile for the core directory. The Makefile will build a shared lib from all .cc files in the core subtree, and will name it mfcore:

$ cd core && opp_makemake -f --deep --make-so -o mfcore -O out

The contrib directory depends on mfcore, so we use the -L and -l options to specify the library we should link with.

$ cd contrib && opp_makemake -f --deep --make-so -o mfcontrib -O out \
  -I../core -L../out/\$\(CONFIGNAME\)/core -lmfcore

The testSuite will be created as an executable file which depends on both mfcontrib and mfcore.

$ cd test && opp_makemake -f --deep -o testSuite -O out \
    -I../core -I../contrib -L../out/\$\(CONFIGNAME\)/contrib -lmfcontrib

Now, let us specify the dependencies among the above directories. Add the lines below to the makefrag file in the project root directory.

contrib_dir: core_dir
test_dir: contrib_dir

Now the last step is to create a top-level Makefile in the root of the project that calls the previously created Makefiles in the correct order. We will use the --nolink option, exclude every subdirectory from the build (-X.), and explicitly call the above Makefiles using -d <dir>. opp_makemake will automatically include the above created makefrag file.

$ opp_makemake -f --nolink -O out -d test -d core -d contrib -X.

9.3 Project Features

Long compile times are often an inconvenience when working with large OMNeT++-based model frameworks. OMNeT++ has a facility named project features that lets you reduce build times by excluding or disabling parts of a large model library. For example, you can disable modules that you do not use for the current simulation study. The word feature refers to a piece of the project codebase that can be turned off as a whole.

Additional benefits of project features include enforcing cleaner separation of unrelated parts in the model framework, being able to exclude code written for other platforms, and a less cluttered model palette in the NED editor.

Project features can be enabled/disabled from both the IDE and the command line. It is possible to query the list of enabled project features, and use this information in creating a Makefile for the project.

9.3.1 What is a Project Feature

Features can be defined per project. As already mentioned, a feature is a piece of the project codebase that can be turned off as a whole, that is, excluded from the C++ sources (and thus from the build) and also from NED. Feature definitions are typically written and distributed by the author of the project; end users are only presented with the option of enabling/disabling those features. A feature definition contains:

9.3.2 The opp_featuretool Program

Project features can be queried and manipulated using the opp_featuretool program. The first argument to the program must be a command; the most frequently used ones are list, enable and disable. The operation of commands can be refined with further options. One can obtain the full list of commands and options using the -h option.

Here are some examples of using the program.

Listing all features in the project:

$ opp_featuretool list

Listing all enabled features in the project:

$ opp_featuretool list -e

Enabling all features:

$ opp_featuretool enable all

Disabling a specific feature:

$ opp_featuretool disable Foo

The following command prints the command line options that should be used with opp_makemake to create a Makefile that builds the project with the currently enabled features:

$ opp_featuretool options

The easiest way to pass the output of the above command to opp_makemake is the $(...) shell construct:

$ opp_makemake --deep $(opp_featuretool options)

Often it is convenient to put feature defines (e.g. WITH_FOO) into a header file instead of passing them to the compiler via -D options. This makes it easier to detect feature enablements from derived projects, and also makes it easier for C++ code editors to correctly highlight conditional code blocks that depend on project features.

The header file can be generated with opp_featuretool using the following command:

$ opp_featuretool defines >feature_defines.h

At the same time, -D options must be removed from the compiler command line. opp_featuretool options has switches to filter them out. The modified command for Makefile generation:

$ opp_makemake --deep $(opp_featuretool options -fl)

It is advisable to create a Makefile rule that regenerates the header file when feature enablements change:

feature_defines.h: $(wildcard .oppfeaturestate) .oppfeatures
        opp_featuretool defines >feature_defines.h

9.3.3 The .oppfeatures File

Project features are defined in the .oppfeatures file in your project's root directory. This is an XML file, and it has to be written by hand (there is no specialized editor for it).

The root element is <features>, and it may have several <feature> child elements, each defining a project feature. The fields of a feature are represented with XML attributes; attribute names are id, name, description, initiallyEnabled, requires, labels, nedPackages, extraSourceFolders, compileFlags and linkerFlags. Items within attributes that represent lists (requires, labels, etc.) are separated by spaces.

Here is an example feature from the INET Framework:

<feature
  id="TCP_common"
  name="TCP Common"
  description = "The common part of TCP implementations"
  initiallyEnabled = "true"
  requires = "IPv4"
  labels = "Transport"
  nedPackages = "inet.transport.tcp_common
                 inet.applications.tcpapp
                 inet.util.headerserializers.tcp"
  extraSourceFolders = ""
  compileFlags = "-DWITH_TCP_COMMON"
  linkerFlags = ""
  />

Project feature enablements are stored in the .featurestate file.

9.3.4 How to Introduce a Project Feature

If you plan to introduce a project feature in your project, here's what you'll need to do:



[Prev] [Next] [TOC] [Chapters]