# Cross-Platform Deployment of ApraPipes Pipelines: Challenges and Solutions

> Discover challenges and solutions for cross-platform deployment of ApraPipes pipelines. Learn how centralized filesystem operations and tailored CI workflows ensure reliability across platforms.

- Repository: [Apra Labs/aprapipes](https://github.com/apra-labs/aprapipes)
- Tags: deep-dive
- Published: 2026-02-25

---

**ApraPipes achieves reliable cross-platform deployment by centralizing filesystem operations in a Boost-backed PathUtils library, using declarative path-type validation, and tailoring CI workflows to handle Windows PowerShell, vcpkg triplets, and CUDA delay-loading.**

ApraPipes is a declarative multimedia-processing framework designed to run unchanged across Windows, Linux, macOS, and ARM-based Jetson devices. Successful cross-platform deployment of ApraPipes pipelines requires handling filesystem semantics, library loading, and build configuration differences that vary by operating system.

## Core Architectural Challenges

Deploying a single pipeline definition across heterogeneous environments exposes several platform-specific failure modes.

### Path Separator and Normalization

Windows uses backslashes (`\`) while POSIX systems use forward slashes (`/`). Naïve string concatenation produces invalid paths, particularly when handling wildcard patterns like `frame_????.jpg`.

The `PathUtils::normalizePath` function in [`base/src/declarative/PathUtils.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/declarative/PathUtils.cpp) (lines 31‑34) delegates to **Boost.Filesystem** to produce platform-native strings, ensuring consistent behavior across operating systems.

### DLL and Shared Library Loading

On Windows, the Git-Bash environment mangles the `PATH` variable when launching CLI tools, causing `STATUS_DLL_NOT_FOUND` (exit 127) errors during shared-library resolution.

The Sprint-12 fix switched integration tests to native PowerShell via `examples/test_all_examples.ps1`, ensuring the Windows `PATH` is honored during test execution.

### Executable Extensions

Linux and macOS invoke `./aprapipesut` directly, while Windows requires `aprapipesut.exe`. Bash scripts that ignore the extension fail to locate the runner on Windows.

The test driver in [`examples/test_all_examples.sh`](https://github.com/apra-labs/aprapipes/blob/main/examples/test_all_examples.sh) (lines 236‑260) explicitly checks the platform and appends `.exe` when `OS` equals `Windows`:

```bash
if [[ "$OS" == "Windows" ]]; then
    CLI="${CLI}.exe"
fi

```

### CUDA Delay-Load on Windows

CUDA DLLs may be absent on machines without NVIDIA GPUs. Without delay-loading, the executable aborts immediately on startup when the CUDA runtime is missing.

The CMake configuration in [`base/CMakeLists.txt`](https://github.com/apra-labs/aprapipes/blob/main/base/CMakeLists.txt) (lines 865‑1072) adds `/DELAYLOAD:nvcuda.dll` on Windows builds, allowing the CLI to start and gracefully detect missing GPU support at runtime.

### Vcpkg Triplet Selection

Windows requires a custom triplet (`x64-windows-cuda`) to pull the correct static/dynamic libraries, while Linux and ARM targets use default triplets.

The reusable workflow in [`.github/workflows/build-test.yml`](https://github.com/apra-labs/aprapipes/blob/main/.github/workflows/build-test.yml) (line 57) sets `VCPKG_DEFAULT_TRIPLET` only for Windows runners:

```yaml
- name: Set vcpkg triplet
  if: inputs.os == 'windows'
  run: echo "VCPKG_DEFAULT_TRIPLET=x64-windows-cuda" >> $GITHUB_ENV

```

### Cross-Platform CI Orchestration

A single workflow must drive both Bash (Linux/macOS) and PowerShell (Windows) while preserving a single source of truth for build flags.

The reusable workflow [`.github/workflows/build-test.yml`](https://github.com/apra-labs/aprapipes/blob/main/.github/workflows/build-test.yml) uses conditional `if: inputs.os == 'windows'` blocks to install tools, set environment variables, and run the correct test script (see the Windows-specific "Prepare builder" section, lines 21‑31).

## Solutions Implemented

### Unified Path-Handling API

The **PathUtils** module ([`base/include/declarative/PathUtils.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/declarative/PathUtils.h) and [`base/src/declarative/PathUtils.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/declarative/PathUtils.cpp)) provides:

- Normalization of separators using Boost.Filesystem
- Existence validation and writeability checks
- Automatic creation of missing directories
- Wildcard expansion for patterns containing `?`

The `PathValidationResult` struct (lines 18‑24 of [`PathUtils.h`](https://github.com/apra-labs/aprapipes/blob/main/PathUtils.h)) bundles **valid**, **error**, **warning**, **normalized_path**, and **directory_created** fields, allowing modules to handle platform-specific edge cases programmatically.

`ModuleFactory` integrates this by calling `path_utils::normalizePath` before storing user-provided paths (line 666 of [`base/src/declarative/ModuleFactory.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/declarative/ModuleFactory.cpp)).

### Declarative Path-Type System

The framework exposes **PathType** and **PathRequirement** enums (documented in [`docs/declarative-pipeline/PATH_TYPES_PLAN.md`](https://github.com/apra-labs/aprapipes/blob/main/docs/declarative-pipeline/PATH_TYPES_PLAN.md)). Factory helpers like `PropDef::filePath(name, PathRequirement::MustExist)` let modules declare expectations up-front, enabling early validation and auto-creation of parent directories for writers.

### Windows-Specific Test Harness

The **PowerShell Test Wrapper** (`examples/test_all_examples.ps1`) runs the same test matrix as the Bash version but under native PowerShell. This guarantees that the Windows loader sees the correct `PATH` and that `.exe` extensions are resolved without Git-Bash interference.

### Build and CI Adjustments

- **CMake delay-load for CUDA**: Adds `/DELAYLOAD:nvcuda.dll` on Windows so the CLI can start on machines without GPUs ([`base/CMakeLists.txt`](https://github.com/apra-labs/aprapipes/blob/main/base/CMakeLists.txt) lines 865‑1072).
- **Vcpkg triplet selection**: Sets `VCPKG_DEFAULT_TRIPLET` only for Windows runners ([`.github/workflows/build-test.yml`](https://github.com/apra-labs/aprapipes/blob/main/.github/workflows/build-test.yml) line 57).
- **Environment-variable handling**: Adds CUDA `bin` folder to `GITHUB_PATH` for both Linux and Windows, using PowerShell syntax on Windows (`$env:GITHUB_PATH`) ([`.github/workflows/build-test.yml`](https://github.com/apra-labs/aprapipes/blob/main/.github/workflows/build-test.yml) lines 57‑63).
- **Artifact naming**: SDK packages use consistent naming (`aprapipes-sdk-{os}-{arch}`) per [`docs/declarative-pipeline/SDK_PACKAGING_PLAN.md`](https://github.com/apra-labs/aprapipes/blob/main/docs/declarative-pipeline/SDK_PACKAGING_PLAN.md).
- **Integration-test entry point**: [`CI-Windows.yml`](https://github.com/apra-labs/aprapipes/blob/main/CI-Windows.yml) calls the reusable workflow with `os: windows` and sets the badge flavor to "Windows" (lines 35‑41).

## Practical Code Examples

### Normalizing a User-Provided File Path

```cpp
#include "declarative/PathUtils.h"

std::string raw = R"(.\data\output\frame_????.jpg)";
auto norm = apra::path_utils::normalizePath(raw);
// On Windows -> ".\\data\\output\\frame_????.jpg"
// On Linux   -> "./data/output/frame_????.jpg"

```

The `normalizePath` function delegates to `boost::filesystem::path::make_preferred()` (see lines 31‑34 of [`PathUtils.cpp`](https://github.com/apra-labs/aprapipes/blob/main/PathUtils.cpp)).

### Validating a Writer-Module Output Directory

```cpp
auto result = apra::path_utils::validatePath(
    "./data/output/",
    apra::PathType::DirectoryPath,
    apra::PathRequirement::WillBeCreated);

if (!result.valid) {
    std::cerr << "Path error: " << result.error << std::endl;
}

```

The result contains `directory_created == true` if the SDK had to create the folder.

### Declaring a Path Property in a Module

```cpp
PropDef::filePath("strFullFileNameWithPattern",
                  PathRequirement::ParentMustExist)
    .required()
    .description("Output file pattern with ???? wildcards");

```

This declaration causes the pipeline builder to automatically call `validatePath` before execution, emitting clear errors such as "Parent directory './data/output' does not exist" when constraints are violated.

### PowerShell Test Driver

```powershell

# examples/test_all_examples.ps1

$cli = Resolve-Path "bin\aprapipes_cli.exe"
& $cli list-modules
& $cli run examples/simple.json

```

This runs the same functional suite as the Bash script but avoids the Git-Bash PATH conversion bug.

## Summary

- **Path normalization** is handled centrally by `PathUtils` using Boost.Filesystem, eliminating separator mismatches between Windows and POSIX systems.
- **Library loading** issues on Windows are circumvented by using native PowerShell for integration tests rather than Git-Bash, ensuring correct PATH resolution.
- **Executable extensions** are managed through explicit platform checks in test drivers, appending `.exe` only on Windows.
- **CUDA dependencies** are delay-loaded on Windows via CMake `/DELAYLOAD` flags, allowing the CLI to start on machines without GPUs.
- **Build configuration** uses conditional vcpkg triplets and environment variables in reusable GitHub Actions workflows to handle platform-specific dependencies.

## Frequently Asked Questions

### How does ApraPipes handle different path separators on Windows and Linux?

ApraPipes centralizes path handling in the `PathUtils` module ([`base/src/declarative/PathUtils.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/declarative/PathUtils.cpp)), which uses Boost.Filesystem to normalize separators. The `normalizePath` function converts any input path to the platform-native format—backslashes on Windows and forward slashes on Linux—ensuring that wildcard patterns like `frame_????.jpg` resolve correctly regardless of the host OS.

### Why does the Windows CI use PowerShell instead of Git-Bash for testing?

The Windows CI uses a native PowerShell test driver (`examples/test_all_examples.ps1`) because Git-Bash mangles the `PATH` environment variable when launching executables, causing `STATUS_DLL_NOT_FOUND` (exit 127) errors during shared-library resolution. PowerShell preserves the Windows `PATH` correctly, allowing the test suite to locate required DLLs and execute the `aprapipesut.exe` binary without modification.

### How does ApraPipes ensure the CLI starts on machines without CUDA support?

On Windows, the CMake configuration in [`base/CMakeLists.txt`](https://github.com/apra-labs/aprapipes/blob/main/base/CMakeLists.txt) (lines 865‑1072) applies the `/DELAYLOAD:nvcuda.dll` linker flag, which instructs the Windows loader to defer resolving the CUDA DLL until the application explicitly calls a CUDA function. This allows the ApraPipes CLI to start on machines without NVIDIA GPUs, where the framework can then gracefully detect the missing hardware and fall back to CPU-only processing paths.

### What mechanism validates file paths before pipeline execution?

ApraPipes implements a declarative path-type system through the `PathUtils` API and `PropDef` helpers. When a module declares a property using `PropDef::filePath("name", PathRequirement::MustExist)`, the `ModuleFactory` automatically invokes `path_utils::validatePath` during pipeline construction. This returns a `PathValidationResult` struct containing `valid`, `error`, `normalized_path`, and `directory_created` fields, enabling early detection of missing directories or permission issues before the pipeline executes.