Build System

The Adapnex build system unifies modern C++ compilation across local simulation and diverse industrial hardware targets. It automates CMake configuration, Ninja build execution, cross-compiler invocation, and dependency management so that industrial automation projects remain portable and deterministic.

Understanding CMake in Adapnex

Adapnex projects use CMake as their underlying build definition system. CMake is an industry-standard, cross-platform build tool generator. Rather than compiling source files directly, CMake reads a declarative configuration file called CMakeLists.txt and generates native build files for high-speed build tools like Ninja or Make.

A major advantage of using CMake is that it makes importing almost any open-source or commercial C++ library into your automation project straightforward. Modern package managers and package scripts such as CPM.cmake integrate directly with CMake, enabling declarative dependency downloads and automated target linking.

In Adapnex, CMake manages project source files, target definitions, include search paths, and third-party dependencies. If you are new to CMake, the official CMake Tutorial and the CMake Documentation offer comprehensive introductions to standard project setup.

Adapnex extends CMake with custom commands such as adapnex_executable(), adapnex_library(), and adapnex_tests(), which automatically wire up platform abstractions and real-time dependencies.

Invoking the Build

Commands such as adapnex app run and adapnex test automatically trigger the build system whenever source files change. Similarly, when using the Adapnex VS Code extension, clicking Run on Target compiles the project automatically before deployment.

When you want to compile an application manually without deploying or running tests, use the adapnex build command in your project directory containing the root CMakeLists.txt file:

adapnex build <target> [<executables>...] [flags]

If you omit the executable name, the build system compiles all executable targets defined in your project.

In the VS Code extension, you can trigger a manual build by clicking the Build for Target inline icon next to any device in the Adapnex sidebar or by selecting Adapnex: Build for Target from the Command Palette.

Target Resolution

The <target> argument specifies the destination environment for the build. The build system resolves targets through three mechanisms:

Target Hardware Identifiers

When targeting physical hardware, specify the target hardware identifier directly:

  • wago_cc100: Compiles for a WAGO CC100 device running Linux RT on an ARM Cortex-A7 processor.

  • wago_cc100_simulator: Compiles the WAGO CC100 application against the desktop simulator for local testing.

  • bnr_x20eds410: Compiles for a B&R X20 controller with an ARM Cortex-A53 processor.

  • compulab_iotdin_imx8p: Compiles for a CompuLab IOT-DIN industrial controller with an NXP i.MX8M Plus quad-core processor.

Registered Device Names and IDs

When your controller is registered with Adapnex Cloud, you can pass its device name or unique hardware ID directly to the build command:

adapnex build line_1_plc conveyor_app

The build system uses the Adapnex Cloud service, discovers the target hardware type configured on that physical device, and compiles the application with the matching cross-compilation toolchain. In the VS Code extension, registered devices appear directly in the Devices view under the Adapnex sidebar, allowing you to select them as the active deployment target.

Generic Host Targets

For desktop development, unit testing, and continuous integration pipelines, you can build applications for your local machine without cross-compilation. Adapnex provides generic targets for host development across operating systems and architectures:

  • generic: Automatically selects the host machine architecture and native toolchain.

  • generic_linux_aarch64: Targets standard 64-bit ARM Linux systems.

  • generic_linux_x86_64: Targets standard 64-bit x86 Linux systems.

  • generic_windows_x86_64: Targets standard 64-bit x86 Windows systems.

  • generic_macos_aarch64: Targets Apple Silicon macOS systems.

Building for generic targets enables rapid test execution with the Adapnex simulation engine before deploying to physical controllers.

Bundled Toolchains

Cross-compiling C++ applications for embedded Linux controllers typically requires installing complex vendor sysroots, glibc toolchains, and matching build utilities.

Adapnex eliminates external compiler installations by bundling pre-configured cross-compilers, standard libraries, and build tools. When you trigger a build, the required toolchain is provisioned automatically.

Because the Adapnex build system treats cross-compilation as a first-class feature, host operating system packages and system libraries installed on your development workstation are not available on target controllers. Any third-party libraries that your project imports should ideally be compiled from source as part of the CMake project build tree.

The build system pairs CMake with the Ninja build generator to maximize parallel compilation speed on multicore developer workstations.

Build Artifacts and IDE Integration

The build system creates an isolated build directory for each target, such as build_generic/, build_wago_cc100/, or build_compulab_iotdin_imx8p/. This isolation prevents object file conflicts when alternating between local simulation and cross-compiled hardware targets.

Each target build directory contains a compile_commands.json compilation database file. IDEs such as CLion, Visual Studio Code, and Neovim use this database to configure language servers, providing accurate code completion, diagnostic warnings, and symbol indexing for the selected target architecture.

Target Conditional Compilation

Different platforms provide different hardware peripherals. Adapnex injects preprocessor definitions into the compilation process, indicating whether a specific driver is available on the current target platform.

Some drivers, such as the B&R X20 driver family (ADAPNEX_DRIVERS_BNR_X2X) or the Modbus master driver (ADAPNEX_DRIVERS_MODBUS), require only standard network connectivity and are available across all platforms. In contrast, onboard I/O drivers depend on physical hardware registers and are available only when targeting the respective controller:

  • ADAPNEX_DRIVERS_WAGO_CC100: Available on WAGO Compact Controller 100 hardware.

  • ADAPNEX_DRIVERS_WAGO_CC100_SIMULATOR: Available when compiling against the desktop WAGO CC100 simulator.

  • ADAPNEX_DRIVERS_COMPULAB_IOTDIN_IMX8P: Available on CompuLab IOT-DIN hardware.

  • ADAPNEX_DRIVERS_BNR_X2X: Available across all platforms via network interface.

  • ADAPNEX_PLATFORM_SIMULATION: Available when running Google Test suites under the simulation platform.

These macros allow you to write a single portable codebase that adapts its hardware I/O mappings to whichever target device is selected.

Full Example: Multi-Target Hardware Switching

The following complete application demonstrates portable hardware switching. It implements a core machine monitoring task and binds physical I/O according to the active target controller, failing at compile time if an unsupported target is selected:

#include "adapnex.h"

// Core application task independent of hardware I/O
class MotorController final : public Task {
public:
    bool run_command = false;
    bool motor_running = false;

    void Update() override {
        motor_running = run_command;
    }
};

void setup() {
    const auto task_group = Application::CreateCyclicTaskGroup({.period = 20ms});

#if defined(ADAPNEX_DRIVERS_WAGO_CC100) || defined(ADAPNEX_DRIVERS_WAGO_CC100_SIMULATOR)
    // 1. WAGO CC100 hardware driver registration
    const auto wago_io = task_group->CreateTask<CC100IODriver>();

#elif defined(ADAPNEX_DRIVERS_COMPULAB_IOTDIN_IMX8P)
    // 1. CompuLab IOT-DIN hardware driver registration
    const auto compulab_io = task_group->CreateTask<DIODriver>();

#else
#error "No supported hardware target configured for this application build."
#endif

    // 2. Register core control task
    const auto controller = task_group->CreateTask<MotorController>();

#if defined(ADAPNEX_DRIVERS_WAGO_CC100) || defined(ADAPNEX_DRIVERS_WAGO_CC100_SIMULATOR)
    // 3. Bind WAGO CC100 terminals to task variables
    wago_io->DI1 >> controller->run_command;
    wago_io->DO1 << controller->motor_running;

#elif defined(ADAPNEX_DRIVERS_COMPULAB_IOTDIN_IMX8P)
    // 3. Bind CompuLab terminals to task variables
    compulab_io->DI0 >> controller->run_command;
    compulab_io->DO0 << controller->motor_running;
#endif
}

To build this application for a WAGO CC100 controller:

adapnex build wago_cc100

To compile the identical codebase for a CompuLab IOT-DIN controller:

adapnex build compulab_iotdin_imx8p