Cross-Platform Deployment of ApraPipes Pipelines: Challenges and Solutions

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 (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 (lines 236‑260) explicitly checks the platform and appends .exe when OS equals Windows:

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 (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 (line 57) sets VCPKG_DEFAULT_TRIPLET only for Windows runners:

- 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 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 and 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) 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).

Declarative Path-Type System

The framework exposes PathType and PathRequirement enums (documented in 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 lines 865‑1072).
  • Vcpkg triplet selection: Sets VCPKG_DEFAULT_TRIPLET only for Windows runners (.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 lines 57‑63).
  • Artifact naming: SDK packages use consistent naming (aprapipes-sdk-{os}-{arch}) per docs/declarative-pipeline/SDK_PACKAGING_PLAN.md.
  • Integration-test entry point: 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

#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).

Validating a Writer-Module Output Directory

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

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


# 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), 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 (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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →