Tutorial · C++ · Version 2.0
Cross Compilation Example
Cross-compile a minimal Holoscan C++ application for Jetson AGX Thor and IGX Thor
| Authors | Zachary Crandall (NVIDIA) |
|---|---|
| Supported platforms | x86_64 |
| Language | C++ |
| Version | 2.0 |
| Compatibility | HSDK 5.0 |
| Contribution level | Level 1 · Very high-quality code |
| Source | holoscan-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:
- 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.
- 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.
fileandreadelf(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:
- Find the deployment GPU's compute capability in NVIDIA's
compute capability documentation.
When
nvidia-smiis available on the target, query it directly:
nvidia-smi --query-gpu=name,compute_cap --format=csv,noheader
- 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.
Find and link Holoscan
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:
- Declare the required AArch64 packages and exact versions in a project-owned manifest.
- Give apt isolated package status, repository metadata, and package-cache directories for arm64.
- Use
apt-get --download-onlyso apt resolves and downloads the dependency closure without installing it. This command does not populate the target package root. - Reject Holoscan packages in the download cache, then extract the external packages with
dpkg-deb --extract. - Append that root to
CMAKE_FIND_ROOT_PATHafter the SDK, GNU AArch64, and CUDA roots, then provide any package-specific CMake configuration. - 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.