Skip to content
Skip to content
All tutorials

Tutorial · C++ · Version 2.0

Cross Compilation Example

Cross-compile a minimal Holoscan C++ application for Jetson AGX Thor and IGX Thor

AuthorsZachary Crandall (NVIDIA)
Supported platformsx86_64
LanguageC++
Version2.0
CompatibilityHSDK 5.0
Contribution levelLevel 1 · Very high-quality code
Sourceholoscan-tutorials

This tutorial shows how to cross-compile a small Holoscan Software Development Kit (SDK) 5.0 application on an x86_64 host machine for NVIDIA Jetson AGX Thor and NVIDIA IGX Thor target systems. Follow along to cross-compile with the Holoscan SDK arm64 Debian package without rebuilding the SDK from source or emulating the target environment with QEMU.

Background

This tutorial is intended for Jetson and IGX embedded developers who want to build Holoscan C++ applications on an x86_64 Linux workstation or Continuous Integration (CI) system and deploy them to an AArch64 target system. Cross-compilation runs the compiler on one architecture while producing binaries for another.

The pinned workflow targets CUDA 13 Server Base System Architecture (SBSA) systems using NVIDIA's generic AArch64 packages. Jetson Linux 39.2.1 aligned Jetson AGX Thor with SBSA, and NVIDIA's CUDA Cross-SBSA packages support cross-platform development for arm64 Jetson Thor and SBSA targets. The same build is therefore suitable for the core Holoscan API on these target baselines:

Target Supported software baseline
NVIDIA Jetson AGX Thor Developer Kit JetPack 7.2.1, Jetson Linux 39.2.1, Ubuntu 24.04, and CUDA 13
NVIDIA IGX Thor Developer Kit and Developer Kit Mini IGX Software (IGX-SW) 2.0 Production Release, Board Support Package (BSP) 38.5.0, Ubuntu 24.04, and CUDA 13

See the Jetson Linux 39.2.1 release notes, IGX-SW 2.0 release notes, and CUDA cross-platform installation guide for the platform details.

This example uses Ubuntu 24.04's AArch64 GNU Compiler Collection (GCC) cross-compiler and target libraries to match the Ubuntu 24.04 Holoscan package. It does not use the standalone Jetson Linux 39.2.1 cross-toolchain, which provides an older GNU C Library (glibc) 2.28 sysroot. Because Thor implements the Server Base System Architecture (SBSA), the core application can instead use NVIDIA's generic AArch64 CUDA and Holoscan packages without a board-specific target filesystem.

This supported path is limited to the core Holoscan C++ API demonstrated here. Applications that use board-specific camera, multimedia, networking, or other BSP libraries need matching target packages and a sysroot captured from the target OS. Those dependencies are outside this tutorial's scope.

A sysroot is a directory tree that mirrors the target system's filesystem and supplies the target headers and libraries used during cross-compilation instead of the host's filesystem.

This separates the build environment from the deployment environment. The x86_64 builder contains the compiler, CMake, headers, and CUDA cross-compilation packages, while the embedded target needs only the application and its matching runtime dependencies. Developers can use workstation or CI resources, create repeatable container builds, avoid installing a complete build toolchain on the target, and build without keeping the target connected. The resulting application must still be tested on the target hardware.

One alternative to cross-compilation is to fully emulate the target build environment. QEMU is an emulator and virtualizer that can run AArch64 programs on an x86_64 host by translating their instructions. It is useful when a build needs to execute target binaries, but emulation adds runtime overhead and configuration. This tutorial instead runs the build tools natively on x86_64, uses a cross-compiler to emit AArch64 code, and extracts the target Holoscan package without running its programs. Avoiding emulation makes the build faster and simpler, but the produced executable cannot be run on the build host.

What you will learn

This tutorial covers two workflows:

  1. Build the containerized example: Create the cross-compilation container with Docker, build and stage the example, verify that the result is an AArch64 executable linked to Holoscan, and validate it on a supported Thor target.
  2. Add Holoscan to an existing project: Install the host and CUDA cross-compilation tools, acquire and extract the AArch64 Holoscan SDK package, configure the CMake toolchain, discover and link Holoscan targets, build the project, and understand the deployment boundary.

Part 1: Build the example

Requirements

  • An x86_64 Linux host with Docker Engine and BuildKit.
  • Internet access while building the image.
  • file and readelf (binutils) on the host for the final artifact checks.
  • Optional: A Jetson AGX Thor Developer Kit with JetPack 7.2.1, or an IGX Thor Developer Kit or Developer Kit Mini with IGX-SW 2.0, for deployment and runtime validation.

A graphics processing unit (GPU) and target hardware are not required to cross-compile. This C++ example does not compile CUDA source, so it does not set CMAKE_CUDA_ARCHITECTURES. For projects that compile CUDA source, this tutorial uses 75-virtual as a portable starting point. It embeds NVIDIA Parallel Thread Execution (PTX) code that a compatible CUDA driver compiles for the target GPU using just-in-time (JIT) compilation. When the deployment GPU is known, replace it with the appropriate hardware-specific architecture, as described in Choose a CUDA target architecture.

Manual Docker and CMake workflow

Build and install the example using Docker and CMake.

Build the cross-compilation image

From the repository root:

docker build --platform linux/amd64 \
  -f tutorials/cross_compilation_example/Dockerfile \
  -t holoscan-5-cross-sbsa:5.0.0 .

The image installs the native x86_64 build tools, Ubuntu 24.04's AArch64 compiler and target libraries, and CUDA Cross-SBSA packages. It then uses NVIDIA's signed SBSA apt repository to download and extract holoscan-core-cuda-13_5.0.0.8.0-1_arm64.deb into /opt/holoscan-target. The extracted SDK prefix is /opt/holoscan-target/usr; libraries and CMake exports use lib/aarch64-linux-gnu. The image also extracts Ubuntu's architecture-independent libexpected-dev package into the same root for the SDK's tl-expected dependency. Extracting the target package makes its headers, CMake exports, and AArch64 libraries available without trying to execute its AArch64 package dependencies in the x86_64 builder.

Configure, build, and stage

Create an ignored output directory and run each CMake step in the same image. Mounting the source for all three steps allows CMake to regenerate the Ninja files when necessary.

cd tutorials/cross_compilation_example
mkdir -p build-cross

# Configure
docker run --rm --platform linux/amd64 \
  --user "$(id -u):$(id -g)" \
  --mount type=bind,src="$PWD",dst=/workspace/source,readonly \
  --mount type=bind,src="$PWD/build-cross",dst=/workspace/build \
  holoscan-5-cross-sbsa:5.0.0 \
  cmake -S /workspace/source -B /workspace/build -G Ninja \
    -DCMAKE_TOOLCHAIN_FILE=/opt/holoscan-cross/aarch64-cross-sbsa.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_INSTALL_PREFIX=/workspace/build/install

# Build
docker run --rm --platform linux/amd64 \
  --user "$(id -u):$(id -g)" \
  --mount type=bind,src="$PWD",dst=/workspace/source,readonly \
  --mount type=bind,src="$PWD/build-cross",dst=/workspace/build \
  holoscan-5-cross-sbsa:5.0.0 \
  cmake --build /workspace/build

# Install
docker run --rm --platform linux/amd64 \
  --user "$(id -u):$(id -g)" \
  --mount type=bind,src="$PWD",dst=/workspace/source,readonly \
  --mount type=bind,src="$PWD/build-cross",dst=/workspace/build \
  holoscan-5-cross-sbsa:5.0.0 \
  cmake --install /workspace/build

The staged executable is build-cross/install/bin/cross_compilation_example. Set the artifact path for the remaining steps:

CROSS_COMPILED_APP=build-cross/install/bin/cross_compilation_example

Verify the result

file "${CROSS_COMPILED_APP}" | grep -E 'ELF .*ARM aarch64'
readelf --dynamic "${CROSS_COMPILED_APP}" | grep -F '[libholoscan_core.so.5]'

file must report an AArch64 Executable and Linkable Format (ELF) binary. readelf must report a dependency on libholoscan_core.so.5. Do not use host ldd: the host cannot load the AArch64 executable.

Deploy and validate on a Thor target

Prepare either supported target baseline listed in Background, then install the Holoscan SDK Core 5.0 Debian package by following the Holoscan SDK installation guide. If the NVIDIA SBSA repository is already configured on the target, the package used by this build can be installed directly:

sudo apt-get update
sudo apt-get install --yes holoscan-core-cuda-13=5.0.0.8.0-1

On the target, confirm the platform and runtime versions:

test "$(uname -m)" = aarch64
. /etc/os-release
test "${VERSION_ID}" = 24.04
dpkg-query -W -f='${Package} ${Version}\n' holoscan-core-cuda-13
dpkg-query -W -f='${Package} ${Version}\n' cuda-cudart-13-0
head -n 1 /etc/nv_tegra_release

The Holoscan package must report 5.0.0.8.0-1, the CUDA runtime must report a 13.0 release, and the Jetson or IGX release information must match the selected baseline. Copy the staged executable from the x86_64 host, replacing the target login and address:

scp "${CROSS_COMPILED_APP}" \
  <user>@<target>:/tmp/cross_compilation_example

Run the remaining checks on the target:

chmod +x /tmp/cross_compilation_example

# Validate that all shared libraries resolve on the target
(
  set -o pipefail
  ldd /tmp/cross_compilation_example | tee /tmp/cross_compilation_example.ldd
) && test -r /tmp/cross_compilation_example.ldd \
  && ! grep -q 'not found' /tmp/cross_compilation_example.ldd

# Validate successful exit and exactly one hello-world activation
(
  set -o pipefail
  timeout 30s /tmp/cross_compilation_example 2>&1 | tee /tmp/cross_compilation_example.log
) && test "$(grep -Fc 'Hello Holoscan!' /tmp/cross_compilation_example.log)" -eq 1

The host-side file and readelf checks validate the cross-build and linkage. This target-side run validates the runtime ABI, Holoscan and CUDA dependencies, and application behavior on Jetson AGX Thor or IGX Thor. This example stages only the application executable; it does not bundle the SDK or other shared libraries.

Part 2: Add Holoscan to an existing project

This section starts with a plain Ubuntu 24.04 x86_64 build environment and keeps the target SDK under the existing project's .cross/ directory. The build machine does not need an NVIDIA GPU.

Before running the commands in this section, change to the existing project's root directory:

cd /path/to/your/existing-project

Run the remaining commands from this directory so $PWD, .cross/, cmake/, and build-cross/ refer to the existing project.

Install the build tools

Confirm that the build machine is x86_64, then install the native tools and Ubuntu 24.04 GNU Compiler Collection (GCC) AArch64 cross-compiler and target libraries:

test "$(dpkg --print-architecture)" = amd64

sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
  binutils-aarch64-linux-gnu \
  ca-certificates \
  curl \
  file \
  g++-aarch64-linux-gnu \
  gnupg \
  ninja-build \
  python3-venv

The Holoscan 5.0 package requires CMake 4.0 and exports C++20 compile requirements. Install the same CMake version used by the container in a project-local Python environment:

python3 -m venv .cross/cmake-venv
. .cross/cmake-venv/bin/activate
python -m pip install 'cmake>=4.0.3'
cmake --version

Install the CUDA cross-compilation packages

Add the NVIDIA x86_64 and Cross-SBSA repositories:

CUDA_KEYRING=/usr/share/keyrings/cuda-archive-keyring.gpg

curl -fsSL \
  https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/3bf863cc.pub \
  | sudo gpg --dearmor --yes --output "${CUDA_KEYRING}"

echo "deb [signed-by=${CUDA_KEYRING}] https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/ /" \
  | sudo tee /etc/apt/sources.list.d/nvidia-cuda-x86_64.list >/dev/null
echo "deb [signed-by=${CUDA_KEYRING}] https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/cross-linux-sbsa/ /" \
  | sudo tee /etc/apt/sources.list.d/nvidia-cuda-cross-sbsa.list >/dev/null
echo "deb [arch=arm64 signed-by=${CUDA_KEYRING}] https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/sbsa/ /" \
  | sudo tee /etc/apt/sources.list.d/nvidia-holoscan-sbsa.list >/dev/null

sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
  cuda-cross-sbsa-13-0 \
  cuda-nvcc-13-0

cuda-nvcc-13-0 supplies the native x86_64 compiler. cuda-cross-sbsa-13-0 supplies the AArch64 CUDA headers and libraries under /usr/local/cuda-13.0/targets/sbsa-linux. NVIDIA documents this Cross-SBSA package as the supported CUDA cross-platform toolkit for arm64 Jetson Thor and SBSA targets.

Extract the AArch64 Holoscan package

Choose one of the following methods to download the released target package. Each method writes the same file, which is validated and extracted afterward:

HOLOSCAN_DEB_VERSION=5.0.0.8.0-1
HOLOSCAN_DEB_NAME="holoscan-core-cuda-13_${HOLOSCAN_DEB_VERSION}_arm64.deb"
HOLOSCAN_DEB="$PWD/.cross/${HOLOSCAN_DEB_NAME}"
mkdir -p .cross/holoscan-root

The Holoscan SDK Core package also requires libexpected-dev (at least 1.1.0). Ubuntu 24.04 provides version 1.1.0-2 as an architecture-independent package. Download it using the host's Ubuntu repository and extract it into the same target root:

(
  cd .cross
  apt-get download libexpected-dev=1.1.0-2
)
test "$(dpkg-deb -f .cross/libexpected-dev_1.1.0-2_all.deb Architecture)" = all
dpkg-deb --extract .cross/libexpected-dev_1.1.0-2_all.deb .cross/holoscan-root

Option 1: Use temporary apt architecture overrides

This is the least invasive option. The architecture settings apply only to these commands and do not register arm64 as a foreign architecture for the build machine:

# Update the target repository metadata
sudo apt-get update \
  -o Dir::Etc::sourcelist=sources.list.d/nvidia-holoscan-sbsa.list \
  -o Dir::Etc::sourceparts=- \
  -o APT::Get::List-Cleanup=0 \
  -o APT::Architecture=arm64 \
  -o APT::Architectures=arm64

# Download the package
(
  cd .cross
  apt-get download \
    -o APT::Architecture=arm64 \
    -o APT::Architectures=arm64 \
    "holoscan-core-cuda-13:arm64=${HOLOSCAN_DEB_VERSION}"
)

Option 2: Register arm64 with dpkg

Use this option when standard Debian multiarch configuration is preferred. Registering the architecture is system-wide, but the isolated update still prevents apt from querying arm64 packages from every configured Ubuntu repository:

# Register arm64 as a foreign architecture
sudo dpkg --add-architecture arm64

# Update the target repository metadata
sudo apt-get update \
  -o Dir::Etc::sourcelist=sources.list.d/nvidia-holoscan-sbsa.list \
  -o Dir::Etc::sourceparts=- \
  -o APT::Get::List-Cleanup=0

# Download the package
(
  cd .cross
  apt-get download "holoscan-core-cuda-13:arm64=${HOLOSCAN_DEB_VERSION}"
)

# Optional if arm64 was not already configured:
sudo dpkg --remove-architecture arm64

The removal command will fail if arm64 packages are installed. Omit it if arm64 was already registered or other workflows need it.

Option 3: Download the package directly with curl

This avoids apt architecture configuration, but the package URL and checksum must be updated together whenever the version changes:

HOLOSCAN_DEB_URL="https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/sbsa/${HOLOSCAN_DEB_NAME}"
HOLOSCAN_DEB_SHA256=b176cd45911294a40963f38ec7016e760fcc1956d44b4c30c276a4cf96e4c160

# Download the package
curl -fsSL "${HOLOSCAN_DEB_URL}" -o "${HOLOSCAN_DEB}"

# Validate the checksum
printf '%s  %s\n' "${HOLOSCAN_DEB_SHA256}" "${HOLOSCAN_DEB}" | sha256sum --check -

Validate and extract the package

After completing one SDK download option, validate and extract the package:

HOLOSCAN_TARGET_SDK_ROOT="$PWD/.cross/holoscan-root/usr"
(
  set -e
  # Validate the package before extracting it.
  test -f "${HOLOSCAN_DEB}"
  test "$(dpkg-deb -f "${HOLOSCAN_DEB}" Architecture)" = arm64
  test "$(dpkg-deb -f "${HOLOSCAN_DEB}" Version)" = "${HOLOSCAN_DEB_VERSION}"

  dpkg-deb --extract "${HOLOSCAN_DEB}" .cross/holoscan-root

  test -f "${HOLOSCAN_TARGET_SDK_ROOT}/include/holoscan/holoscan.hpp"
  test -f "${HOLOSCAN_TARGET_SDK_ROOT}/lib/aarch64-linux-gnu/cmake/holoscan/holoscan-config.cmake"
  file -L "${HOLOSCAN_TARGET_SDK_ROOT}/lib/aarch64-linux-gnu/libholoscan_core.so" \
    | grep -E 'ELF .*ARM aarch64'
)

The final command must report an ARM AArch64 library. The apt options verify the repository signature and package hash; the curl option relies on the explicit SHA-256 check. Add .cross/ to the project's .gitignore. The target needs the matching runtime package from the Holoscan SDK installation guide. Using the same holoscan-core-cuda-13 version in the extracted build dependency and on the target avoids an SDK application binary interface (ABI) mismatch.

Extracting this package is sufficient for the core holoscan::core API used by this tutorial. It is not equivalent to installing every optional Holoscan backend and its development dependencies. Installing holoscan-core-cuda-13:arm64 into the x86_64 builder would mix target packages into the host package database and could run AArch64 package-maintainer scripts. Keep downloading and extracting target packages for the cross-build instead. On the AArch64 target system, use apt-get install so apt can install and configure the matching runtime dependency closure normally.

Add the CMake toolchain

Copy aarch64-cross-sbsa.cmake and nvcc-cross-sbsa into the existing project's cmake/ directory.

The toolchain accepts the project-specific roots below and sets the remaining CMake cross-compilation variables. It uses the standard compiler and CUDA locations installed above:

Variable Value or role
CMAKE_SYSTEM_NAME, CMAKE_SYSTEM_PROCESSOR Select Linux and AArch64 as the target
HOLOSCAN_CROSS_SYSROOT GNU cross-tool root, /usr/aarch64-linux-gnu by default
HOLOSCAN_TARGET_SDK_ROOT Extracted AArch64 SDK prefix, /opt/holoscan-target/usr by default
CMAKE_C_COMPILER, CMAKE_CXX_COMPILER Standard aarch64-linux-gnu cross compilers
CMAKE_CUDA_COMPILER The included wrapper around /usr/local/cuda-13.0/bin/nvcc that selects CUDA's sbsa-linux target
CMAKE_CUDA_HOST_COMPILER aarch64-linux-gnu-g++
CUDAToolkit_ROOT, CUDAToolkit_NVCC_EXECUTABLE Keep CUDA discovery on the native NVIDIA CUDA Compiler (NVCC) and SBSA target toolkit
CMAKE_FIND_ROOT_PATH Restricts target lookup to Holoscan, GNU AArch64, and CUDA SBSA roots
CMAKE_FIND_ROOT_PATH_MODE_* Finds host programs but target packages, headers, and libraries

CMAKE_SYSROOT is deliberately not set. Ubuntu's GNU cross compiler already has its target sysroot, while Holoscan and CUDA are separate prefixes. Forcing one global sysroot would incorrectly rewrite those absolute package paths.

Choose a CUDA target architecture

Set CMAKE_CUDA_ARCHITECTURES only when the project enables CUDA. It is not hard-coded in the toolchain because it is a property of the deployment GPU, not the compiler container.

In 75-virtual, 75 means CUDA compute capability 7.5 and virtual tells CMake to embed PTX instead of machine code for a particular GPU. At runtime, the CUDA driver JIT-compiles that PTX for the installed GPU. This is more portable across compatible GPUs, at the cost of JIT work when the application first loads its CUDA code.

For a hardware-specific build:

  1. Find the deployment GPU's compute capability in NVIDIA's compute capability documentation. When nvidia-smi is available on the target, query it directly:
nvidia-smi --query-gpu=name,compute_cap --format=csv,noheader
  1. Remove the decimal point and append -real. For example, the integrated Thor GPU reports compute capability 11.0 with CUDA 13, which becomes -DCMAKE_CUDA_ARCHITECTURES="110-real".

The -real suffix generates GPU machine code and avoids PTX JIT for that architecture, but the result is less portable. Omitting the suffix, such as 110, makes CMake generate both real and virtual code. See CMake's CUDA_ARCHITECTURES documentation for the complete syntax.

A C++-only project needs this minimum:

cmake_minimum_required(VERSION 4.0)
project(my_holoscan_app LANGUAGES CXX)

find_package(holoscan 5.0 REQUIRED CONFIG COMPONENTS core)

add_executable(my_holoscan_app main.cpp)
target_link_libraries(my_holoscan_app PRIVATE holoscan::core)

install(TARGETS my_holoscan_app RUNTIME DESTINATION bin)

If the project contains .cu files, enable CUDA in project():

project(my_holoscan_app LANGUAGES CXX CUDA)

After add_executable(), explicitly require C++20 for both C++ and CUDA sources so NVCC can compile the SDK 5 headers:

target_compile_features(my_holoscan_app PRIVATE cxx_std_20 cuda_std_20)

Pass the target architecture while configuring:

cmake ... -DCMAKE_CUDA_ARCHITECTURES="75-virtual"

Request additional SDK 5 components and link their exported targets only when the application uses them. The core package alone is sufficient for this example.

Configure and build

Activate the CMake environment, point the toolchain at the project-local SDK, and build:

# Activate the project-local CMake environment
. .cross/cmake-venv/bin/activate

# Configure
cmake -S . -B build-cross -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE="$PWD/cmake/aarch64-cross-sbsa.cmake" \
  -DHOLOSCAN_TARGET_SDK_ROOT="$PWD/.cross/holoscan-root/usr" \
  -DHOLOSCAN_CROSS_SYSROOT=/usr/aarch64-linux-gnu \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX="$PWD/build-cross/install"

# Build
cmake --build build-cross

# Install
cmake --install build-cross

For a CUDA project, also configure with the CUDA architecture selected in Choose a CUDA target architecture.

Every additional native library used by the project also needs AArch64 headers and libraries. Check CMake's configure output to ensure it never satisfies a target dependency with an x86_64 library from the build machine.

Extend the target sysroot for optional SDK components

Holoscan SDK 5 splits Core, Inference, Vision, and Visualization into separate packages. This example extracts only holoscan-core-cuda-13; the holoscan package is a metapackage that pulls in all components when installed with apt.

Applications using additional components must download their actual AArch64 runtime and development packages, plus their dependencies. Extract Holoscan component packages into the same .cross/holoscan-root as Core so their CMake exports share one SDK prefix. Extract external dependencies, such as TensorRT, into .cross/target-root.

In the pinned 5.0.0.8.0-1 Debian package set, these CUDA 13 component packages are metapackages. Their Depends fields identify the development packages containing the component payloads:

Metapackage Development package
holoscan-inference-cuda-13 libholoscan-inference-dev-cuda-13
holoscan-vision-cuda-13 libholoscan-vision5-dev-cuda-13
holoscan-visualization-cuda-13 libholoscan-visualization-dev-cuda-13

Extracting only a metapackage does not provide component headers or libraries. Download its referenced development package and matching runtime dependencies, and extract all Holoscan payload packages into .cross/holoscan-root alongside Core.

Install native x86_64 NVCC and NVIDIA's Cross-SBSA packages normally. Keep architecture-specific optional dependencies out of the builder's package database unless their multiarch compatibility has been verified. The architecture-independent libexpected-dev package used above contains headers and CMake files and can be extracted alongside Core.

The workflow below is for third-party AArch64 dependencies only. Do not add holoscan, holoscan-*, or libholoscan* packages to TARGET_PACKAGES. Their dependency closure can pull Core and AArch64 CUDA compiler packages into the external root. Download the required SDK payload packages separately and extract them into .cross/holoscan-root, retaining the verified Core.

The safe default workflow for these external packages is:

  1. Declare the required AArch64 packages and exact versions in a project-owned manifest.
  2. Give apt isolated package status, repository metadata, and package-cache directories for arm64.
  3. Use apt-get --download-only so apt resolves and downloads the dependency closure without installing it. This command does not populate the target package root.
  4. Reject Holoscan packages in the download cache, then extract the external packages with dpkg-deb --extract.
  5. Append that root to CMAKE_FIND_ROOT_PATH after the SDK, GNU AArch64, and CUDA roots, then provide any package-specific CMake configuration.
  6. Check every linked library's architecture and validate the result on the target system.

The isolated apt directories prevent the target dependency resolver from using the builder's amd64 package state. The source list must describe the same Ubuntu and NVIDIA repositories used by the target. For the supported Ubuntu 24.04 SBSA baseline, the setup has this form:

TARGET_APT_ROOT="$PWD/.cross/apt-arm64"
TARGET_PACKAGE_ROOT="$PWD/.cross/target-root"
CUDA_KEYRING=/usr/share/keyrings/cuda-archive-keyring.gpg

mkdir -p \
  "${TARGET_APT_ROOT}/lists/partial" \
  "${TARGET_APT_ROOT}/archives/partial" \
  "${TARGET_PACKAGE_ROOT}"
: > "${TARGET_APT_ROOT}/status"

cat > "${TARGET_APT_ROOT}/sources.list" <<EOF
deb [arch=arm64 signed-by=/usr/share/keyrings/ubuntu-archive-keyring.gpg] http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse
deb [arch=arm64 signed-by=/usr/share/keyrings/ubuntu-archive-keyring.gpg] http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse
deb [arch=arm64 signed-by=/usr/share/keyrings/ubuntu-archive-keyring.gpg] http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse
deb [arch=arm64 signed-by=${CUDA_KEYRING}] https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/sbsa/ /
EOF

APT_TARGET_OPTIONS=(
  -o APT::Architecture=arm64
  -o APT::Architectures=arm64
  -o "Dir::Etc::sourcelist=${TARGET_APT_ROOT}/sources.list"
  -o Dir::Etc::sourceparts=-
  -o "Dir::State::status=${TARGET_APT_ROOT}/status"
  -o "Dir::State::lists=${TARGET_APT_ROOT}/lists"
  -o "Dir::Cache::archives=${TARGET_APT_ROOT}/archives"
  -o Debug::NoLocking=1
)

Define TARGET_PACKAGES with the required external AArch64 packages and exact versions before running the resolver. Review the simulated package transaction before downloading. Packages that rely on maintainer scripts, alternatives, or triggers to construct their installed layout might not work through extraction alone. For those packages, reproduce the required links explicitly or use a sysroot captured from the exact target root filesystem.

Add TensorRT development files (optional)

The "Hello World" example described above focuses on the Holoscan SDK core library minimal dependency set only. A real application might additionally rely on other domain-specific libraries such as the Holoscan Inference module, which in turn leverages the NVIDIA TensorRT backend. Applications using Holoscan Inference need its matching AArch64 runtime and development packages, including TensorRT headers and link libraries; extracting the Core package alone does not supply them.

In the pinned package set, the holoscan-inference-cuda-13 metapackage depends on libholoscan-inference-dev-cuda-13. Download and inspect that development package and its matching Holoscan runtime dependencies, then extract those SDK packages into .cross/holoscan-root. Keep them out of the external dependency manifest below: resolving their complete apt dependency closure can pull in an AArch64 CUDA compiler, while this builder uses native NVCC and Cross-SBSA.

Choose a TensorRT release compatible with the selected Holoscan SDK 5 package, CUDA version, and target OS. Use the SDK's package metadata and NVIDIA's TensorRT Debian installation guide to identify the required development and runtime packages. For a project that needs TensorRT's inference library, plugins, and ONNX model parser, start the external dependency manifest with:

# Set this to the exact Debian version required by the selected SDK and target.
: "${TENSORRT_DEB_VERSION:?Set the matching TensorRT Debian package version}"

TARGET_PACKAGES=(
  "libnvinfer-dev:arm64=${TENSORRT_DEB_VERSION}"
  "libnvinfer-headers-dev:arm64=${TENSORRT_DEB_VERSION}"
  "libnvinfer-plugin-dev:arm64=${TENSORRT_DEB_VERSION}"
  "libnvinfer-headers-plugin-dev:arm64=${TENSORRT_DEB_VERSION}"
  "libnvonnxparsers-dev:arm64=${TENSORRT_DEB_VERSION}"
)

Review the simulated transaction below and add exact pins for the matching runtime packages to the manifest before downloading. Runtime package names depend on the TensorRT major version. Use the same TensorRT release on the target, where apt should install its runtime dependencies normally. On the builder, download and extract these packages rather than installing them beside any x86_64 TensorRT installation.

(
  set -e
  : "${TARGET_PACKAGES[0]:?Define a version-pinned third-party TARGET_PACKAGES manifest}"

  # Review the dependency closure without changing the builder
  apt-get "${APT_TARGET_OPTIONS[@]}" update
  apt-get "${APT_TARGET_OPTIONS[@]}" \
    --simulate --no-install-recommends install \
    "${TARGET_PACKAGES[@]}"

  # Download the reviewed dependency closure
  apt-get "${APT_TARGET_OPTIONS[@]}" \
    --download-only --no-install-recommends --yes install \
    "${TARGET_PACKAGES[@]}"

  # Extract the packages without running AArch64 maintainer scripts
  for package in "${TARGET_APT_ROOT}/archives/"*.deb; do
    package_name=$(dpkg-deb -f "${package}" Package)
    case "${package_name}" in
      holoscan*|libholoscan*)
        echo "Keep ${package_name} out of the external dependency root." >&2
        exit 1
        ;;
    esac
    dpkg-deb --extract "${package}" "${TARGET_PACKAGE_ROOT}"
  done
)

Request each additional component explicitly in CMake. For example, an inference application uses:

find_package(holoscan 5.0 REQUIRED CONFIG COMPONENTS core inference)
target_link_libraries(my_holoscan_app PRIVATE holoscan::core holoscan::op_inference)

This optional discussion covers TensorRT setup only. The hello-world build does not validate the Inference component or TensorRT; validate an application that actually uses them. Use the inference targets and CMake package discovery provided by the matching SDK 5 release.

Add the package root to CMake and validate it

In the project's copy of aarch64-cross-sbsa.cmake, add the optional package root before setting the CMAKE_FIND_ROOT_PATH_MODE_* variables:

set(HOLOSCAN_TARGET_PACKAGE_ROOT "" CACHE PATH
  "Root containing additional extracted AArch64 packages")
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
  HOLOSCAN_TARGET_PACKAGE_ROOT)

if(HOLOSCAN_TARGET_PACKAGE_ROOT)
  list(APPEND CMAKE_FIND_ROOT_PATH "${HOLOSCAN_TARGET_PACKAGE_ROOT}")
endif()

Pass the package root when configuring:

cmake ... \
  -DHOLOSCAN_TARGET_PACKAGE_ROOT="$PWD/.cross/target-root"

Build an application that actually links the selected backend; the hello-world executable cannot validate these dependencies. Check representative inputs and the completed executable without using host ldd:

(
  set -e
  set -o pipefail
  test -d .cross/target-root

  # Check the extracted TensorRT library when using the optional setup above.
  file -L .cross/target-root/usr/lib/aarch64-linux-gnu/libnvinfer.so \
    | grep -E 'ELF .*ARM aarch64'
  file -L .cross/holoscan-root/usr/lib/aarch64-linux-gnu/libholoscan_infer.so \
    | grep -E 'ELF .*ARM aarch64'
  file -L .cross/holoscan-root/usr/lib/aarch64-linux-gnu/libholoscan_op_inference.so \
    | grep -E 'ELF .*ARM aarch64'
  file build-cross/install/bin/my_holoscan_app | grep -E 'ELF .*ARM aarch64'

  # The app links the operator; the operator links the inference library.
  readelf --dynamic build-cross/install/bin/my_holoscan_app \
    | grep -F '[libholoscan_op_inference.so.5]'
  readelf --dynamic .cross/holoscan-root/usr/lib/aarch64-linux-gnu/libholoscan_op_inference.so \
    | grep -F '[libholoscan_infer.so.5]'

  libraries=$(find .cross/target-root -type f \
      \( -name '*.so' -o -name '*.so.*' \) -print0 \
      | xargs -0 -r file)
  if grep -q 'ELF .*x86-64' <<< "${libraries}"; then
    echo 'Found an x86_64 library in an AArch64 target root.' >&2
    exit 1
  fi
)

Run the resulting application on the supported Thor target to validate the target driver, ABI, backend plugins, and runtime package versions. Architecture and link checks on the builder do not replace that target-side test.

Deployment boundary

Cross-compilation proves that the application configures and links for AArch64. It does not prove runtime behavior. The target must provide a compatible Linux application binary interface (ABI), CUDA runtime/driver, matching Holoscan 5.0 libraries, and every application-specific shared library. Complete Deploy and validate on a Thor target to validate the executable on Jetson AGX Thor or IGX Thor hardware. Jetson or IGX camera, multimedia, networking, and other BSP-specific dependencies require matching packages and may require a sysroot from the exact target OS rather than the generic SBSA roots used here.

All tutorials