Project Structure and Build System¶
A Termina program is organized as a project: a directory with a fixed layout, a configuration file, and the source modules that make up the application. This chapter describes that layout, the configuration file that governs it, and the commands that turn the source into a running program.
Creating a project¶
A project is created with the new command, which takes the project name and,
optionally, the target platform with -p or --platform. When no platform is
given, the project is created for posix-gcc, the development platform:
The command generates the project directory with its initial structure:
The command also initializes a Git repository in the new directory, unless the
directory already lies inside one, and writes a .gitignore that excludes the
built executables in output/bin/. The option --vcs none skips the
repository, although the .gitignore is still written. The option --vscode
adds the files that Visual Studio Code needs to work on the project inside the
Termina development container: .devcontainer/devcontainer.json, which names
the container image, .vscode/tasks.json, with a termina: build task, and
.vscode/launch.json, with a debug configuration for posix-gcc.
The project layout¶
The file termina.yaml holds the project configuration. The app directory contains the application
module, app.fin, where the system is assembled. The src directory holds the
source modules that define the component classes and functions of the program.
The output directory receives the C code that the transpiler generates, and is
empty until the project is built.
The modules under src are organized in subdirectories, and the directory
structure mirrors the dotted paths used in import declarations. A module at
src/resources/counter.fin is imported as resources.counter, and one at
src/common/types.fin as common.types. A module always belongs to a
subdirectory; the root of src holds directories, not modules.
The configuration file¶
The termina.yaml file records the settings the transpiler needs. A freshly
created project contains the following:
builder: make
efp-folder: efp
name: hello_world
output-folder: output
platform: posix-gcc
source-modules: src
The name is the name of the project, and platform is the target it is built
for. The remaining entries name the parts of the layout: the folder of the
source modules, the folder for the generated output, the folder reserved for
analysis support files, and the build tool used to compile that output. The
application module has no entry of its own, since it is always app/app.fin.
The optional profile entry selects how the output is built: release, the
default, or debug, which ties the generated code to the Termina sources for
the debugger, as the chapter on debugging describes.
Some features of the runtime are switched on here as well. Setting
enable-system-port to true deploys the system_entry resource that provides
the system interface, and the platform-flags section enables platform-specific
sources, such as the keyboard interrupt of the posix-gcc platform:
The keys of platform-flags name platforms. The flags under a key that names no
platform would be dropped without notice, so such a key is rejected.
A project that calls C code keeps it in an extern directory at its root and
lists it in an extern section of termina.yaml, as the chapter on
calling C code describes.
Building a project¶
Building a project is a two-stage process: the transpiler turns the Termina sources into C, and the platform's build system turns that C into a program.
The first stage is transpilation,
carried out by the build command from the project's root directory:
The transpiler reads the application module and the source modules, checks the
program, and writes the resulting C into the output directory. The output
mirrors the source: a src tree of C files, an include tree of headers, and a
Makefile that compiles them against the Operating System
Abstraction Layer for the configured platform.
The second stage compiles that generated code. On the posix-gcc platform, this
is done with make from inside the output directory, and it produces an
executable in bin that can be run directly:
For an embedded target, the same two stages apply, but the compilation uses the cross-toolchain of the chosen platform, and the result is an image to be loaded onto the device or an emulator rather than an executable for the host.
The generated Makefile includes platform/common.mk from the OSAL, which
compiles every platform in C11 with -pedantic-errors and a set of warnings
beyond -Wall and -Wextra, among them -Wconversion and -Wshadow. The
code the transpiler emits is expected to compile without warnings under those
flags, so a warning comes either from the transpiler or from C code written by
hand, such as a platform driver.
Static analysis¶
The same Makefile provides two targets that run
Cppcheck over the generated sources. The
cppcheck target runs the analysis with the warning, style, performance and
portability checks enabled, and cppcheck-misra adds the MISRA addon of
Cppcheck, restricted to the mandatory and required rules of MISRA C:
The OSAL is analyzed in its own repository, so both targets analyze only the
sources of the project. Each target keeps its results in a directory
of its own under the build directory, and a second run only analyzes the files
that have changed. The targets report their findings without failing; setting
CPPCHECK_ERROR_EXITCODE=1 on the make command line makes them fail when
there is any, which is the setting for a continuous integration job. The Docker
image ships Cppcheck, whereas an installation from source requires it on the
host.
Findings of the MISRA addon are identified by the number of the rule, such as
misra-c2012-11.5. A class member receives its instance as a void * and
converts it into a pointer to its class, so every project gets findings under
Rule 11.5 that come from the way the generated code is built.
Platforms¶
The posix-gcc platform builds the application for a conventional Unix-like
host, using the system's own compiler, and is intended for development and
validation. The embedded platforms are rtems5-leon3sf-nexysa7, for the LEON3
processor under RTEMS 5; rtems6-zynq7000-pynqz2, for the Zynq-7000 of the
PYNQ-Z2 board under RTEMS 6; and freertos10-stm32l432-nucleol432kc, for the
STM32L432 microcontroller under FreeRTOS 10. The platform is chosen when the
project is created, with the
--platform option of the new command, or changed afterwards by editing the
platform field in termina.yaml. Each target beyond posix-gcc requires its
cross-toolchain to be installed, as described in the chapter on installation.