Skip to content

Installation

The Termina toolchain can be obtained as a prebuilt Docker image or built from source, and the choice depends on the intended use. For readers who only wish to write Termina programs and reproduce the examples presented in this book, the recommended approach is the prebuilt Docker image, which provides a complete and ready-to-use development environment without requiring any installation on the host beyond Docker itself. For those who intend to work on the transpiler, or who operate on systems where Docker is not a suitable option, the transpiler can also be built from source and the supporting components configured manually. The remainder of this book assumes the Docker-based setup, although every example applies equally to a native installation.

Throughout the book, commands intended to be typed in a terminal are shown preceded by a $ prompt. This character indicates the beginning of a command and must not be typed by the reader.

Using the Docker image

The Termina project publishes a container image that bundles the transpiler, the Operating System Abstraction Layer (OSAL) against which the generated code is compiled, and the cross-toolchains of the rtems5-leon3sf-nexysa7, rtems6-zynq7000-pynqz2 and freertos10-stm32l432-nucleol432kc platforms. Because all of these elements are distributed together within a single image, the user is relieved of the usual tasks of installing a compiler, obtaining the appropriate toolchain for each target, and ensuring that the different components are mutually compatible. For this reason, the image is the recommended starting point.

The only prerequisite on the host system is Docker. On macOS and Windows, the simplest option is to install Docker Desktop, whereas on Linux, Docker Engine can be installed from the distribution's own repositories or from the official packages. The image is built for the linux/amd64 architecture. Consequently, on Apple Silicon computers, Rosetta must be enabled in Docker Desktop, under Settings → General → Use Rosetta for x86/amd64 emulation, so that the SPARC and ARM cross-compilers can run transparently. On Windows, Docker Desktop should be used with the WSL 2 backend.

Once Docker is available, the image is retrieved with the following command. The latest tag tracks the most recent published release, while a specific version such as v0.6.0 may be pinned instead in order to obtain a reproducible environment, which is particularly convenient in a course where all participants must work with an identical setup:

$ docker pull ghcr.io/termina-lang/docker-termina:latest

The following sections describe three ways of using the image, which can be combined according to the task at hand.

Working in a disposable container

The quickest way to obtain the toolchain, and the one with which a first project is created, is to mount the current directory into a disposable container and work from a shell within it. The container is removed on exit, together with any change made outside the mounted directory. The project resides on a bind mount of the host directory, and every file produced in it remains on the host.

$ docker run --rm -it -v "$PWD":/work -w /work \
    ghcr.io/termina-lang/docker-termina:latest bash
$ docker run --rm -it -v "${PWD}:/work" -w /work `
    ghcr.io/termina-lang/docker-termina:latest bash

The resulting shell provides the complete toolchain. A project is created with termina new:

$ termina new myapp
$ cd myapp

The application is then written under app/ and src/. Transpiling it to C and compiling the result is done with:

$ termina build
$ cd output && make

Defining a termina alias

When editing is performed in a host application and the only service required from the image is the transpiler itself, an alias may be defined that wraps each invocation in a container. Once this alias is in place, the commands termina new, termina build, and the rest behave as though the tool were installed natively, while in reality each execution launches the image against the current directory.

$ alias termina='docker run --rm -it -v "$PWD":/work -w /work ghcr.io/termina-lang/docker-termina:latest termina'

Adding this line to the ~/.bashrc or ~/.zshrc file makes the alias permanent across sessions.

function termina { docker run --rm -it -v "${PWD}:/work" -w /work ghcr.io/termina-lang/docker-termina:latest termina @args }

Adding this function to the PowerShell profile ($PROFILE) makes it permanent across sessions.

The alias covers transpilation, which is normally the only service needed from the host. The compilation and execution of the generated code, however, rely on make and the bundled toolchains, and therefore those steps still require a shell inside the container, whether the disposable container shown above or the Dev Container described next.

Working inside Visual Studio Code

For users of Visual Studio Code, the most convenient arrangement consists of letting the editor open a project inside the container. Under this configuration, the transpiler, its language server, and the Termina syntax extension run within the image, while the source files remain on the host, where they can be managed as usual.

The editor support is provided by the Termina extension, published on the Visual Studio Code Marketplace as termina-lang.termina. It adds syntax highlighting for .fin files and connects to the transpiler's language server for diagnostics and code navigation. Listing it under extensions in the .devcontainer/devcontainer.json shown below installs it automatically when the project is opened in the container.

This arrangement requires the Dev Containers extension to be installed in Visual Studio Code. In the root of a project, created as shown above, a file named .devcontainer/devcontainer.json is added with the following contents:

{
  "name": "Termina dev container",
  "image": "ghcr.io/termina-lang/docker-termina:latest",
  "customizations": {
    "vscode": {
      "extensions": ["termina-lang.termina"]
    }
  },
  "remoteUser": "vscode"
}

The project folder is then opened in Visual Studio Code, selecting the Reopen in Container command when prompted, or invoking it from the command palette. From that point on, the integrated terminal is a shell running inside the image, in which termina, make, and the cross-compilers are all available, and the editor provides syntax support through the bundled extension. A project can also be started this way from scratch, by placing the .devcontainer/devcontainer.json shown above in an empty folder, reopening the folder in the container, and running termina new from the integrated terminal.

Building from source

The native installation is intended for those who wish to work on the transpiler itself, as well as for the occasional environment in which Docker is not appropriate. In this case the user assembles the same components that the image otherwise provides.

The transpiler is written in Haskell and built with Stack, which may be installed directly or through GHCup:

$ curl -sSL https://get.haskellstack.org/ | sh

Stack reads the exact GHC version required by the project from its stack.yaml file and downloads it automatically, so no compiler needs to be installed manually and a single stack install command produces the binary. The repository is cloned at the current release tag, v0.6.0 at the time of writing, and then built; stack install places the resulting termina executable in Stack's local binary directory, which is ~/.local/bin by default and should therefore be present in the PATH:

$ git clone --branch v0.6.0 https://github.com/termina-lang/termina.git
$ cd termina
$ stack install

The command termina --version confirms that the tool has been installed correctly and reports the release it was built from:

$ termina --version
termina 0.6.0

The code emitted by the transpiler is compiled against the OSAL, which must likewise be present on the system. The OSAL is cloned at the corresponding release tag. The transpiler and the OSAL must share the same MAJOR.MINOR version, so any v0.6.x of one is compatible with any v0.6.y of the other. The Makefile generated by termina build expects to find the OSAL under /opt/termina-osal, and the most straightforward configuration is therefore to create a symbolic link at that location:

$ git clone --branch v0.6.0 https://github.com/termina-lang/termina-osal.git
$ sudo ln -s "$(pwd)/termina-osal" /opt/termina-osal

Should the use of /opt be undesirable, the location of the OSAL may instead be supplied explicitly on each build through the make command line:

$ cd output && make TERMINA_OSAL_DIR=/absolute/path/to/termina-osal

The steps described so far are sufficient for the default posix-gcc platform, whose generated code is compiled with the host's own gcc and requires nothing further. Targeting one of the embedded platforms additionally requires the corresponding cross-toolchain to be present in the PATH. The rtems5-leon3sf-nexysa7 platform, which targets LEON3 under RTEMS 5, relies on the Gaisler RCC toolchain (sparc-gaisler-rtems5-gcc), available from Gaisler. The rtems6-zynq7000-pynqz2 platform targets the Zynq-7000 of the PYNQ-Z2 board under RTEMS 6. It relies on the arm-rtems6-gcc toolchain and on the xilinx_zynq_pynq board support package, both built from the RTEMS 6 sources with the RTEMS Source Builder. They are expected under /opt/rtems/6, and another location is passed to make as RTEMS6_PREFIX. The freertos10-stm32l432-nucleol432kc platform, which targets the STM32L432 microcontroller of the NUCLEO-L432KC board under FreeRTOS 10, relies on the bare-metal ARM toolchain arm-none-eabi-gcc with its C library, newlib. The Arm GNU Toolchain, available from Arm, includes both, and the image ships its release 13.3.rel1. The target platform is selected when the project is created, by means of termina new myproj --platform rtems5-leon3sf-nexysa7, or subsequently by editing the platform field in the termina.yaml file.