Skip to main content

Build with CMake

PARA applications are built through the CMake module provided by the SDK (share/cmake/Modules/PARA/Para.cmake). This module configures the PARA version, standard, and install rules, and provides helper macros so applications can link Functional Clusters easily.

1) SDK Bootstrap

A project starts with a small bootstrap that locates the SDK (PARA_SDK) and loads the PARA CMake module. The example Machine uses share/examples/Machine/cmake/ParaSdk.cmake, whose contents are as follows.

macro(ParaSdk_Init)
# Promote env var PARA_SDK to a cache variable
if ((NOT DEFINED PARA_SDK OR PARA_SDK STREQUAL "") AND (NOT "$ENV{PARA_SDK}" STREQUAL ""))
set(PARA_SDK "$ENV{PARA_SDK}" CACHE PATH "PARA SDK location" FORCE)
endif()

if (NOT DEFINED PARA_SDK OR PARA_SDK STREQUAL "")
message(FATAL_ERROR "[PARA] ERROR: The variable 'PARA_SDK' is not set")
elseif (NOT EXISTS "${PARA_SDK}")
unset(PARA_SDK CACHE)
message(FATAL_ERROR "[PARA] ERROR: The variable 'PARA_SDK' has invalid path")
endif()

list(APPEND CMAKE_MODULE_PATH ${PARA_SDK}/share/cmake/Modules)
include(PARA/Para)
endmacro()

ParaSdk_Init()

PARA_SDK is set by the environment setup script, so it is enough to first source . ./para-env-setup.sh. The final include(PARA/Para) loads the SDK's Para.cmake module, which calls Para_Init() internally.

2) Defining the Application

The application's top-level CMakeLists.txt sets the app metadata and calls Para_App(). Below is the example share/examples/Machine/apps/HelloWorld/CMakeLists.txt.

set(PARA_APP_NAME HelloWorld)
set(PARA_APP_EXECUTABLE HelloWorld)
set(PARA_APP_VERSION 1.0.0)
set(PARA_APP_GEN_DIR ${CMAKE_CURRENT_SOURCE_DIR})

Para_App()

project(${PARA_APP_NAME}
VERSION ${PARA_APP_VERSION}
LANGUAGES CXX)

add_subdirectory(src)

Para_App() sets the install path to opt/<PARA_APP_NAME>/ (that is, bin/lib/etc/var) and installs the app's manifest (PARA_APP_GEN_DIR/manifest) into etc/. This is how the deployment unit structure is created under opt/<PARA_APP_NAME>/.

3) Target Configuration and Cluster Linking

The actual executable is created with add_executable() and configured with Para_Target(). The example apps/HelloWorld/src/CMakeLists.txt is as follows.

add_executable(${PARA_APP_NAME})

Para_Target(${PARA_APP_NAME}
PARA_GEN_DIR ${PARA_APP_GEN_DIR}
PARA_LIBS_PRIVATE core exec log
OUTPUT_NAME ${PARA_APP_EXECUTABLE}
)

target_sources(${PARA_APP_NAME}
PRIVATE
main.cpp
)

The main arguments of Para_Target() are as follows.

ArgumentMeaning
PARA_LIBS_PUBLIC / PARA_LIBS_PRIVATEList of Functional Clusters to link. For each entry <lib>, it runs find_package(para-<lib> REQUIRED) and then links as para::<lib> (e.g., corepara::core).
PARA_GEN_DIRThe directory where generated headers/IDL (manifests, DDS IDL, etc.) reside. Used for include paths and install rules.
OUTPUT_NAMEThe name of the produced executable

The example above links core exec log. That is, after the build this app depends on para::core, para::exec, and para::log. If you use communication, you add com.

4) Minimal Application Code

The example apps/HelloWorld/src/main.cpp shows the standard skeleton of a PARA application — ara::core initialization, creating an ara::log logger, and reporting the execution state via ara::exec::ExecutionClient.

#include "ara/core/initialization.h"
#include "ara/exec/execution_client.h"
#include "ara/log/logger.h"

static std::atomic<bool> g_running{true};

int main(int argc, char* argv[])
{
ara::core::Initialize(argc, argv);

auto& logger = ara::log::CreateLogger("MAIN", "HelloWorld Main Context",
ara::log::LogLevel::kDebug);

// Termination handler: gracefully handle the SIGTERM sent by the EM
auto terminationHandler = [&logger]() {
logger.LogWarn() << "SIGTERM received — shutting down";
g_running = false;
};

auto clientResult = ara::exec::ExecutionClient::Create(terminationHandler);
if (!clientResult.HasValue()) {
logger.LogError() << "Failed to create ExecutionClient";
return 1;
}
auto client = std::move(clientResult).Value();

// Report execution state to the EM: kRunning
auto reportResult = client.ReportExecutionState(ara::exec::ExecutionState::kRunning);
if (!reportResult.HasValue()) {
logger.LogError() << "Failed to ReportExecutionState";
return 1;
}

int32_t count = 0;
while (g_running) {
logger.LogInfo() << "Hello, World! (" << count << ")";
++count;
std::this_thread::sleep_for(std::chrono::seconds(1));
}

ara::core::Deinitialize();
return 0;
}

The code above is a transcription of the example main.cpp focused on the core flow (the original controls log output via the HELLO_WORLD_VERBOSE environment variable). Note that both ExecutionClient::Create() and ReportExecutionState() return an ara::core::Result, so results are handled with HasValue()/Value() — this is the error handling pattern used throughout PARA (see the API Reference).

5) Running the Build

The flow for building the entire example Machine is as follows.

# 1. Set up the SDK environment (configures PARA_SDK, etc.)
cd /path/to/para-sdk
. ./para-env-setup.sh

# 2. Build the example Machine
cd share/examples/Machine
cmake -S . -B build
cmake --build build

6) Running the Built App

The build artifacts run on top of the PARA runtime (EM). At install time, Para_Init() generates the run wrapper para-exec.sh, which sets PARA_CONF/PARA_DATA/PARA_APPL to the install location and then runs the EM. Unless you set CMAKE_INSTALL_PREFIX yourself, Para.cmake defaults the install location to build/install.

# Install the build output (default install location: build/install)
cmake --install build

# Run the runtime from the install directory (generated wrapper)
cd build/install
./para-exec.sh

When the EM starts, as described in the runtime behavior model, MachineFG transitions to Startup, and processes are forked according to each app's manifest state-dependencies. You check the output logs with para-tui (the SDK bundles only dlt-daemon; if you install the DLT tools separately, dlt-receive also works).