yams/conan/onnxruntime/conanfile.py

1071 lines
45 KiB
Python

from conan import ConanFile
from conan.tools.files import get, copy, save, mkdir, download, chdir
from conan.tools.scm import Git
from conan.errors import ConanInvalidConfiguration
import os
import shutil
import subprocess
class OnnxRuntimeConan(ConanFile):
name = "onnxruntime"
# NOTE: Windows DirectML binaries are distributed via NuGet and the latest
# DirectML package versions may lag behind GitHub release tags.
# As of Feb 2026, Microsoft.ML.OnnxRuntime.DirectML is published as 1.23.0.
version = "1.23.0"
license = "MIT"
url = "https://github.com/microsoft/onnxruntime"
description = "ONNX Runtime: cross-platform, high performance ML inferencing and training accelerator"
topics = ("onnx", "machine-learning", "inference", "neural-network")
settings = "os", "compiler", "build_type", "arch"
options = {
"shared": [True, False],
"fPIC": [True, False],
# GPU acceleration options:
# - cuda: NVIDIA CUDA (prebuilt binary)
# - directml: Windows DirectML (prebuilt binary)
# - coreml: macOS CoreML (prebuilt binary)
# - migraphx: AMD ROCm MIGraphX (built from source, requires ROCm)
"with_gpu": [None, "cuda", "directml", "coreml", "migraphx"],
# ROCm installation path (for migraphx builds)
"rocm_path": ["ANY"],
# Parallel build jobs (for source builds)
"parallel_jobs": ["ANY"],
# Use prebuilt Python package instead of building from source
"use_prebuilt_python": [True, False],
# Use system-installed onnxruntime (e.g. Homebrew, apt, manual install)
"use_system": [True, False],
# Prefix path for system-installed onnxruntime (auto-detected if empty)
"system_prefix": ["ANY"],
}
default_options = {
"shared": False,
"fPIC": True,
"with_gpu": None,
"rocm_path": "/opt/rocm",
"parallel_jobs": "4",
"use_prebuilt_python": False,
"use_system": False,
"system_prefix": "",
}
# Source builds need these
exports_sources = []
def requirements(self):
# ONNX Runtime vendored most dependencies
# For source builds, we rely on system ROCm/MIGraphX
pass
def build_requirements(self):
if self.options.with_gpu == "migraphx":
# Source builds need CMake 3.28+
self.tool_requires("cmake/[>=3.28]")
def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC
def _detect_system_prefix(self):
"""Auto-detect system onnxruntime installation prefix.
Search order:
1. Explicit system_prefix option
2. pkg-config --variable=prefix onnxruntime
3. Homebrew prefix (macOS)
4. Common system paths (/usr/local, /usr, /opt/homebrew)
"""
explicit = str(self.options.system_prefix)
if explicit:
return explicit
# Try pkg-config
try:
result = subprocess.run(
["pkg-config", "--variable=prefix", "onnxruntime"],
capture_output=True, text=True, timeout=5,
)
if result.returncode == 0 and result.stdout.strip():
prefix = result.stdout.strip()
if os.path.isdir(prefix):
return prefix
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
# Try brew --prefix onnxruntime (macOS)
if str(self.settings.os) == "Macos":
try:
result = subprocess.run(
["brew", "--prefix", "onnxruntime"],
capture_output=True, text=True, timeout=5,
)
if result.returncode == 0 and result.stdout.strip():
prefix = result.stdout.strip()
if os.path.isdir(prefix):
return prefix
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
# Probe common system prefixes
candidates = []
if str(self.settings.os) == "Macos":
candidates = ["/opt/homebrew", "/usr/local"]
else:
candidates = ["/usr/local", "/usr"]
for prefix in candidates:
if self._has_onnxruntime_lib(prefix) and self._has_onnxruntime_header(prefix):
return prefix
return None
@staticmethod
def _has_onnxruntime_lib(prefix):
"""Check if libonnxruntime exists under prefix/lib."""
lib_dir = os.path.join(prefix, "lib")
if not os.path.isdir(lib_dir):
return False
for name in os.listdir(lib_dir):
if name.startswith("libonnxruntime") and (
name.endswith(".dylib") or name.endswith(".so") or ".so." in name
):
return True
return False
@staticmethod
def _has_onnxruntime_header(prefix):
"""Check if onnxruntime_c_api.h exists in any known layout.
Layouts:
- Flat: include/onnxruntime_c_api.h (prebuilt archives)
- Homebrew: include/onnxruntime/onnxruntime_c_api.h (brew install)
- Source: include/onnxruntime/core/session/onnxruntime_c_api.h
- Linux: include/onnxruntime/core/session/onnxruntime_c_api.h (apt/manual)
"""
include_dir = os.path.join(prefix, "include")
return any(os.path.exists(os.path.join(include_dir, p)) for p in [
"onnxruntime_c_api.h",
os.path.join("onnxruntime", "onnxruntime_c_api.h"),
os.path.join("onnxruntime", "core", "session", "onnxruntime_c_api.h"),
])
@staticmethod
def _find_onnxruntime_header(prefix):
"""Return the path to onnxruntime_c_api.h under prefix, or None."""
include_dir = os.path.join(prefix, "include")
for rel in [
"onnxruntime_c_api.h",
os.path.join("onnxruntime", "onnxruntime_c_api.h"),
os.path.join("onnxruntime", "core", "session", "onnxruntime_c_api.h"),
]:
candidate = os.path.join(include_dir, rel)
if os.path.exists(candidate):
return candidate
return None
def _validate_system_install(self, prefix):
"""Validate that system onnxruntime has required headers and library."""
lib_dir = os.path.join(prefix, "lib")
include_dir = os.path.join(prefix, "include")
if not self._has_onnxruntime_lib(prefix):
raise ConanInvalidConfiguration(
f"System onnxruntime library not found in {lib_dir}. "
f"Install onnxruntime or set -o onnxruntime/*:use_system=False"
)
if not self._has_onnxruntime_header(prefix):
raise ConanInvalidConfiguration(
f"System onnxruntime headers not found under {include_dir}. "
f"Checked flat (include/), Homebrew (include/onnxruntime/), "
f"and source (include/onnxruntime/core/session/) layouts. "
f"Install onnxruntime development headers or set -o onnxruntime/*:use_system=False"
)
# Detect version from header
version_header = self._find_onnxruntime_header(prefix)
if version_header:
self._detect_system_version(version_header)
self.output.info(f"System onnxruntime validated at {prefix}")
return True
def _detect_system_version(self, header_path):
"""Read ORT API version from onnxruntime_c_api.h."""
try:
with open(header_path, "r", encoding="utf-8") as f:
content = f.read()
import re
major = re.search(r"ORT_API_VERSION\s+([0-9]+)", content)
minor_match = re.search(r"ORT_API_MINOR_VERSION\s+([0-9]+)", content)
# Fallback patterns used in different ORT versions
if not major:
major = re.search(r"ORT_API_VERSION_MAJOR\s+([0-9]+)", content)
if not minor_match:
minor_match = re.search(r"ORT_API_VERSION_MINOR\s+([0-9]+)", content)
if major:
ver = major.group(1)
if minor_match:
ver += f".{minor_match.group(1)}"
self.output.info(f"System onnxruntime API version: {ver}")
except Exception:
pass
def configure(self):
if self.options.shared:
self.options.rm_safe("fPIC")
# When using system onnxruntime, validate the installation
if self.options.use_system:
prefix = self._detect_system_prefix()
if prefix is None:
raise ConanInvalidConfiguration(
"use_system=True but no system onnxruntime installation found. "
"Install onnxruntime (e.g., 'brew install onnxruntime') or set "
"-o onnxruntime/*:system_prefix=/path/to/prefix"
)
self._validate_system_install(prefix)
# Store resolved prefix for later stages
self._system_prefix_resolved = prefix
self.output.info(f"Using system onnxruntime from: {prefix}")
return # Skip GPU validation for system builds
# Validate GPU options per platform
gpu = self.options.with_gpu
os_name = str(self.settings.os)
if gpu == "cuda":
if os_name not in ["Linux", "Windows"]:
raise ConanInvalidConfiguration(
f"CUDA is only supported on Linux and Windows, not {os_name}"
)
elif gpu == "directml":
if os_name != "Windows":
raise ConanInvalidConfiguration(
f"DirectML is only supported on Windows, not {os_name}"
)
elif gpu == "coreml":
if os_name != "Macos":
raise ConanInvalidConfiguration(
f"CoreML is only supported on macOS, not {os_name}"
)
elif gpu == "migraphx":
if os_name != "Linux":
raise ConanInvalidConfiguration(
f"MIGraphX is only supported on Linux, not {os_name}"
)
# Validate ROCm installation
rocm_path = str(self.options.rocm_path)
if not os.path.isdir(rocm_path):
raise ConanInvalidConfiguration(
f"ROCm not found at {rocm_path}. Install ROCm or set "
f"-o onnxruntime/*:rocm_path=/path/to/rocm"
)
# Check for MIGraphX library (ROCm 7.x uses libmigraphx_c.so)
migraphx_lib = os.path.join(rocm_path, "lib", "libmigraphx.so")
migraphx_c_lib = os.path.join(rocm_path, "lib", "libmigraphx_c.so")
if not os.path.exists(migraphx_lib) and not os.path.exists(migraphx_c_lib):
raise ConanInvalidConfiguration(
f"MIGraphX not found at {rocm_path}. Ensure MIGraphX is installed "
f"(typically part of ROCm or install separately)."
)
self.output.info(f"MIGraphX found at {rocm_path}")
def _is_source_build(self):
"""Check if this GPU option requires building from source"""
return self.options.with_gpu == "migraphx"
def _get_download_url(self):
"""Get platform-specific download URL for prebuilt binaries"""
base_url = f"https://github.com/microsoft/onnxruntime/releases/download/v{self.version}"
os_name = str(self.settings.os)
arch = str(self.settings.arch)
gpu = str(self.options.with_gpu) if self.options.with_gpu else None
if os_name == "Linux":
if arch == "x86_64":
if gpu == "cuda":
filename = f"onnxruntime-linux-x64-gpu-{self.version}.tgz"
else:
filename = f"onnxruntime-linux-x64-{self.version}.tgz"
elif arch == "aarch64" or arch == "armv8":
if gpu == "cuda":
raise ConanInvalidConfiguration(
"CUDA GPU build not available for Linux ARM64"
)
filename = f"onnxruntime-linux-aarch64-{self.version}.tgz"
else:
raise ConanInvalidConfiguration(f"Unsupported arch: {arch}")
elif os_name == "Macos":
if arch == "x86_64":
filename = f"onnxruntime-osx-x86_64-{self.version}.tgz"
elif arch == "armv8":
filename = f"onnxruntime-osx-arm64-{self.version}.tgz"
else:
raise ConanInvalidConfiguration(f"Unsupported arch: {arch}")
elif os_name == "Windows":
if arch == "x86_64":
if gpu == "cuda":
filename = f"onnxruntime-win-x64-gpu-{self.version}.zip"
elif gpu == "directml":
return self._get_directml_url()
else:
filename = f"onnxruntime-win-x64-{self.version}.zip"
else:
raise ConanInvalidConfiguration(f"Unsupported arch: {arch}")
else:
raise ConanInvalidConfiguration(f"Unsupported OS: {os_name}")
return f"{base_url}/{filename}"
def _get_directml_url(self):
"""Get DirectML package URL from NuGet"""
return f"https://www.nuget.org/api/v2/package/Microsoft.ML.OnnxRuntime.DirectML/{self.version}"
def source(self):
# Source download happens in build() where options are available
pass
def _build_from_source(self):
"""Build ONNX Runtime from source with MIGraphX support"""
rocm_path = str(self.options.rocm_path)
build_type = str(self.settings.build_type)
parallel_jobs = str(self.options.parallel_jobs)
# Clone the repository (reuse existing checkout when possible)
source_dir = os.path.join(self.build_folder, "onnxruntime-src")
if os.path.exists(source_dir):
contents = os.listdir(source_dir)
if contents:
self.output.info(
f"Reusing existing ONNX Runtime checkout at {source_dir}"
)
else:
self.output.info(
f"Empty ONNX Runtime checkout at {source_dir}; recloning"
)
if not os.path.exists(source_dir) or not os.listdir(source_dir):
self.output.info(f"Cloning ONNX Runtime v{self.version}...")
git = Git(self, folder=source_dir)
git.clone(
url="https://github.com/microsoft/onnxruntime.git",
target=".",
args=["--depth", "1", "--branch", f"v{self.version}", "--recursive"],
)
build_script = os.path.join(source_dir, "build.sh")
if not os.path.exists(build_script):
raise ConanInvalidConfiguration(
f"build.sh not found at {build_script}. Source checkout may have failed."
)
# Patch tpause builtin signature for newer Clang toolchains
spin_pause_path = os.path.join(
source_dir, "onnxruntime", "core", "common", "spin_pause.cc"
)
if os.path.exists(spin_pause_path):
with open(spin_pause_path, "r", encoding="utf-8") as handle:
spin_pause_src = handle.read()
old_call = (
"__builtin_ia32_tpause(0x0, __rdtsc() + tpause_spin_delay_cycles);"
)
new_call = (
"__builtin_ia32_tpause(0x0, __rdtsc() + tpause_spin_delay_cycles, 0);"
)
if old_call in spin_pause_src and new_call not in spin_pause_src:
spin_pause_src = spin_pause_src.replace(old_call, new_call)
with open(spin_pause_path, "w", encoding="utf-8") as handle:
handle.write(spin_pause_src)
self.output.info("Patched spin_pause.cc for tpause builtin signature")
# Prepare build arguments
build_args = [
build_script,
"--config",
build_type,
"--build_dir",
self.build_folder,
"--use_migraphx",
"--migraphx_home",
rocm_path,
"--rocm_home",
rocm_path,
"--parallel",
parallel_jobs,
"--skip_tests",
"--build_shared_lib",
"--cmake_extra_defines",
f"CMAKE_HIP_COMPILER={rocm_path}/llvm/bin/clang++",
"--cmake_extra_defines",
"CMAKE_CXX_FLAGS=-Wno-array-bounds -Wno-error=array-bounds -Wno-error=shorten-64-to-32",
"--cmake_extra_defines",
"CMAKE_CXX_FLAGS_RELEASE=-Wno-array-bounds -Wno-error=array-bounds -Wno-error=shorten-64-to-32",
"--cmake_extra_defines",
"CMAKE_CXX_FLAGS_DEBUG=-Wno-array-bounds -Wno-error=array-bounds -Wno-error=shorten-64-to-32",
"--cmake_extra_defines",
"CMAKE_HIP_FLAGS=-Wno-array-bounds -Wno-error=array-bounds -Wno-error=shorten-64-to-32",
"--cmake_extra_defines",
"CMAKE_HIP_FLAGS_RELEASE=-Wno-array-bounds -Wno-error=array-bounds -Wno-error=shorten-64-to-32",
"--cmake_extra_defines",
"CMAKE_HIP_FLAGS_DEBUG=-Wno-array-bounds -Wno-error=array-bounds -Wno-error=shorten-64-to-32",
"--cmake_extra_defines",
"onnxruntime_DEV_MODE=OFF",
"--cmake_extra_defines",
"onnxruntime_BUILD_UNIT_TESTS=OFF",
]
# GPU targets - use auto-detected from environment or fall back to common targets
# Auto-detection happens in setup.sh via rocminfo
# Common architectures:
# gfx906 = Vega 20 (MI50/60, Radeon VII)
# gfx908 = CDNA (MI100)
# gfx90a = CDNA2 (MI200 series)
# gfx942 = CDNA3 (MI300 series)
# gfx1030 = RDNA2 (RX 6800/6900)
# gfx1100 = RDNA3 (RX 7900)
gpu_targets = os.environ.get("YAMS_ROCM_GPU_TARGETS", "")
if gpu_targets:
self.output.info(f"Using GPU targets from environment: {gpu_targets}")
build_args.extend(
[
"--cmake_extra_defines",
f"GPU_TARGETS={gpu_targets}",
]
)
else:
self.output.info(
"No GPU targets specified, ONNX Runtime will use its defaults"
)
self.output.info(f"Building ONNX Runtime with MIGraphX support...")
self.output.info(f"ROCm path: {rocm_path}")
self.output.info(f"Build type: {build_type}")
self.output.info(f"Build command: {' '.join(build_args)}")
# Set environment for ROCm
env = os.environ.copy()
env["ROCM_PATH"] = rocm_path
env["HIP_PATH"] = rocm_path
env["PATH"] = f"{rocm_path}/bin:{rocm_path}/llvm/bin:{env.get('PATH', '')}"
env["LD_LIBRARY_PATH"] = f"{rocm_path}/lib:{env.get('LD_LIBRARY_PATH', '')}"
# Run the build
try:
subprocess.run(
build_args,
cwd=source_dir,
env=env,
check=True,
capture_output=False, # Let output stream to console
)
except subprocess.CalledProcessError as e:
raise ConanInvalidConfiguration(
f"ONNX Runtime build failed with exit code {e.returncode}. "
f"Check the build output for errors."
)
def build(self):
if self.options.use_system:
self.output.info("Using system onnxruntime - skipping build")
return
if self.options.use_prebuilt_python:
# Skip build - we'll package from Python installation
self.output.info("Using prebuilt Python package - skipping build")
return
if self._is_source_build():
self._build_from_source()
else:
# Download prebuilt binaries
url = self._get_download_url()
self.output.info(f"Downloading ONNX Runtime from: {url}")
get(self, url, destination=self.build_folder, strip_root=False)
def _package_source_build(self):
"""Package libraries from source build"""
build_type = str(self.settings.build_type)
# ONNX Runtime build.sh creates output in build_folder/<build_type>
build_output = os.path.join(self.build_folder, build_type)
if not os.path.isdir(build_output):
# Sometimes it's just in the build folder directly
build_output = self.build_folder
# Or try to find it
for item in os.listdir(self.build_folder):
candidate = os.path.join(self.build_folder, item)
if os.path.isdir(candidate) and item in [
"Release",
"Debug",
"RelWithDebInfo",
]:
build_output = candidate
break
self.output.info(f"Packaging from build output: {build_output}")
# Find and copy headers from source (cloned in build step)
source_dir = os.path.join(self.build_folder, "onnxruntime-src")
include_src = os.path.join(source_dir, "include", "onnxruntime")
# Copy main headers
if os.path.exists(include_src):
copy(
self,
"*.h",
src=include_src,
dst=os.path.join(self.package_folder, "include", "onnxruntime"),
keep_path=True,
)
# Also copy top-level public headers to include/
session_headers = os.path.join(include_src, "core", "session")
for header in [
"onnxruntime_c_api.h",
"onnxruntime_cxx_api.h",
"onnxruntime_ep_c_api.h",
"onnxruntime_float16.h",
"onnxruntime_cxx_inline.h",
]:
copy(
self,
header,
src=session_headers,
dst=os.path.join(self.package_folder, "include"),
keep_path=False,
)
# Also copy the C API headers
c_api_include = os.path.join(
source_dir, "include", "onnxruntime", "core", "session"
)
if os.path.exists(c_api_include):
copy(
self,
"*.h",
src=c_api_include,
dst=os.path.join(
self.package_folder, "include", "onnxruntime", "core", "session"
),
keep_path=True,
)
# Copy built libraries
lib_dst = os.path.join(self.package_folder, "lib")
mkdir(self, lib_dst)
# Look for libraries in various possible locations
lib_search_paths = [
build_output,
os.path.join(build_output, "lib"),
os.path.join(build_output, "Release"),
os.path.join(build_output, "Release", "lib"),
]
for search_path in lib_search_paths:
if not os.path.isdir(search_path):
continue
for item in os.listdir(search_path):
src_path = os.path.join(search_path, item)
if not os.path.isfile(src_path) and not os.path.islink(src_path):
continue
# Copy library files
if item.endswith((".so", ".a")) or ".so." in item:
dst_path = os.path.join(lib_dst, item)
if os.path.islink(src_path):
link_target = os.readlink(src_path)
if os.path.exists(dst_path) or os.path.islink(dst_path):
os.remove(dst_path)
os.symlink(link_target, dst_path)
self.output.info(f"Preserved symlink: {item} -> {link_target}")
else:
shutil.copy2(src_path, dst_path)
self.output.info(f"Copied library: {item}")
# Copy license
license_path = os.path.join(source_dir, "LICENSE")
if os.path.exists(license_path):
copy(
self,
"LICENSE",
src=source_dir,
dst=os.path.join(self.package_folder, "licenses"),
keep_path=False,
)
def _package_prebuilt(self):
"""Package prebuilt binaries"""
gpu = str(self.options.with_gpu) if self.options.with_gpu else None
# Windows DirectML builds are distributed via NuGet (nupkg). Conan's get()
# extracts the nupkg into the build folder root (no onnxruntime-* directory),
# with native artifacts typically under runtimes/win-x64/native.
if str(self.settings.os) == "Windows" and gpu == "directml" and os.path.isdir(
os.path.join(self.build_folder, "runtimes")
):
native_candidates = [
os.path.join(self.build_folder, "runtimes", "win-x64", "native"),
os.path.join(self.build_folder, "runtimes", "win10-x64", "native"),
]
native_src = next(
(p for p in native_candidates if os.path.isdir(p)),
None,
)
if not native_src:
self.output.error("DirectML NuGet layout detected but native folder not found")
self.output.info(f"Build folder contents: {os.listdir(self.build_folder)}")
raise ConanInvalidConfiguration(
"ONNX Runtime DirectML native binaries not found in NuGet package"
)
lib_dst = os.path.join(self.package_folder, "lib")
bin_dst = os.path.join(self.package_folder, "bin")
mkdir(self, lib_dst)
mkdir(self, bin_dst)
copied_any = False
for item in os.listdir(native_src):
src_path = os.path.join(native_src, item)
if not os.path.isfile(src_path) and not os.path.islink(src_path):
continue
if item.endswith(".dll"):
shutil.copy2(src_path, os.path.join(bin_dst, item))
self.output.info(f"Copied library: {item}")
copied_any = True
elif item.endswith(".lib"):
shutil.copy2(src_path, os.path.join(lib_dst, item))
self.output.info(f"Copied library: {item}")
copied_any = True
if not copied_any:
raise ConanInvalidConfiguration(
"NuGet package did not contain any .dll/.lib artifacts under runtimes/*/native"
)
# Headers are not consistently shipped in the NuGet package; try common paths,
# then fall back to downloading the official CPU zip just for headers.
include_src = None
include_candidates = [
os.path.join(self.build_folder, "build", "native", "include"),
os.path.join(self.build_folder, "build", "include"),
os.path.join(self.build_folder, "include"),
]
include_src = next((p for p in include_candidates if os.path.isdir(p)), None)
if not include_src:
headers_root = os.path.join(self.build_folder, "_headers")
mkdir(self, headers_root)
cpu_zip = (
f"https://github.com/microsoft/onnxruntime/releases/download/v{self.version}/"
f"onnxruntime-win-x64-{self.version}.zip"
)
self.output.info(f"Downloading ONNX Runtime headers from: {cpu_zip}")
get(self, cpu_zip, destination=headers_root, strip_root=False)
# Find extracted onnxruntime-* directory inside headers_root
extracted_dir = None
for item in os.listdir(headers_root):
item_path = os.path.join(headers_root, item)
if os.path.isdir(item_path) and item.startswith("onnxruntime-"):
extracted_dir = item_path
break
if extracted_dir:
candidate = os.path.join(extracted_dir, "include")
if os.path.isdir(candidate):
include_src = candidate
if not include_src:
self.output.error("Could not locate ONNX Runtime headers for DirectML packaging")
raise ConanInvalidConfiguration(
"ONNX Runtime headers not found (NuGet package lacks headers and header fallback failed)"
)
copy(
self,
"*.h",
src=include_src,
dst=os.path.join(self.package_folder, "include"),
keep_path=True,
)
copy(
self,
"*.hpp",
src=include_src,
dst=os.path.join(self.package_folder, "include"),
keep_path=True,
)
# Copy license/docs if present
for license_name in ["LICENSE", "ThirdPartyNotices.txt"]:
if os.path.exists(os.path.join(self.build_folder, license_name)):
copy(
self,
license_name,
src=self.build_folder,
dst=os.path.join(self.package_folder, "licenses"),
keep_path=False,
)
return
# Default behavior: archives extracted into an onnxruntime-* root with include/ and lib/
extracted_dir = None
for item in os.listdir(self.build_folder):
item_path = os.path.join(self.build_folder, item)
if os.path.isdir(item_path) and item.startswith("onnxruntime-"):
extracted_dir = item_path
break
if not extracted_dir:
self.output.error("Could not find extracted onnxruntime directory")
self.output.info(f"Build folder contents: {os.listdir(self.build_folder)}")
raise ConanInvalidConfiguration(
"ONNX Runtime directory not found after extraction"
)
include_src = os.path.join(extracted_dir, "include")
if not os.path.exists(include_src):
self.output.error(f"Include directory not found at {include_src}")
self.output.info(f"Extracted dir contents: {os.listdir(extracted_dir)}")
raise ConanInvalidConfiguration(
"ONNX Runtime headers not found in expected location"
)
copy(
self,
"*.h",
src=include_src,
dst=os.path.join(self.package_folder, "include"),
keep_path=True,
)
copy(
self,
"*.hpp",
src=include_src,
dst=os.path.join(self.package_folder, "include"),
keep_path=True,
)
lib_src = os.path.join(extracted_dir, "lib")
lib_dst = os.path.join(self.package_folder, "lib")
bin_dst = os.path.join(self.package_folder, "bin")
mkdir(self, lib_dst)
for item in os.listdir(lib_src):
src_path = os.path.join(lib_src, item)
if item.endswith(".dll"):
mkdir(self, bin_dst)
dst_path = os.path.join(bin_dst, item)
elif (
item.endswith((".so", ".a", ".dylib", ".lib"))
or ".so." in item
or ".dylib." in item
):
dst_path = os.path.join(lib_dst, item)
else:
continue
if os.path.islink(src_path):
link_target = os.readlink(src_path)
if os.path.exists(dst_path) or os.path.islink(dst_path):
os.remove(dst_path)
os.symlink(link_target, dst_path)
self.output.info(f"Preserved symlink: {item} -> {link_target}")
elif os.path.isfile(src_path):
shutil.copy2(src_path, dst_path)
self.output.info(f"Copied library: {item}")
license_path = os.path.join(extracted_dir, "LICENSE")
if os.path.exists(license_path):
copy(
self,
"LICENSE",
src=extracted_dir,
dst=os.path.join(self.package_folder, "licenses"),
keep_path=False,
)
def _package_python_prebuilt(self):
"""Package from prebuilt Python package (onnxruntime-rocm)"""
# Find the Python package installation
python_pkg_paths = [
"/usr/local/lib/python3.13/dist-packages/onnxruntime",
"/usr/lib/python3/dist-packages/onnxruntime",
"/usr/local/lib/python3.12/dist-packages/onnxruntime",
"/usr/local/lib/python3.11/dist-packages/onnxruntime",
]
onnxruntime_pkg = None
for path in python_pkg_paths:
if os.path.exists(path):
onnxruntime_pkg = path
break
if not onnxruntime_pkg:
raise ConanInvalidConfiguration(
"Could not find onnxruntime Python package. "
"Install with: pip install onnxruntime-rocm"
)
self.output.info(f"Packaging from Python package: {onnxruntime_pkg}")
# Copy headers from include directory
include_src = os.path.join(onnxruntime_pkg, "include")
if os.path.exists(include_src):
copy(
self,
"*.h",
src=include_src,
dst=os.path.join(self.package_folder, "include"),
keep_path=True,
)
else:
# Try to find headers in the capi directory
capi_include = os.path.join(onnxruntime_pkg, "capi")
if os.path.exists(capi_include):
# Create minimal headers
include_dst = os.path.join(self.package_folder, "include")
mkdir(self, include_dst)
# The Python package doesn't include headers, so we'll need to download them
self.output.warning(
"Headers not found in Python package, downloading from GitHub"
)
# Download headers from GitHub release
headers_url = f"https://github.com/microsoft/onnxruntime/raw/v{self.version}/include/onnxruntime/core/session/onnxruntime_c_api.h"
try:
download(
self,
headers_url,
os.path.join(include_dst, "onnxruntime_c_api.h"),
)
except:
self.output.warning("Could not download headers, creating stub")
# Copy libraries from capi directory
capi_dir = os.path.join(onnxruntime_pkg, "capi")
if os.path.exists(capi_dir):
lib_dst = os.path.join(self.package_folder, "lib")
mkdir(self, lib_dst)
for item in os.listdir(capi_dir):
if item.endswith(".so"):
src_path = os.path.join(capi_dir, item)
dst_path = os.path.join(lib_dst, item)
if os.path.islink(src_path):
link_target = os.readlink(src_path)
if os.path.exists(dst_path) or os.path.islink(dst_path):
os.remove(dst_path)
os.symlink(link_target, dst_path)
self.output.info(f"Preserved symlink: {item} -> {link_target}")
elif os.path.isfile(src_path):
shutil.copy2(src_path, dst_path)
self.output.info(f"Copied library: {item}")
# Create a minimal license file
license_dst = os.path.join(self.package_folder, "licenses")
mkdir(self, license_dst)
with open(os.path.join(license_dst, "LICENSE"), "w") as f:
f.write("ONNX Runtime - MIT License\n")
f.write("See: https://github.com/microsoft/onnxruntime/blob/main/LICENSE\n")
def _package_system(self):
"""Package from system-installed onnxruntime (Homebrew, apt, etc.).
Copies headers and libraries from the system prefix into the Conan
package so downstream consumers get a self-contained package reference
without needing system paths at link time.
"""
prefix = getattr(self, "_system_prefix_resolved", None) or self._detect_system_prefix()
if prefix is None:
raise ConanInvalidConfiguration(
"System onnxruntime prefix could not be resolved during packaging"
)
include_src = os.path.join(prefix, "include")
lib_src = os.path.join(prefix, "lib")
# Copy headers — handle all known layouts:
# Flat: include/onnxruntime_c_api.h
# Homebrew: include/onnxruntime/onnxruntime_c_api.h
# Source: include/onnxruntime/core/session/onnxruntime_c_api.h
#
# Target layout in Conan package:
# include/onnxruntime_c_api.h (flat, for #include "onnxruntime_c_api.h")
# include/onnxruntime/... (tree, for #include <onnxruntime/...>)
if os.path.isdir(include_src):
# Copy any flat headers (prebuilt archive layout)
copy(self, "onnxruntime*.h", src=include_src,
dst=os.path.join(self.package_folder, "include"), keep_path=False)
copy(self, "onnxruntime*.hpp", src=include_src,
dst=os.path.join(self.package_folder, "include"), keep_path=False)
# Copy nested header tree (onnxruntime/...)
onnx_inc = os.path.join(include_src, "onnxruntime")
if os.path.isdir(onnx_inc):
copy(self, "*.h", src=onnx_inc,
dst=os.path.join(self.package_folder, "include", "onnxruntime"),
keep_path=True)
copy(self, "*.hpp", src=onnx_inc,
dst=os.path.join(self.package_folder, "include", "onnxruntime"),
keep_path=True)
# Homebrew layout: headers are directly in include/onnxruntime/
# (e.g. include/onnxruntime/onnxruntime_c_api.h)
# Also copy them flat into include/ so #include "onnxruntime_c_api.h" works
copy(self, "onnxruntime*.h", src=onnx_inc,
dst=os.path.join(self.package_folder, "include"), keep_path=False)
# Source layout: headers in include/onnxruntime/core/session/
session_inc = os.path.join(include_src, "onnxruntime", "core", "session")
if os.path.isdir(session_inc):
copy(self, "onnxruntime*.h", src=session_inc,
dst=os.path.join(self.package_folder, "include"), keep_path=False)
# Copy libraries (shared + static)
lib_dst = os.path.join(self.package_folder, "lib")
mkdir(self, lib_dst)
if os.path.isdir(lib_src):
for item in os.listdir(lib_src):
if not item.startswith("libonnxruntime"):
continue
src_path = os.path.join(lib_src, item)
dst_path = os.path.join(lib_dst, item)
if item.endswith((".so", ".a", ".dylib")) or ".so." in item or ".dylib." in item:
if os.path.islink(src_path):
link_target = os.readlink(src_path)
if os.path.exists(dst_path) or os.path.islink(dst_path):
os.remove(dst_path)
os.symlink(link_target, dst_path)
self.output.info(f"Preserved symlink: {item} -> {link_target}")
elif os.path.isfile(src_path):
shutil.copy2(src_path, dst_path)
self.output.info(f"Copied library: {item}")
# Copy existing pkg-config if available (we'll overwrite with ours later)
system_pc = os.path.join(lib_src, "pkgconfig", "onnxruntime.pc")
if os.path.exists(system_pc):
self.output.info("Found system pkg-config file")
# License
license_candidates = [
os.path.join(prefix, "share", "doc", "onnxruntime", "LICENSE"),
os.path.join(prefix, "share", "onnxruntime", "LICENSE"),
os.path.join(prefix, "LICENSE"),
]
for lic in license_candidates:
if os.path.exists(lic):
copy(self, os.path.basename(lic), src=os.path.dirname(lic),
dst=os.path.join(self.package_folder, "licenses"), keep_path=False)
break
else:
# Create a stub license
licenses_dir = os.path.join(self.package_folder, "licenses")
mkdir(self, licenses_dir)
save(self, os.path.join(licenses_dir, "LICENSE"),
"ONNX Runtime - MIT License\n"
"System installation from: {}\n"
"See: https://github.com/microsoft/onnxruntime/blob/main/LICENSE\n".format(prefix))
self.output.info(f"Packaged system onnxruntime from {prefix}")
def package(self):
if self.options.use_system:
self._package_system()
elif self.options.use_prebuilt_python:
self._package_python_prebuilt()
elif self._is_source_build():
self._package_source_build()
else:
self._package_prebuilt()
# Generate pkg-config file
pc_dir = os.path.join(self.package_folder, "lib", "pkgconfig")
mkdir(self, pc_dir)
pc_content = f"""prefix={self.package_folder}
libdir=${{prefix}}/lib
includedir=${{prefix}}/include
Name: onnxruntime
Description: ONNX Runtime - cross-platform ML inference
Version: {self.version}
Libs: -L${{libdir}} -lonnxruntime
Libs.private: -lpthread -ldl -lm
Cflags: -I${{includedir}}
"""
save(self, os.path.join(pc_dir, "onnxruntime.pc"), pc_content)
def package_info(self):
gpu = str(self.options.with_gpu) if self.options.with_gpu else None
# Base library
self.cpp_info.libs = ["onnxruntime"]
self.cpp_info.includedirs = ["include", os.path.join("include", "onnxruntime")]
# GPU-specific libraries and defines
if gpu == "cuda":
self.cpp_info.libs.extend(
[
"onnxruntime_providers_shared",
"onnxruntime_providers_cuda",
]
)
self.cpp_info.defines = ["YAMS_ONNX_CUDA_ENABLED=1"]
elif gpu == "directml":
# The DirectML NuGet package layout used on Windows does not ship
# an import library named "onnxruntime_providers_dml" (and may not
# ship a separate providers DLL at all). Linking against it causes
# LNK1181. The DML EP is enabled via runtime provider registration.
self.cpp_info.defines = ["YAMS_ONNX_DIRECTML_ENABLED=1"]
elif gpu == "coreml":
self.cpp_info.defines = ["YAMS_ONNX_COREML_ENABLED=1"]
elif gpu == "migraphx":
# MIGraphX provider is built into the main library when built from source
self.cpp_info.defines = ["YAMS_ONNX_MIGRAPHX_ENABLED=1"]
# When using prebuilt Python package, we need the providers
if self.options.use_prebuilt_python:
self.cpp_info.libs.extend(
[
"onnxruntime_providers_shared",
"onnxruntime_providers_migraphx",
"onnxruntime_providers_rocm",
]
)
# Add ROCm library path for runtime linking
rocm_path = str(self.options.rocm_path)
rocm_lib = os.path.join(rocm_path, "lib")
rocm_migraphx_lib = os.path.join(rocm_path, "lib", "migraphx", "lib")
self.cpp_info.libdirs.extend([rocm_lib, rocm_migraphx_lib])
# System libs
if self.settings.os == "Linux":
self.cpp_info.system_libs = ["pthread", "dl", "m"]
if gpu == "migraphx":
# ROCm runtime libraries
rocm_path = str(self.options.rocm_path)
migraphx_shared = os.path.join(rocm_path, "lib", "libmigraphx.so")
migraphx_c_shared = os.path.join(rocm_path, "lib", "libmigraphx_c.so")
migraphx_lib = (
"migraphx" if os.path.exists(migraphx_shared) else "migraphx_c"
)
if not os.path.exists(migraphx_shared) and not os.path.exists(
migraphx_c_shared
):
migraphx_lib = "migraphx_c"
self.cpp_info.system_libs.extend(["amdhip64", migraphx_lib])
# Library paths
if self.settings.os in ["Linux", "Macos"]:
if "lib" not in self.cpp_info.libdirs:
self.cpp_info.libdirs.insert(0, "lib")
self.cpp_info.rpath_dirs = ["lib"]
elif self.settings.os == "Windows":
self.cpp_info.libdirs = ["lib"]
self.cpp_info.bindirs = ["bin"]
# CMake properties
self.cpp_info.set_property("cmake_file_name", "onnxruntime")
self.cpp_info.set_property("cmake_target_name", "onnxruntime::onnxruntime")
# Pkg-config properties
self.cpp_info.set_property("pkg_config_name", "onnxruntime")