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_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 ) 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")