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.
| Argument | Meaning |
|---|---|
PARA_LIBS_PUBLIC / PARA_LIBS_PRIVATE | List of Functional Clusters to link. For each entry <lib>, it runs find_package(para-<lib> REQUIRED) and then links as para::<lib> (e.g., core → para::core). |
PARA_GEN_DIR | The directory where generated headers/IDL (manifests, DDS IDL, etc.) reside. Used for include paths and install rules. |
OUTPUT_NAME | The name of the produced executable |
The example above links
core exec log. That is, after the build this app depends onpara::core,para::exec, andpara::log. If you use communication, you addcom.
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.cppfocused on the core flow (the original controls log output via theHELLO_WORLD_VERBOSEenvironment variable). Note that bothExecutionClient::Create()andReportExecutionState()return anara::core::Result, so results are handled withHasValue()/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).