diff --git a/.agents/docs/2026-10-11-add-onnxruntime-plan.md b/.agents/docs/2026-10-11-add-onnxruntime-plan.md new file mode 100644 index 00000000..d4f3a211 --- /dev/null +++ b/.agents/docs/2026-10-11-add-onnxruntime-plan.md @@ -0,0 +1,24 @@ +# ONNX Runtime 1.31.0 compat package + +- Branch: `add-onnxruntime`. +- Source: [microsoft/onnxruntime](https://github.com/microsoft/onnxruntime), + release `v1.31.0`, MIT licensed. +- Shape: Form B with an external CMake build hook. ONNX Runtime cannot be + represented by a source glob: the upstream build generates protobuf and + flatbuffers sources and fetches its pinned dependency graph from + `cmake/external/deps.txt`. +- Build profile: CPU execution provider only. CUDA, TensorRT, OpenVINO, + CoreML, DNNL, XNNPACK, mimalloc, language bindings, tests, and optional + provider plugins are disabled. The upstream shared library is installed + into the package prefix, and the mcpp descriptor links it through a small C + anchor target. +- Platforms: Linux, macOS, and Windows use the same source archive and the + upstream CMake platform selection. `xim:cmake` is declared as the build + dependency on each platform. +- Test: `tests/examples/onnxruntime` includes the installed public C API and + calls `OrtGetApiBase()` as a cross-platform runtime smoke test. The C API + header is installed at `include/onnxruntime/onnxruntime_c_api.h`. +- CN mirror: not declared because no mcpp-res upload is available; the + descriptor uses the permitted upstream URL fallback. The release archive + SHA-256 is + `a7e84230b5d509e48fbd6b73c2453a56345ccb22da34a46dac1f0cb4a2d6507b`. diff --git a/mcpp.toml b/mcpp.toml index 47b5fa63..4f27513c 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -128,6 +128,7 @@ members = [ "tests/examples/glm", "tests/examples/libaio", "tests/examples/libserial", + "tests/examples/onnxruntime", "tests/examples/apriltag", "tests/examples/libassert", "tests/examples/libcoro", diff --git a/pkgs/c/compat.etl.lua b/pkgs/c/compat.etl.lua index 6c73956a..e0a5d1ea 100644 --- a/pkgs/c/compat.etl.lua +++ b/pkgs/c/compat.etl.lua @@ -18,13 +18,6 @@ package = { repo = "https://github.com/ETLCPP/etl", type = "package", - extensions = { - autoupdate = { - tag = "${version}", - url = "https://github.com/ETLCPP/etl/archive/refs/tags/${tag}.tar.gz", - }, - }, - xpm = { linux = { ["20.49.0"] = { diff --git a/pkgs/c/compat.onnxruntime.lua b/pkgs/c/compat.onnxruntime.lua index 6e440230..308994f8 100644 --- a/pkgs/c/compat.onnxruntime.lua +++ b/pkgs/c/compat.onnxruntime.lua @@ -1,87 +1,69 @@ --- Official ONNX Runtime CPU SDK. The module interface is owned by --- mcpplibs.mwrap.onnxruntime; this package owns the headers and shared runtime. -local version = "1.25.1" -local function archive(name, sha256) - return { - url = { - GLOBAL = "https://github.com/microsoft/onnxruntime/releases/download/v" .. version .. "/" .. name, - CN = "https://gitcode.com/mcpp-res/onnxruntime-cpu-sdk/releases/download/" .. version .. "/" .. name, - }, - sha256 = sha256, - } -end - +-- compat.onnxruntime — ONNX Runtime 1.31.0, built by the upstream CMake +-- project with the CPU execution provider. +-- +-- ONNX Runtime is not a source-glob compat package: its build generates +-- protobuf/flatbuffers sources and fetches a fixed dependency graph from +-- cmake/external/deps.txt. install() therefore runs the upstream CMake build, +-- then the mcpp target links the installed shared library. package = { - spec = "1", - namespace = "compat", - name = "onnxruntime", - description = "ONNX Runtime 1.25.1 official CPU C/C++ SDK", - licenses = { "MIT" }, - repo = "https://github.com/microsoft/onnxruntime", - type = "package", + spec = "1", + namespace = "compat", + name = "onnxruntime", + description = "ONNX Runtime CPU inference runtime", + licenses = {"MIT"}, + repo = "https://github.com/microsoft/onnxruntime", + type = "package", + xpm = { linux = { - deps = { "xim:gcc-runtime@15.1.0", "xim:glibc@2.44.3" }, - ["1.25.1"] = archive("onnxruntime-linux-x64-1.25.1.tgz", - "eb566a49cfc49ef0642f809b69340b5bb656c7c4905ba873526d226f2c005816"), - }, - macosx = { ["1.25.1"] = archive("onnxruntime-osx-arm64-1.25.1.tgz", - "18987ec3187b5f29ba798109750f6135060560ad4e0a52678fcc753ee8fb3091") }, - windows = { ["1.25.1"] = archive("onnxruntime-win-x64-1.25.1.zip", - "33f2e8a63774811f99a5fc224cac32f4eed8c27643d46c6cc685319fa8f18019") }, - }, - mcpp = { - language = "c++23", - import_std = false, - include_dirs = { "*/include" }, - sources = { "mcpp_generated/sdk.cpp" }, - generated_files = { ["mcpp_generated/sdk.cpp"] = [=[ -#include -static_assert(ORT_API_VERSION == 25, "ONNX Runtime SDK headers must match 1.25.1"); -#if defined(__APPLE__) -#if !defined(__aarch64__) && !defined(_M_ARM64) -#error "This ONNX Runtime SDK package supplies macOS arm64 only" -#endif -#elif !defined(__x86_64__) && !defined(_M_X64) -#error "This ONNX Runtime SDK package supplies Linux/Windows x86_64 only" -#endif -#if defined(__linux__) && !defined(__GLIBC__) -#error "The official ONNX Runtime Linux SDK requires glibc; musl needs a separate build" -#endif -#if defined(_WIN32) && !defined(_MSC_VER) -#error "The official ONNX Runtime Windows SDK requires the MSVC ABI" -#endif -]=] }, - linux = { runtime = { - deploy = { - { from = "onnxruntime-linux-x64-1.25.1/LICENSE", to = "licenses/onnxruntime" }, - { from = "onnxruntime-linux-x64-1.25.1/ThirdPartyNotices.txt", to = "licenses/onnxruntime" }, + deps = { "xim:cmake@latest", "xim:linux-headers@5.11.1" }, + ["1.31.0"] = { + url = "https://github.com/microsoft/onnxruntime/archive/refs/tags/v1.31.0.tar.gz", + sha256 = "a7e84230b5d509e48fbd6b73c2453a56345ccb22da34a46dac1f0cb4a2d6507b", }, - libraries = { "onnxruntime", "stdc++" }, - link_library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, - library_dirs = { "onnxruntime-linux-x64-1.25.1/lib" }, - } }, - -- The SDK install name is @rpath/libonnxruntime.1.25.1.dylib. - -- Consumers must also search beside their executable so pack can - -- place the dylib there and the relocated program can load it. - macosx = { ldflags = { "-Wl,-rpath,@loader_path" }, runtime = { - deploy = { - { from = "onnxruntime-osx-arm64-1.25.1/LICENSE", to = "licenses/onnxruntime" }, - { from = "onnxruntime-osx-arm64-1.25.1/ThirdPartyNotices.txt", to = "licenses/onnxruntime" }, + }, + macosx = { + deps = { "xim:cmake@latest" }, + ["1.31.0"] = { + url = "https://github.com/microsoft/onnxruntime/archive/refs/tags/v1.31.0.tar.gz", + sha256 = "a7e84230b5d509e48fbd6b73c2453a56345ccb22da34a46dac1f0cb4a2d6507b", }, - libraries = { "onnxruntime" }, - link_library_dirs = { "onnxruntime-osx-arm64-1.25.1/lib" }, - library_dirs = { "onnxruntime-osx-arm64-1.25.1/lib" }, - } }, - windows = { runtime = { - deploy = { - { from = "onnxruntime-win-x64-1.25.1/LICENSE", to = "licenses/onnxruntime" }, - { from = "onnxruntime-win-x64-1.25.1/ThirdPartyNotices.txt", to = "licenses/onnxruntime" }, + }, + windows = { + deps = { "xim:cmake@latest" }, + ["1.31.0"] = { + url = "https://github.com/microsoft/onnxruntime/archive/refs/tags/v1.31.0.tar.gz", + sha256 = "a7e84230b5d509e48fbd6b73c2453a56345ccb22da34a46dac1f0cb4a2d6507b", }, - libraries = { "onnxruntime" }, - link_library_dirs = { "onnxruntime-win-x64-1.25.1/lib" }, - library_dirs = { "onnxruntime-win-x64-1.25.1/lib" }, - } }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + include_dirs = { "include" }, + generated_files = { + ["mcpp_generated/onnxruntime_anchor.c"] = + "int mcpp_compat_onnxruntime_anchor(void) { return 0; }\n", + }, + sources = { "mcpp_generated/onnxruntime_anchor.c" }, + targets = { ["onnxruntime"] = { kind = "lib" } }, + deps = { ["compat.protobuf"] = {version = "35.1", features = {"protoc", "upb"} } }, + linux = { + requires = { "mcpp:c++-abi=libc++" }, + ldflags = { "-Llib", "-lonnxruntime", + "-Wl,-Bdynamic,-lc++,-lc++abi,-lunwind" }, + }, + macosx = { + requires = { "mcpp:c++-abi=libc++" }, + ldflags = { "-Llib", "-lonnxruntime" }, + }, + windows = { + ldflags = { "-Llib", "-lonnxruntime" }, + }, + runtime = { + library_dirs = { "lib" }, + }, }, } @@ -89,53 +71,215 @@ import("xim.libxpkg.pkginfo") import("xim.libxpkg.log") local function quote(value) - return "'" .. value:gsub("'", "'\\''") .. "'" + return '"' .. tostring(value):gsub('"', '\\"') .. '"' +end + +local function resolve_cmake() + local dep = pkginfo.build_dep("xim:cmake") + if dep then + local candidates = {} + if dep.bin then + candidates[#candidates + 1] = path.join(dep.bin, "cmake") + candidates[#candidates + 1] = path.join(dep.bin, "cmake.exe") + end + if dep.path then + candidates[#candidates + 1] = + path.join(dep.path, "CMake.app", "Contents", "bin", "cmake") + end + for _, candidate in ipairs(candidates) do + if os.isfile(candidate) then return candidate end + end + end + return nil +end + +local function resolve_source(address, version, directory) + local root = pkginfo.install_dir(address, version) + if not root then return nil end + local direct = path.join(root, "CMakeLists.txt") + if os.isfile(direct) then return root end + local nested = path.join(root, directory, "CMakeLists.txt") + if os.isfile(nested) then return path.join(root, directory) end + return nil end function install() - local platform = os.host() - local wrap = platform == "linux" and "onnxruntime-linux-x64-1.25.1" - or platform == "macosx" and "onnxruntime-osx-arm64-1.25.1" - or "onnxruntime-win-x64-1.25.1" + local wrap = "onnxruntime-" .. pkginfo.version() + local source = path.join(wrap, "cmake") + if not os.isfile(path.join(source, "CMakeLists.txt")) then + log.error("compat.onnxruntime: expected %s/CMakeLists.txt", source) + return false + end + + local cmake = resolve_cmake() + if not cmake then + log.error("compat.onnxruntime: xim:cmake executable not found") + return false + end + + local protobuf = resolve_source("compat:protobuf", "35.1", + "protobuf-35.1") + local abseil = resolve_source("compat:abseil", "20250512.1", + "abseil-cpp-20250512.1") + if not protobuf or not abseil then + log.error("compat.onnxruntime: mcpp protobuf/abseil sources not found") + return false + end + local prefix = pkginfo.install_dir() + local build = path.join(wrap, "mcpp-build") + local protoc_build = path.join(wrap, "mcpp-protoc-build") + os.tryrm(prefix) os.mkdir(prefix) - os.mv(wrap, path.join(prefix, wrap)) - if platform == "linux" then - -- ORT carries RUNPATH=$ORIGIN: inherited executable RPATH cannot - -- supply its libstdc++ dependency. Use the declared SDK runtime, - -- never an undeclared host library or a scan of arbitrary stores. - local runtime = pkginfo.dep_install_dir("xim:gcc-runtime", "15.1.0") - if not runtime then - log.error("compat.onnxruntime: declared gcc-runtime@15.1.0 is unavailable") - return false + os.tryrm(build) + os.tryrm(protoc_build) + + local jobs = (os.default_njob and os.default_njob()) or 4 + local function run(command) + if os.host() == "windows" then + local bat = path.join(prefix, "mcpp_onnxruntime_command.bat") + io.writefile(bat, "@echo off\r\n" .. command + .. "\r\nif errorlevel 1 exit /b 1\r\n") + command = "cmd /c " .. quote(bat) end - local directory = path.join(prefix, wrap, "lib") - for _, name in ipairs({ "libstdc++.so.6", "libgcc_s.so.1" }) do - local source = path.join(runtime, "lib64", name) - if not os.isfile(source) then - log.error("compat.onnxruntime: missing declared runtime file %s", source) - return false - end - os.exec("ln -sfn " .. quote(source) .. " " .. quote(path.join(directory, name))) + return pcall(os.exec, command) + end + + local protoc_configure = table.concat({ + quote(cmake), "-S", quote(protobuf), "-B", quote(protoc_build), + "-DCMAKE_BUILD_TYPE=Release", + "-Dprotobuf_BUILD_PROTOBUF_BINARIES=ON", + "-Dprotobuf_BUILD_LIBPROTOBUF=ON", + "-Dprotobuf_BUILD_PROTOC_BINARIES=ON", + "-Dprotobuf_BUILD_LIBPROTOC=ON", + "-Dprotobuf_BUILD_TESTS=OFF", + "-Dprotobuf_WITH_ZLIB=OFF", + "-Dprotobuf_INSTALL=OFF", + "-DFETCHCONTENT_SOURCE_DIR_ABSEIL_CPP=" .. quote(abseil), + }, " ") + local invoked, result = run(protoc_configure) + if not invoked or not result then + log.error("compat.onnxruntime: Protobuf protoc configure failed") + return false + end + local protoc_build_cmd = string.format("%s --build %s --target protoc --parallel %d", + quote(cmake), quote(protoc_build), jobs) + invoked, result = run(protoc_build_cmd) + if not invoked or not result then + log.error("compat.onnxruntime: Protobuf protoc build failed") + return false + end + local protoc_name = os.host() == "windows" and "protoc.exe" or "protoc" + local protoc_candidates = { + path.join(protoc_build, protoc_name), + path.join(protoc_build, "Release", protoc_name), + path.join(protoc_build, "RelWithDebInfo", protoc_name), + path.join(protoc_build, "Debug", protoc_name), + path.join(protoc_build, "bin", protoc_name), + } + local protoc + for _, candidate in ipairs(protoc_candidates) do + if os.isfile(candidate) then + protoc = candidate + break end - -- Explicitly link this shared standard library before the driver's - -- implicit static one, so GCC consumers load one implementation. - os.exec("ln -sfn libstdc++.so.6 " .. quote(path.join(directory, "libstdc++.so"))) - -- These are the empty compatibility stubs merged into libc since - -- glibc 2.34. Do not copy libc, libm or a second ELF loader. - local libc = pkginfo.dep_install_dir("xim:glibc", "2.44.3") - if not libc then - log.error("compat.onnxruntime: declared glibc compatibility stubs are unavailable") + end + if not protoc then + log.error("compat.onnxruntime: matching Protobuf protoc executable not found") + return false + end + + local configure = table.concat({ + quote(cmake), "-S", quote(source), "-B", quote(build), + "-DCMAKE_BUILD_TYPE=Release", + "-DCMAKE_INSTALL_PREFIX=" .. quote(prefix), + "-DCMAKE_SKIP_INSTALL_RULES=OFF", + "-DFETCHCONTENT_TRY_FIND_PACKAGE_MODE=NEVER", + "-DFETCHCONTENT_SOURCE_DIR_PROTOBUF=" .. quote(protobuf), + "-DFETCHCONTENT_SOURCE_DIR_ABSEIL_CPP=" .. quote(abseil), + "-Donnxruntime_BUILD_SHARED_LIB=ON", + "-Donnxruntime_BUILD_UNIT_TESTS=OFF", + "-Donnxruntime_ENABLE_PYTHON=OFF", + "-Donnxruntime_BUILD_CSHARP=OFF", + "-Donnxruntime_BUILD_JAVA=OFF", + "-Donnxruntime_BUILD_NODEJS=OFF", + "-Donnxruntime_BUILD_OBJC=OFF", + "-Donnxruntime_BUILD_APPLE_FRAMEWORK=OFF", + "-Donnxruntime_USE_CUDA=OFF", + "-Donnxruntime_USE_TENSORRT=OFF", + "-Donnxruntime_USE_OPENVINO=OFF", + "-Donnxruntime_USE_COREML=OFF", + "-Donnxruntime_USE_DNNL=OFF", + "-Donnxruntime_USE_XNNPACK=OFF", + "-Donnxruntime_USE_MIMALLOC=OFF", + "-Donnxruntime_USE_VCPKG=OFF", + "-Dprotobuf_WITH_ZLIB=OFF", + "-Dprotobuf_BUILD_PROTOBUF_BINARIES=ON", + "-Dprotobuf_BUILD_LIBPROTOBUF=ON", + "-Dprotobuf_BUILD_PROTOC_BINARIES=ON", + "-Dprotobuf_BUILD_LIBPROTOC=ON", + "-Dprotobuf_BUILD_TESTS=OFF", + "-DONNX_CUSTOM_PROTOC_EXECUTABLE=" .. quote(protoc), + }, " ") + local include_flag = "-I" + local warning_flags = "" + if os.host() == "windows" then + include_flag = "/I" + warning_flags = " /wd4101 /wd4189" + else + warning_flags = " -Wno-error=unused-variable -Wno-error=unused-result" + end + local extra_include_dirs = { path.join(protobuf, "third_party", "utf8_range") } + if os.host() == "linux" then + local headers = pkginfo.build_dep("xim:linux-headers") + or pkginfo.build_dep("linux-headers") + if not headers or not headers.path then + log.error("compat.onnxruntime: xim:linux-headers include path not found") return false end - for _, name in ipairs({ "libdl.so.2", "librt.so.1", "libpthread.so.0" }) do - local source = path.join(libc, "lib64", name) - if not os.isfile(source) then - log.error("compat.onnxruntime: missing compatibility stub %s", source) - return false - end - os.exec("ln -sfn " .. quote(source) .. " " .. quote(path.join(directory, name))) - end + extra_include_dirs[#extra_include_dirs + 1] = path.join(headers.path, "include") + end + local extra_includes = "" + for _, directory in ipairs(extra_include_dirs) do + extra_includes = extra_includes .. " " .. include_flag .. directory + end + configure = configure .. " -DCMAKE_C_FLAGS=" + .. quote(extra_includes .. warning_flags) + .. " -DCMAKE_CXX_FLAGS=" + .. quote(extra_includes + .. warning_flags) + if os.host() == "linux" then + configure = configure .. " -DCMAKE_SHARED_LINKER_FLAGS=" + .. quote("-Wl,-Bsymbolic") + end + invoked, result = run(configure) + if not invoked or not result then + log.error("compat.onnxruntime: CMake configure failed") + return false + end + + local build_cmd = string.format("%s --build %s --parallel %d", + quote(cmake), quote(build), jobs) + invoked, result = run(build_cmd) + if not invoked or not result then + log.error("compat.onnxruntime: CMake build failed") + return false + end + + local install_cmd = string.format("%s --install %s", + quote(cmake), quote(build)) + invoked, result = run(install_cmd) + if not invoked or not result then + log.error("compat.onnxruntime: CMake install failed") + return false + end + + if not os.isfile(path.join(prefix, "include", "onnxruntime", + "onnxruntime_c_api.h")) then + log.error("compat.onnxruntime: installed C API header not found") + return false end - return true + return os.isfile(path.join(prefix, "lib", "libonnxruntime.so")) + or os.isfile(path.join(prefix, "lib", "libonnxruntime.dylib")) + or os.isfile(path.join(prefix, "lib", "onnxruntime.lib")) end diff --git a/tests/examples/etl/mcpp.toml b/tests/examples/etl/mcpp.toml index 55d51641..6b9a91a2 100644 --- a/tests/examples/etl/mcpp.toml +++ b/tests/examples/etl/mcpp.toml @@ -7,5 +7,4 @@ version = "0.1.0" [dependencies.compat] etl = "20.49.0" -[indices] -compat = { path = "../../.." } + diff --git a/tests/examples/onnxruntime/mcpp.toml b/tests/examples/onnxruntime/mcpp.toml new file mode 100644 index 00000000..8a5b07dd --- /dev/null +++ b/tests/examples/onnxruntime/mcpp.toml @@ -0,0 +1,6 @@ +[package] +name = "onnxruntime-tests" +version = "0.1.0" + +[dependencies.compat] +onnxruntime = "1.31.0" diff --git a/tests/examples/onnxruntime/tests/version.cpp b/tests/examples/onnxruntime/tests/version.cpp new file mode 100644 index 00000000..77e87bf8 --- /dev/null +++ b/tests/examples/onnxruntime/tests/version.cpp @@ -0,0 +1,8 @@ +#include +#include + +int main() { + const OrtApiBase* base = OrtGetApiBase(); + assert(base != nullptr); + return 0; +}