Skip to main content
Version: Nightly 🚧

This article has examples in the following target languages:

Target Declaration

Every Lingua Franca program begins with a statement of this form:

    target uC

Unlike the other targets, the uC target does not use target parameters. Instead, it uses annotations, as described in https://micro-lf.org/documentation/annotations/.

Every Lingua Franca program begins with a statement of this form:

    target <name> <parameters>

The <name> gives the name of some Lingua Franca target language, which is the language in which reactions are written. This is also the language of the program(s) generated by the Lingua Franca compiler. The target languages currently supported are C, C++, Python, TypeScript, and Rust. There is also a target CCpp that is just like the C target except that it uses a C++ compiler to compile the code, thereby allowing inclusion of C++ code.

Summary of Target Parameters

A target specification may have optional parameters, the names and values of which depend on which specific target you are using. Each parameter is a key-value pair, where the supported keys are a subset of the following:

  • auth: A boolean specifying to apply authorization between RTI and federates when federated execution.
  • build: A command to execute after code generation instead of the default compile command.
  • build-type: One of Debug (the default), Release, RelWithDebInfo and MinSizeRel.
  • cargo-dependencies: (Rust only) list of dependencies to include in the generated Cargo.toml file.
  • cargo-features: (Rust only) List of string names of features to include.
  • cmake-args: (C and Python only) A dictionary of CMake variable definitions to pass to the build.
  • cmake-include: List of paths to cmake files to guide compilation.
  • compiler: A string giving the name of the target language compiler to use.
  • docker: A boolean to generate a Dockerfile.
  • external-runtime-path: Specify a pre-compiled external runtime library located to link to instead of the default.
  • export-dependency-graph: To export the reaction dependency graph as a dot graph (for debugging).
  • fast: A boolean specifying to execute as fast as possible without waiting for physical time to match logical time.
  • files: An array of paths to files or directories to be copied to the directory that contains the generated sources.
  • logging: An indicator of how much information to print when executing the program.
  • no-compile: If true, then do not invoke a target language compiler. Just generate code.
  • no-runtime-validation: If true, disable runtime validation.
  • protobufs: An array of .proto files that are to be compiled and included in the generated code.
  • python-version: (Python only) A string (with quotation marks) giving an exact Python version to use.
  • runtime-version: Specify which version of the runtime system to use.
  • rust-include: (Rust only) A set of Rust modules in the generated project.
  • scheduler: (C only) Specification of the scheduler to use.
  • single-file-project: (Rust only) If true, enables single-file project layout.
  • single-threaded: Specify to not use multithreading.
  • timeout: A time value (with units) specifying the logical stop time of execution. See Termination.
  • trace-plugin: (C only) Specification of an external trace plugin to use with tracing.
  • workers: If using multiple threads, how many worker threads to create.

Not all targets support all target parameters. The full set of target parameters supported by the target is:

target C {
  auth: <true or false>
  build: <string>,
  build-type: <Debug, Release, RelWithDebInfo, or MinSizeRel>,
  cmake-args: <object of key-value pairs>,
  cmake-include: <string or list of strings>,
  compiler: <string>,
  compiler-flags: <string or list of strings>,
  docker: <true or false>,
  fast: <true or false>,
  files: <string or list of strings>,
  logging: <error, warning, info, log, debug>,
  no-compile: <true or false>,
  protobufs: <string or list of strings>,
  single-threaded: <true or false>,
  timeout: <time>,
  trace-plugin: <object with package, library, and optional path>,
  workers: <non-negative integer>,
};

For example:

target C {
    cmake: false,
    compiler: "cc",
    fast: true,
    logging: log,
    timeout: 1 secs,
};

This specifies to use compiler cc instead of the default gcc, to use optimization level 3, to execute as fast as possible, and to exit execution when logical time has advanced to 10 seconds. Note that all events at logical time 10 seconds greater than the starting logical time will be executed.

The comma on the last parameter is optional, as is the semicolon on the last line. A target may support overriding the target parameters on the command line when invoking the compiled program.

auth​

The detailed documentation is here.

build​

A command to execute after code generation instead of the default compile command. This is either a single string or an array of strings. The specified command(s) will be executed an environment that has the following environment variables defined:

  • LF_CURRENT_WORKING_DIRECTORY: The directory in which the command is invoked.
  • LF_SOURCE_DIRECTORY: The directory containing the .lf file being compiled.
  • LF_PACKAGE_DIRECTORY: The directory for the root of the project or package (normally the directory above the src directory).
  • LF_SOURCE_GEN_DIRECTORY: The directory in which generated files and any files in the files target directive are placed.
  • LF_BIN_DIRECTORY: The directory into which to put binaries.

The command will be executed in the same directory as the .lf file being compiled. For example, if you specify

target C {
    build: "./compile.sh Foo"
}

then instead of invoking the C compiler after generating code, the code generator will invoke your compile.sh script, which could look something like this:

#!/bin/bash
# Build the generated code.
cd ${LF_SOURCE_GEN_DIRECTORY}
cmake .
make

# Move the executable to the bin directory.
mv $1 ${LF_BIN_DIRECTORY}

# Invoke the executable.
${LF_BIN_DIRECTORY}/$1

# Plot the results, which have appeared in the src-gen directory.
gnuplot ${LF_SOURCE_DIRECTORY}/$1.gnuplot
open $1.pdf

The first few lines of this script do the same thing that is normally done when there is no build option in the target. Specifically, they use cmake to create a makefile, invoke make, and then move the executable to the bin directory. The next line, however, gives new functionality. It executes the compiled code! The final two lines assume that the program has produced a file with data to be plotted and use gnuplot to plot the data. This requires, of course, that you have gnuplot installed, and that there is a file called Foo.gnuplot in the same directory as Foo.lf. The file Foo.gnuplot contains the commands to plot the data, and might look something like the following:

set title 'My Title'
set xrange [0:3]
set yrange [-2:2]
set xlabel "Time (seconds)"
set terminal pdf size 5, 3.5
set output 'Foo.pdf'
plot 'mydata1.data' using 1:2 with lines, \
'mydata2.data' using 1:2 with lines

This assumes that your program has written two files, mydata1.data and mydata2.data containing two columns, time and value.

build-type​

This parameter specifies how to compile the code. The following options are supported:

  • Debug: Optimization is disabled and debug information is included in the executable.
  • Release: Optimization is enabled and debug information is missing.
  • RelWithDebInfo: Optimization with debug information.
  • MinSizeRel: Optimize for smallest size.

This defaults to Debug.

cargo-dependencies​

This target does not support the cargo-dependencies target option.

cargo-features​

This target does not support the cargo-features target option.

cmake-args​

The cmake-args target property allows you to pass arbitrary CMake variable definitions to the build. This is useful for advanced configuration scenarios, such as specifying custom paths or enabling specific features in external libraries.

target C {
    cmake-args: {
        MY_VARIABLE: "value",
        ANOTHER_VARIABLE: "/path/to/something",
    },
}

Each key-value pair in the cmake-args object becomes a -D<key>=<value> argument passed to CMake during the build configuration. This is particularly useful when integrating with external trace plugins or other libraries that require custom CMake configuration.

cmake-include​

target C {
    cmake-include: ["relative/path/to/foo.txt", "relative/path/to/bar.txt", ...]
};
target Cpp {
    cmake-include: ["relative/path/to/foo.txt", "relative/path/to/bar.txt", ...]
};

This will optionally append additional custom CMake instructions to the generated CMakeLists.txt, drawing these instructions from the specified text files (e.g, foo.txt). The specified files are resolved using the same file search algorithm as used for the files target parameter. Those files will be copied into the src-gen directory that contains the generated sources. This is done to make the generated code more portable (a feature that is useful in federated execution).

The cmake-include target property can be used, for example, to add dependencies on various packages (e.g., by using the find_package and target_link_libraries commands).

A CMake variable called ${LF_MAIN_TARGET} can be used in the included text file(s) for convenience. This variable will contain the name of the CMake target (i.e., the name of the main reactor). For example, a foo.txt file can contain:

find_package(m REQUIRED) # Finds the m library

target_link_libraries( ${LF_MAIN_TARGET} m ) # Links the m library

foo.txt can then be included by specifying it as an argument to cmake-include.

Note: For a general tutorial on finding packages in CMake, see this external documentation entry. For a list of CMake find modules, see this.

The cmake-include parameter works in conjunction with the import statement. If any imported .lf file has cmake-include among its target properties, the specified text files will be appended to the current list of cmake-includes. These files will be resolved relative to the imported .lf file using the same search procedure as for the files parameter. This helps resolve dependencies in imported reactors automatically and makes the code more modular.

CMakeInclude.lf is an example that uses this feature. A more sophisticated example of the usage of this target parameter can be found in Rhythm.lf. A distributed version can be found in DistributedCMakeInclude.lf is a test that uses this feature.

Note: For federated execution, both cmake-include and files are kept separate for each federate as much as possible. This means that if one federate is imported, or uses an imported reactor that other federates don't use, it will only have access to cmake-includes and files defined in the main .lf file, plus the selectively imported .lf files. DistributedCMakeIncludeSeparateCompile.lf is a test that demonstrates this feature.

See AsyncCallback.lf for an example.

compiler​

This parameter is a string giving the name of the compiler to use. Normally CMake selects the best compiler for your system, but you can use this parameter to point to your preferred compiler. Possible values are, for instance, gcc, g++, clang, or clang++.

docker​

This option takes a boolean argument (default is false).

If true, a docker file will be generated in the unfederated case.

In the federated case, a docker file for each federate will be generated. A docker-compose file will also be generated for the top-level federated reactor.

external-runtime-path​

This target does not support the external-runtime-path target option.

export-dependency-graph​

This target does not support the export-dependency-graph target option.

fast​

By default, the execution of a Lingua Franca program is slowed down, if necessary, so that logical time does not elapse faster than physical time. If you wish to execute the program as fast as possible without this constraint, then specify the fast target parameter with value true.

files​

The files target parameter specifies array of files or directories to be copied to the directory that contains the generated sources. The full path to that directory is available in C code via the LF_SOURCE_GEN_DIRECTORY macro.

target C {
    files: ["file1", "file2", ...]
}
target Python {
    files: ["file1", "file2", ...]
}

The lookup procedure for these files and directories is as follows:

1- Search in the directory containing the .lf file that has the target directive.

2- If not found, search in LF_CLASSPATH.

3- If still not found, search in CLASSPATH.

4- If still not found, search for the file as a resource. Specifically, if a file begins with a forward slash /, then the path is assumed to be relative to the root directory of the Lingua Franca source tree.

For example, if you wish to use audio on a Mac, you can specify:

target C {
    files: ["/lib/C/util/audio_loop_mac.c", "/lib/C/util/audio_loop.h"]
}

Your preamble code can then include these files, for example:

preamble {=
    #include "audio_loop_mac.c"
=}

Your reactions can then invoke functions defined in that .c file.

Sometimes, you will need access to these files from target code in a reaction. For the C target (at least), the generated program can use a variable LF_TARGET_FILES_DIRECTORY, which evaluates to a string giving the full path of the directory containing the supporting files. For example, the audio_loop_mac.c file will be located in the directory given by the string LF_TARGET_FILES_DIRECTORY. This can be used in reactions, for example, to read those files.

Moreover, the files target specification works in conjunction with the import statement. If a .lf file is imported and has designated supporting files using the files target parameter, those files will be resolved relative to that .lf file and copied to the directory that contains the generated sources. This is done to make code that imports other .lf files more modular.

Rhythm.lf, which imports PlayWaveform.lf is an example that demonstrates most of these features.

logging​

By default, when executing a generated Lingua Franca program, error messages, warnings, and informational messages are printed to standard out. You can get additional information printed by setting the logging parameter to LOG or DEBUG (or log or debug). The latter is more verbose. If you set the logging parameter to warn, then warnings and errors will be printed, but informational messages will not (e.g. message produced using the info_print utility function). If you set logging to error, then warning messages will also not be printed.

The C and Python targets also support tracing, which outputs binary traces of an execution rather than human-readable text and is designed to have minimal impact on performance.

The logging option is one of error, warn, info, log or debug. It specifies the level of diagnostic messages about execution to print to the console. A message will print if this parameter is greater than or equal to the level of the message, where error < warn < info < log < debug. The default value is info, which means that messages log or debug messages will not print.

The logging option is one of ERROR, WARN, INFO, LOG or DEBUG. It specifies the level of diagnostic messages about execution to print to the console. A message will print if this parameter is greater than or equal to the level of the message, where ERROR < WARN < INFO < LOG < DEBUG. The default value is INFO, which means that messages tagged LOG and DEBUG will not print. Internally this is handled by the pinojs module.

no-compile​

If true, then do not invoke a target language compiler nor cmake. Just generate code.

no-runtime-validation​

This target does not support the no-runtime-validation target option.

protobufs​

Protobufs is a serialization protocol by which data in a target language can be copied over the network to a remote location. The protobufs target parameter gives an array of .proto files that are to be compiled and included in the generated code. For an example, see PersonProtocolBuffers.lf PersonProtocolBuffers.lf

python-version​

This target does not support the python-version target option.

runtime-version​

This target does not support the runtime-version target option.

rust-include​

This target does not support the rust-include target option.

scheduler​

This target does not support the scheduler target option.

single-file-project​

This target does not support the single-file-project target option.

single-threaded​

If threading is disabled (by setting single-threaded to true), then no thread library is used. This is mostly useful for bare-metal embedded software implementations where no thread library is available. In such a setting, mutual exclusion is typically implemented by disabling interrupts.

timeout​

A time value (with units) specifying the logical stop time of execution. See Termination.

trace-plugin​

The trace-plugin target property specifies an external trace plugin to use when tracing is enabled. This allows you to use alternative tracing implementations, such as the lf-trace-xronos plugin that streams data to the Xronos Dashboard using OpenTelemetry.

The property accepts an object with the following fields:

  • package: The CMake package name (required). Used in CMake's find_package() call.
  • library: The CMake library name to link (required). Used in target_link_libraries().
  • path: An optional relative or absolute path to the directory containing the CMake package. If omitted, the system path will be searched.

Basic Usage (System-installed Plugin)​

If the trace plugin is installed to the system prefix:

target C {
    tracing: true,
    trace-plugin: {
        package: "lf-trace-xronos",
        library: "lf::trace-impl",
    },
}

Usage with Custom Path​

If the trace plugin is installed to a user-chosen prefix:

target C {
    tracing: true,
    trace-plugin: {
        package: "lf-trace-xronos",
        library: "lf::trace-impl",
        path: "../../install/", // Relative to this LF file's location
    },
}

See tracing for more information on using trace plugins.

workers​

This parameter takes a non-negative integer and specifies the number of worker threads to execute the generated program. Unless the single-threaded target property is set to true on (see single-threaded), the generated code will use a target platform thread library and generate multithreaded code. This can transparently execute reactions that have no dependence on one another in parallel on multiple cores. By default, single-threaded set to false, and the workers property is set to 0, which means that the number of workers is determined by the runtime system. Typically, it will be set to the number of cores on the machine running the code. To use a different number of worker threads, give a positive integer for this target parameter.

Command-Line Arguments​

The generated C program understands the following command-line arguments, each of which has a short form (one character) and a long form:

  • -f, --fast [true | false]: Specifies whether to wait for physical time to match logical time. The default is false. If this is true, then the program will execute as fast as possible, letting logical time advance faster than physical time.
  • -o, --timeout <duration> <units>: Stop execution when logical time has advanced by the specified duration. The units can be any of nsec, usec, msec, sec, minute, hour, day, week, or the plurals of those.
  • -w, --workers <n>: Executed using <n> worker threads if possible. This option is ignored in the single-threaded version. That is, it is ignored if a threading option was given in the target properties with value false.
  • -i, --id <n>: The ID of the federation that this reactor will join.
  • -x, --tmux: Run the program in a tmux session, which splits the terminal into multiple panes, one for each federate and one for the RTI.
  • -h, --help: Print a description of the command-line arguments.

In addition, if the main or federated reactor declares parameters with the primitive types int, time, float, double, bool, or string, these parameters can be overridden when invoking the binary. Each LF parameter named param corresponds to a CLI parameter named --param. For example, for a main reactor declared as follows:

target C;
main reactor Hello(param: int = 42) {
  reaction(startup) {=
    printf("Hello World %d\n", self->param);
  =}
}

Running bin/Hello --param 43 will result in "Hello World 43" being printed.