From 6a60a58e3ef012800f5e264c44031cd1a5b5eaf6 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 2 Oct 2026 18:24:27 +0000 Subject: [PATCH 1/6] usd-exchange: Add version 3.0.0 --- .github/workflows/build-usd-exchange.yml | 311 ++++++++++++++++++ docs/packages/usd-exchange.yaml | 6 + ...001-Support-riscv64-in-pxr-base-arch.patch | 121 +++++++ 3 files changed, 438 insertions(+) create mode 100644 .github/workflows/build-usd-exchange.yml create mode 100644 docs/packages/usd-exchange.yaml create mode 100644 patches/usd-exchange/3.0.0/0001-Support-riscv64-in-pxr-base-arch.patch diff --git a/.github/workflows/build-usd-exchange.yml b/.github/workflows/build-usd-exchange.yml new file mode 100644 index 00000000000..9e24c1391f8 --- /dev/null +++ b/.github/workflows/build-usd-exchange.yml @@ -0,0 +1,311 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Mirrors upstream's repo_man build (tools/repoman/cmake_build.py, install_usdex.py, +# py_package.py) and its whl test suite (tools/pyproject/pytest.sh), narrowed to riscv64. +# Upstream links prebuilt packman OpenUSD/MaterialX/oneTBB packages with no riscv64 +# flavor, so the same stock OpenUSD release is built here with build_usd.py instead. +name: Build usd-exchange wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/usd-exchange.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-usd-exchange.yml' + - 'docs/packages/usd-exchange.yaml' + - 'patches/usd-exchange/**' + push: + branches: [main] + paths: + - '.github/workflows/build-usd-exchange.yml' + - 'docs/packages/usd-exchange.yaml' + - 'patches/usd-exchange/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: usd-exchange + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build usd-exchange ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 1440 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + # Upstream's usd_flavors.json and requires-python stop at 3.13. + python: ["cp310", "cp311", "cp312", "cp313"] + + env: + USD_EXCHANGE_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout usd-exchange v${{ env.USD_EXCHANGE_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: NVIDIA-Omniverse/usd-exchange + ref: v${{ env.USD_EXCHANGE_VERSION }} + persist-credentials: false + + - name: Resolve the OpenUSD release of the default flavor + id: usd + run: | + set -euo pipefail + usd_ver="$(sed -n 's/^default_flavor = "usd_\([0-9.]*\)_py_.*"$/\1/p' repo.toml)" + test -n "${usd_ver}" + printf 'ver=%s\ntag=v%s\n' "${usd_ver}" "${usd_ver}" >> "$GITHUB_OUTPUT" + + - name: Checkout OpenUSD ${{ steps.usd.outputs.tag }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: PixarAnimationStudios/OpenUSD + ref: ${{ steps.usd.outputs.tag }} + path: OpenUSD + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + fetch-depth: 1 + persist-credentials: false + + - name: Patch OpenUSD + run: git -C OpenUSD apply "${GITHUB_WORKSPACE}"/python-wheels/patches/usd-exchange/${{ env.USD_EXCHANGE_VERSION }}/00*.patch + + - name: Pull manylinux_riscv64 image + run: docker pull "${MANYLINUX_RISCV64_IMAGE}" + + - name: Build OpenUSD, usd-exchange and the wheel + run: | + mkdir -p dist + docker run -i --rm \ + -v "${GITHUB_WORKSPACE}:/work" \ + -w /work \ + -e PYTHON_TAG="${{ matrix.python }}" \ + -e USD_EXCHANGE_VERSION \ + -e USD_VER="${{ steps.usd.outputs.ver }}" \ + "${MANYLINUX_RISCV64_IMAGE}" bash -s <<'USDEX_BUILD_EOF' + #!/usr/bin/env bash + set -euxo pipefail + + PY_ROOT="/opt/python/${PYTHON_TAG}-${PYTHON_TAG}" + PY="${PY_ROOT}/bin/python" + PY_VER="$("$PY" -c 'import sys; print("%d.%d" % sys.version_info[:2])')" + "$PY" -m pip install build toml wheel "pybind11==2.11.1" + + # The manylinux image ships no libpython (PEP 513); FindPython's Development + # component needs the file to exist, and nothing ends up linking against it. + "$PY" -c "import pathlib,sysconfig; pathlib.Path(sysconfig.get_config_var('LIBDIR'), sysconfig.get_config_var('LDLIBRARY')).touch()" + + USD=/work/_build/usd + # usdex only ships MaterialXCore/Format; the render modules want X11, absent from the image. + "$PY" OpenUSD/build_scripts/build_usd.py --onetbb \ + --build-args USD,"-DPXR_PY_UNDEFINED_DYNAMIC_LOOKUP=ON -DPXR_BUILD_EXEC=OFF" \ + MaterialX,"-DMATERIALX_BUILD_RENDER=OFF" \ + --no-imaging --no-tools --no-examples --no-tutorials \ + --build /work/_build/usd-build --src /work/_build/usd-src "${USD}" -v + + cmake -S /work -B /work/_build/cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_LIBDIR=lib \ + -DUSDEX_INSTALL_PYTHONDIR=python \ + -DUSDEX_USD_ROOT="${USD}" \ + -DUSDEX_TBB_ROOT="${USD}" \ + -DUSDEX_MATERIALX_ROOT="${USD}" \ + -DUSDEX_VERSION_STRING="${USD_EXCHANGE_VERSION}" \ + -DUSDEX_PYBIND11_INCLUDE_DIR="$("$PY" -c 'import pybind11; print(pybind11.get_include())')" \ + -DUSDEX_PYTHON_VERSION="${PY_VER}" \ + -DUSDEX_PYTHON_ROOT="${PY_ROOT}" + cmake --build /work/_build/cmake --config Release -j "$(nproc)" + cmake --install /work/_build/cmake --config Release --prefix /work/_build/install + + "$PY" - <<'STAGE_EOF' + import glob, importlib.metadata, os, shutil, sys, toml + + usd ="/work/_build/usd" + usdex = "/work/_build/install" + staging = "/work/_build/pyproject" + libs = f"{staging}/usd_exchange.libs" + usd_ver = os.environ["USD_VER"] + major, minor = (int(x) for x in usd_ver.split(".")) + + def copy(src, dst): + os.makedirs(os.path.dirname(dst), exist_ok=True) + (shutil.copytree if os.path.isdir(src) else shutil.copyfile)(src, dst) + + for sub in ("core", "rtx", "test"): + shutil.copytree(f"{usdex}/python/usdex/{sub}", f"{staging}/usdex/{sub}") + + plugins = ["ar", "sdf", "sdr", "usd", "usdGeom", "usdLux", "usdMtlx", "usdPhysics", "usdShade", "usdShaders", "usdUI"] + schemas = ["usdMedia", "usdProc", "usdRender", "usdSemantics", "usdSkel", "usdVol"] + validators = ["usdValidation", "usdGeomValidators", "usdPhysicsValidators", "usdShadeValidators", "usdSkelValidators", "usdUtilsValidators"] + modules = ["Ar", "Gf", "Kind", "Pcp", "Plug", "Sdf", "Sdr", "Tf", "Trace", "Ts", "Usd", "UsdGeom", "UsdLux", "UsdPhysics", "UsdShade", "UsdUI", "UsdUtils", "Vt", "Work", + "UsdMedia", "UsdMtlx", "UsdProc", "UsdRender", "UsdSemantics", "UsdSkel", "UsdVol", "UsdValidation"] + if (major, minor) < (25, 8): + plugins.append("ndr") + modules.append("Ndr") + if (major, minor) >= (26, 8): + schemas += ["usdLod", "usdProfiles"] + validators.append("usdLuxValidators") + modules += ["UsdLod", "UsdProfiles"] + + copy(f"{usd}/lib/usd/plugInfo.json", f"{libs}/usd/plugInfo.json") + for plugin in plugins + schemas + validators: + for root in (f"{usd}/lib/usd", f"{usd}/plugin/usd"): + if os.path.exists(f"{root}/{plugin}"): + copy(f"{root}/{plugin}", f"{libs}/usd/{plugin}") + break + else: + sys.exit(f"plugin {plugin} not found") + copy(f"{usd}/plugin/usd/usdShaders.so", f"{libs}/usd/usdShaders.so") + for name in validators: + if name.endswith("Validators"): + copy(f"{usd}/lib/libusd_{name}.so", f"{libs}/libusd_{name}.so") + + mtlx = f"{libs}/usd/usdMtlx/resources/libraries" + for src in glob.glob(f"{usd}/libraries/bxdf/*open_pbr_surface.mtlx"): + copy(src, f"{mtlx}/bxdf/{os.path.basename(src)}") + for name in ("stdlib_defs.mtlx", "stdlib_ng.mtlx"): + copy(f"{usd}/libraries/stdlib/{name}", f"{mtlx}/stdlib/{name}") + + for module in modules: + src = f"{usd}/lib/python/pxr/{module}" + lib = "_" + module[0].lower() + module[1:] + for path in glob.glob(f"{src}/*.py") + glob.glob(f"{src}/*.pyi") + glob.glob(f"{src}/{lib}*.so"): + copy(path, f"{staging}/pxr/{module}/{os.path.basename(path)}") + if not glob.glob(f"{staging}/pxr/{module}/{lib}*.so"): + sys.exit(f"pxr.{module} has no {lib} binding") + + notices = { + "materialx-LICENSE.txt": glob.glob("/work/_build/usd-src/MaterialX-*/LICENSE")[0], + "onetbb-LICENSE.txt": glob.glob("/work/_build/usd-src/oneTBB-*/LICENSE.txt")[0], + "openusd-LICENSE.txt": "/work/OpenUSD/LICENSE.txt", + "pybind11-LICENSE.txt": [f for f in importlib.metadata.files("pybind11") if f.name == "LICENSE"][0].locate(), + "pyboost11-LICENSE.txt": "/work/tools/internal-licenses/pyboost11-LICENSE.txt", + "usd-exchange-LICENSE.md": "/work/LICENSE.md", + } + for name, src in notices.items(): + copy(src, f"{staging}/{name}") + + validator_version = toml.load("/work/tools/repoman/repo_tools.toml")["repo_install_usdex"]["usd_validation_version"] + data = toml.load("/work/tools/pyproject/pyproject.toml") + data["project"]["version"] = os.environ["USD_EXCHANGE_VERSION"] + data["project"]["optional-dependencies"].update({ + "usd" + usd_ver.replace(".", ""): [], + "test": [f"usd-validation-nvidia>={validator_version},<{int(validator_version.split('.')[0]) + 1}"], + }) + with open(f"{staging}/pyproject.toml", "w") as f: + toml.dump(data, f) + with open("/work/README.md") as f: + readme = f.readlines()[4:7] + with open("/work/tools/pyproject/pypi-notice.md") as f: + notice = f.read() + with open(f"{staging}/README.md", "w") as f: + f.writelines(readme) + f.write(f"\n{notice}") + shutil.copyfile("/work/tools/pyproject/hatch_build.py", f"{staging}/hatch_build.py") + STAGE_EOF + + # Upstream strips before auditwheel's patchelf, never after it. + find /work/_build/pyproject /work/_build/usd/lib /work/_build/install/lib -name '*.so*' -type f \ + -exec sh -c 'head -c4 "$1" | grep -q ELF && strip "$1"' _ {} \; + + "$PY" -m build --wheel --outdir /work/_build/pyproject/dist /work/_build/pyproject + LD_LIBRARY_PATH="/work/_build/install/lib:/work/_build/usd/lib:${LD_LIBRARY_PATH:-}" \ + auditwheel repair -w /work/_build/wheelhouse /work/_build/pyproject/dist/*.whl + + "$PY" - <<'PLUGINFO_EOF' + import glob, json, os, re, subprocess, sys, tempfile + + wheel = glob.glob("/work/_build/wheelhouse/*.whl")[0] + with tempfile.TemporaryDirectory() as tmp: + subprocess.run([sys.executable, "-m", "wheel", "unpack", wheel, "--dest", tmp], check=True) + unpacked = glob.glob(f"{tmp}/*/")[0].rstrip("/") + libs_root = f"{unpacked}/usd_exchange.libs" + hashed = {} + for lib in glob.glob(f"{libs_root}/*.so*"): + match = re.match(r"^(lib.+?)-[0-9a-f]{6,}\.so", os.path.basename(lib)) + if match: + hashed[match.group(1)] = os.path.basename(lib) + for plug_info in glob.glob(f"{libs_root}/usd/*/resources/plugInfo.json"): + with open(plug_info) as f: + data = json.loads("".join(line for line in f if not line.lstrip().startswith("#"))) + modified = False + for plug in data.get("Plugins", []): + lib = hashed.get(f"libusd_{plug.get('Name')}") + if lib and "LibraryPath" in plug: + plug["LibraryPath"] = f"../../{lib}" + modified = True + if modified: + with open(plug_info, "w") as f: + json.dump(data, f, indent=4) + subprocess.run([sys.executable, "-m", "wheel", "pack", unpacked, "--dest-dir", "/work/dist"], check=True) + PLUGINFO_EOF + ls -la /work/dist + USDEX_BUILD_EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: usd-exchange-${{ env.USD_EXCHANGE_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: dist/*.whl + if-no-files-found: error + + - name: Test usd-exchange wheel + run: | + docker run -i --rm \ + -v "${GITHUB_WORKSPACE}:/work" \ + -w /work \ + -e PYTHON_TAG="${{ matrix.python }}" \ + "${MANYLINUX_RISCV64_IMAGE}" bash -s <<'USDEX_TEST_EOF' + #!/usr/bin/env bash + set -euxo pipefail + + VENV=/tmp/venv + "/opt/python/${PYTHON_TAG}-${PYTHON_TAG}/bin/python" -m venv "${VENV}" + wheel="$(echo /work/dist/*.whl)" + "${VENV}/bin/python" -m pip install "${wheel}[test]" + + unset PYTHONPATH + "${VENV}/bin/python" tools/pyproject/check_wheel_imports.py + "${VENV}/bin/python" -m unittest discover -v -s source/core/tests/unittest + "${VENV}/bin/python" -m unittest discover -v -s source/rtx/tests/unittest + USDEX_TEST_EOF + + publish: + name: Publish usd-exchange ${{ matrix.version }} + needs: [setup, build_wheels] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + secrets: + app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} + with: + artifact-pattern: usd-exchange-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/usd-exchange.yaml b/docs/packages/usd-exchange.yaml new file mode 100644 index 00000000000..9a5bb7e5f0c --- /dev/null +++ b/docs/packages/usd-exchange.yaml @@ -0,0 +1,6 @@ +package-name: usd-exchange +source-code: https://github.com/NVIDIA-Omniverse/usd-exchange +license: Apache-2.0 +versions: +- version: 3.0.0 + patched: true diff --git a/patches/usd-exchange/3.0.0/0001-Support-riscv64-in-pxr-base-arch.patch b/patches/usd-exchange/3.0.0/0001-Support-riscv64-in-pxr-base-arch.patch new file mode 100644 index 00000000000..a022f2e8a1c --- /dev/null +++ b/patches/usd-exchange/3.0.0/0001-Support-riscv64-in-pxr-base-arch.patch @@ -0,0 +1,121 @@ +From e346d5ee838ae79333ed9e0f6242891c841ed1b8 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 21 Sep 2026 01:53:09 +0000 +Subject: [PATCH] Support riscv64 in pxr/base/arch + +pxr/base/arch/defines.h recognises only x86 and ARM, and its "Bits" block +ends in #error "Unsupported architecture. x86_64 or ARM64 required." - so +every translation unit of USD fails on riscv64 before anything else is +compiled. Two further sites are reached once that is fixed: arch/math.h +guards ArchSign/ArchFloatToBitPattern/... on +ARCH_CPU_INTEL || ARCH_CPU_ARM || ARCH_OS_WASM_VM and ends in #error Unknown +system architecture (the code it guards is portable IEEE-754 bit twiddling, +not x86 or ARM specific), and nonLockingLinux__execve() in +arch/stackTrace.cpp has hand-written syscall asm for aarch64 and x86_64 with +#error Unknown architecture in the #else. + +Add an ARCH_CPU_RISCV define for __riscv, admit __riscv_xlen == 64 to +ARCH_BITS_64, let math.h's gate accept it, and give +nonLockingLinux__execve() a riscv64 branch. The syscall branch mirrors the +aarch64 one exactly: the Linux riscv64 ABI passes arguments in a0-a2, takes +the syscall number in a7, uses the same generic __NR_execve == 221 as +aarch64, and traps with ecall instead of svc. Everything else in arch is +already portable: ARCH_SPIN_PAUSE() has a no-op #else, timing.h only reaches +rdtsc behind PXR_ARCH_PREFER_TSC_TIMING and otherwise uses +std::chrono::steady_clock, and the remaining #error branches in +systemInfo.cpp/fileSystem.cpp/assumptions.cpp select on the OS, not the CPU. + +With this, the core (non-imaging) USD libraries and their Python bindings +build and run on riscv64; pxr/base/gf's SIMD paths are already gated on +__SSE2__/__ARM_NEON with scalar fallbacks, and the vendored +double-conversion already lists __riscv. + +Upstream-Status: To upstream [not submitted from this automated port run; needs a pull request against PixarAnimationStudios/OpenUSD] +--- + pxr/base/arch/defines.h | 7 +++++-- + pxr/base/arch/math.h | 1 + + pxr/base/arch/overview.dox | 1 + + pxr/base/arch/stackTrace.cpp | 14 ++++++++++++++ + 4 files changed, 21 insertions(+), 2 deletions(-) + +diff --git a/pxr/base/arch/defines.h b/pxr/base/arch/defines.h +index b7ed634..58e860c 100644 +--- a/pxr/base/arch/defines.h ++++ b/pxr/base/arch/defines.h +@@ -39,6 +39,8 @@ + #elif defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || \ + defined(_M_ARM64) + #define ARCH_CPU_ARM ++#elif defined(__riscv) ++#define ARCH_CPU_RISCV + #endif + + // +@@ -46,12 +48,13 @@ + // + + #if defined(__x86_64__) || defined(__aarch64__) || defined(_M_X64) || \ +- defined(_M_ARM64) || defined(__wasm64__) ++ defined(_M_ARM64) || defined(__wasm64__) || \ ++ (defined(__riscv) && __riscv_xlen == 64) + #define ARCH_BITS_64 + #elif defined(__wasm32__) + #define ARCH_BITS_32 + #else +-#error "Unsupported architecture. x86_64 or ARM64 required." ++#error "Unsupported architecture. x86_64, ARM64 or RISC-V 64 required." + #endif + + // +diff --git a/pxr/base/arch/math.h b/pxr/base/arch/math.h +index ccd53a6..8191023 100644 +--- a/pxr/base/arch/math.h ++++ b/pxr/base/arch/math.h +@@ -30,6 +30,7 @@ PXR_NAMESPACE_OPEN_SCOPE + ///@{ + + #if defined (ARCH_CPU_INTEL) || defined (ARCH_CPU_ARM) || \ ++ defined (ARCH_CPU_RISCV) || \ + defined(ARCH_OS_WASM_VM) || defined (doxygen) + + /// This is the smallest value e such that 1+e^2 == 1, using floats. +diff --git a/pxr/base/arch/overview.dox b/pxr/base/arch/overview.dox +index b8eaf9b..045375b 100644 +--- a/pxr/base/arch/overview.dox ++++ b/pxr/base/arch/overview.dox +@@ -64,6 +64,7 @@ For processor: + + + For bits: +diff --git a/pxr/base/arch/stackTrace.cpp b/pxr/base/arch/stackTrace.cpp +index 384959e..6a90d38 100644 +--- a/pxr/base/arch/stackTrace.cpp ++++ b/pxr/base/arch/stackTrace.cpp +@@ -878,6 +878,20 @@ nonLockingLinux__execve (const char *file, + ); + result = __file_result; + } ++#elif defined (ARCH_CPU_RISCV) && defined(ARCH_BITS_64) ++ { ++ long __file_result asm ("a0") = (long)file; ++ char* const* __argv asm ("a1") = argv; ++ char* const* __envp asm ("a2") = envp; ++ long __num_execve asm ("a7") = 221; ++ __asm__ __volatile__ ( ++ "ecall" ++ : "=r" (__file_result) ++ : "r"(__num_execve), "r" (__file_result), "r" (__argv), "r" (__envp) ++ : "memory" ++ ); ++ result = __file_result; ++ } + #elif defined(ARCH_CPU_INTEL) && defined(ARCH_BITS_64) + + /* +-- +2.43.0 + From 1ab99ed1de2365c7c7698c63c1cb32f8b5d21090 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 2 Oct 2026 21:49:16 +0000 Subject: [PATCH 2/6] usd-exchange: Install X11/Xt headers for MaterialXConfig.cmake MaterialX 1.39.5's exported MaterialXConfig.cmake calls find_dependency(X11 REQUIRED COMPONENTS Xt) on every non-Apple Unix, regardless of MATERIALX_BUILD_RENDER, so OpenUSD's find_package(MaterialX) fails to configure in the manylinux_2_39_riscv64 image, which has no X11 development packages. --- .github/workflows/build-usd-exchange.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-usd-exchange.yml b/.github/workflows/build-usd-exchange.yml index 9e24c1391f8..75c417cf1c5 100644 --- a/.github/workflows/build-usd-exchange.yml +++ b/.github/workflows/build-usd-exchange.yml @@ -119,8 +119,13 @@ jobs: # component needs the file to exist, and nothing ends up linking against it. "$PY" -c "import pathlib,sysconfig; pathlib.Path(sysconfig.get_config_var('LIBDIR'), sysconfig.get_config_var('LDLIBRARY')).touch()" + # MaterialX's installed MaterialXConfig.cmake runs find_dependency(X11 COMPONENTS Xt) + # on every non-Apple Unix, even with MATERIALX_BUILD_RENDER=OFF, so OpenUSD's and + # usdex's find_package(MaterialX) need the headers; nothing links against X11. + dnf install -y --setopt=install_weak_deps=False libX11-devel libXt-devel + USD=/work/_build/usd - # usdex only ships MaterialXCore/Format; the render modules want X11, absent from the image. + # usdex only ships MaterialXCore/Format; the render modules want OpenGL and X11. "$PY" OpenUSD/build_scripts/build_usd.py --onetbb \ --build-args USD,"-DPXR_PY_UNDEFINED_DYNAMIC_LOOKUP=ON -DPXR_BUILD_EXEC=OFF" \ MaterialX,"-DMATERIALX_BUILD_RENDER=OFF" \ From 2279f3ebad1888cc8ac6fdf8aa8f9e5cf5d26d15 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 3 Oct 2026 04:36:12 +0000 Subject: [PATCH 3/6] usd-exchange: Install OpenUSD Python bindings to lib/python OpenUSD 26.08 moved the default PXR_PYTHON_INSTALL_DIR from lib/python to lib/pythonX.Y/site-packages, so the pxr staging step (which mirrors upstream install_usdex.py and reads /lib/python/pxr) found no modules and exited on the first one: "pxr.Ar has no _ar binding". Pin the legacy layout of upstream's packman OpenUSD, as build-usd-core.yml already does. --- .github/workflows/build-usd-exchange.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-usd-exchange.yml b/.github/workflows/build-usd-exchange.yml index 75c417cf1c5..195040a2ea3 100644 --- a/.github/workflows/build-usd-exchange.yml +++ b/.github/workflows/build-usd-exchange.yml @@ -126,8 +126,11 @@ jobs: USD=/work/_build/usd # usdex only ships MaterialXCore/Format; the render modules want OpenGL and X11. + # OpenUSD 26.08 defaults PXR_PYTHON_INSTALL_DIR to lib/pythonX.Y/site-packages; + # pin the lib/python layout of upstream's packman OpenUSD, which install_usdex.py + # (and the staging below) reads pxr from. "$PY" OpenUSD/build_scripts/build_usd.py --onetbb \ - --build-args USD,"-DPXR_PY_UNDEFINED_DYNAMIC_LOOKUP=ON -DPXR_BUILD_EXEC=OFF" \ + --build-args USD,"-DPXR_PY_UNDEFINED_DYNAMIC_LOOKUP=ON -DPXR_BUILD_EXEC=OFF -DPXR_PYTHON_INSTALL_DIR=lib/python" \ MaterialX,"-DMATERIALX_BUILD_RENDER=OFF" \ --no-imaging --no-tools --no-examples --no-tutorials \ --build /work/_build/usd-build --src /work/_build/usd-src "${USD}" -v From 2b9e309f89634cab1de8c6a9c8a687e58c016f09 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 3 Oct 2026 10:11:19 +0000 Subject: [PATCH 4/6] usd-exchange: Fix second round of CI test failures (15 unit test failures) Group A (14 failures, testMaterialAlgo.py): usdex's own test extra declares usd-validation-nvidia as an open range (py_package.py: >=X, +Date: Sat, 3 Oct 2026 10:04:54 +0000 +Subject: [PATCH] Skip testEnableTranscodingSetting on riscv64 + +OpenUSD's ARCH_CACHE_LINE_SIZE (pxr/base/arch/align.h) has no riscv64 case and +falls back to x86_64's 64, which does not match this runner's L1 cache line +size, so Arch_ValidateAssumptions() (pxr/base/arch/assumptions.cpp) prints an +unconditional ' ArchWarn: ARCH_CACHE_LINE_SIZE != Arch_ObtainCacheLineSize()' +to stderr on every subprocess that imports pxr. This is an ARCH_WARNING, not +an ARCH_ERROR (riscv64 is little-endian, so the one check that would abort +is unaffected), and there is no per-arch 'correct' constant to patch in since +L1 cache line size varies across riscv64 implementations, unlike x86_64's +near-universal 64 bytes - see CLAUDE.md gotcha 630 for the full writeup. + +testEnableTranscodingSetting is the one test in the suite that asserts a +subprocess's stderr is byte-for-byte empty rather than matching a regex, so +it is the only one this unconditional diagnostic breaks; skip it on riscv64 +rather than patching OpenUSD's own startup diagnostic for one test. + +Upstream-Status: Inappropriate [riscv64 CI environment quirk; the ArchWarn itself is upstream's own intentional diagnostic and is not a bug] +--- + source/core/tests/unittest/testSettings.py | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/source/core/tests/unittest/testSettings.py b/source/core/tests/unittest/testSettings.py +index c51e902..89f222e 100644 +--- a/source/core/tests/unittest/testSettings.py ++++ b/source/core/tests/unittest/testSettings.py +@@ -3,8 +3,10 @@ + + import inspect + import os ++import platform + import subprocess + import sys ++import unittest + + import usdex.core + import usdex.test +@@ -50,6 +52,13 @@ class SettingsTest(usdex.test.TestCase): + else: + self.assertTrue(Tf.GetEnvSetting(usdex.core.enableTranscodingSetting)) + ++ @unittest.skipIf( ++ platform.machine() == "riscv64", ++ "OpenUSD's ARCH_CACHE_LINE_SIZE (pxr/base/arch/align.h) has no riscv64 case and falls back to x86_64's 64, which does not " ++ "match this runner's L1 cache line size, so every subprocess that imports pxr prints an unconditional " ++ "' ArchWarn: ARCH_CACHE_LINE_SIZE != Arch_ObtainCacheLineSize()' to stderr (pxr/base/arch/assumptions.cpp) that this test's " ++ "exact stderr-is-empty assertion cannot tolerate; see CLAUDE.md gotcha 630.", ++ ) + def testEnableTranscodingSetting(self): + # when enabled the transcoding algorithm is used to make valid identifiers + self.assertEnvSetting( +-- +2.43.0 + From b2b5e9d31aa71324b47d4f992165f5e88ca3f3bf Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 4 Oct 2026 01:31:09 +0000 Subject: [PATCH 5/6] usd-exchange: Skip OpenUSD's cache-line size check on riscv64 The third CI run failed the same four testDiagnostics.py tests on every leg (testLevel, testOutputFormatting, testOutputStream, testUtf8Diagnostics). They were already failing in the second run: its 15 failures were 10 testMaterialAlgo + 4 testDiagnostics + 1 testSettings, not 14 + 1, so skipping testEnableTranscodingSetting alone could never have been enough. All five share one cause. Every process that loads libusd_arch on these runners prints OpenUSD's four-line "ArchWarn: ARCH_CACHE_LINE_SIZE != Arch_ObtainCacheLineSize()" block to stderr, and the suite has two helpers that compare a pxr-importing subprocess's stderr exactly: assertOutputStreams() in testDiagnostics.py counts stderr lines, and assertEnvSetting() in testSettings.py asserts an empty stderr for expectedOutputPattern="". Every success-path assertOutputStreams() caller (the four tests above) and the one empty-pattern assertEnvSetting() caller fail. Everything else in both suites that reads stderr is tolerant of extra lines: testFatal uses assertIn/assertLessEqual, the other two testSettings tests use assertRegex (a search), testCore's usd-core conflict tests use assertIn/assertNotIn on strings the warning does not contain, testPxr checks only the return code, and the rtx suite and usdex.test helpers capture nothing (ScopedDiagnosticChecker goes through a Tf delegate, which never sees an fprintf). Instead of skipping each test, drop the narrow testSettings.py patch and fix the warning at its source with a second OpenUSD patch: on riscv64 the comparison has no correct answer. The ISA defines no cache-line size, glibc's riscv sysconf returns the kernel's AT_L1D_CACHEGEOMETRY auxv value, and the kernel fills that from the firmware's cache description (0 when it has none), so whatever align.h hardcodes is wrong on some machines. The warning is printed with a bare fprintf below Tf, so no environment variable can turn it off. It is also user-visible on every import of pxr from the published wheel, not only in the tests. Gate just that comparison on !ARCH_CPU_RISCV; the other Arch_ValidateAssumptions() checks are untouched, and both patches now apply to the OpenUSD checkout from a single step. --- .github/workflows/build-usd-exchange.yml | 5 +- ...tEnableTranscodingSetting-on-riscv64.patch | 57 ----------------- ...ine-size-assumption-check-on-riscv64.patch | 61 +++++++++++++++++++ 3 files changed, 62 insertions(+), 61 deletions(-) delete mode 100644 patches/usd-exchange/3.0.0/0002-Skip-testEnableTranscodingSetting-on-riscv64.patch create mode 100644 patches/usd-exchange/3.0.0/0002-Skip-the-cache-line-size-assumption-check-on-riscv64.patch diff --git a/.github/workflows/build-usd-exchange.yml b/.github/workflows/build-usd-exchange.yml index 6b13d31bc09..2a78bb1373e 100644 --- a/.github/workflows/build-usd-exchange.yml +++ b/.github/workflows/build-usd-exchange.yml @@ -92,10 +92,7 @@ jobs: persist-credentials: false - name: Patch OpenUSD - run: git -C OpenUSD apply "${GITHUB_WORKSPACE}"/python-wheels/patches/usd-exchange/${{ env.USD_EXCHANGE_VERSION }}/0001-*.patch - - - name: Patch usd-exchange - run: git apply "${GITHUB_WORKSPACE}"/python-wheels/patches/usd-exchange/${{ env.USD_EXCHANGE_VERSION }}/0002-*.patch + run: git -C OpenUSD apply "${GITHUB_WORKSPACE}"/python-wheels/patches/usd-exchange/${{ env.USD_EXCHANGE_VERSION }}/00*.patch - name: Pull manylinux_riscv64 image run: docker pull "${MANYLINUX_RISCV64_IMAGE}" diff --git a/patches/usd-exchange/3.0.0/0002-Skip-testEnableTranscodingSetting-on-riscv64.patch b/patches/usd-exchange/3.0.0/0002-Skip-testEnableTranscodingSetting-on-riscv64.patch deleted file mode 100644 index e052cc5262b..00000000000 --- a/patches/usd-exchange/3.0.0/0002-Skip-testEnableTranscodingSetting-on-riscv64.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0d561d00a88fee4ec3273121ed862c1974076441 Mon Sep 17 00:00:00 2001 -From: Ludovic Henry -Date: Sat, 3 Oct 2026 10:04:54 +0000 -Subject: [PATCH] Skip testEnableTranscodingSetting on riscv64 - -OpenUSD's ARCH_CACHE_LINE_SIZE (pxr/base/arch/align.h) has no riscv64 case and -falls back to x86_64's 64, which does not match this runner's L1 cache line -size, so Arch_ValidateAssumptions() (pxr/base/arch/assumptions.cpp) prints an -unconditional ' ArchWarn: ARCH_CACHE_LINE_SIZE != Arch_ObtainCacheLineSize()' -to stderr on every subprocess that imports pxr. This is an ARCH_WARNING, not -an ARCH_ERROR (riscv64 is little-endian, so the one check that would abort -is unaffected), and there is no per-arch 'correct' constant to patch in since -L1 cache line size varies across riscv64 implementations, unlike x86_64's -near-universal 64 bytes - see CLAUDE.md gotcha 630 for the full writeup. - -testEnableTranscodingSetting is the one test in the suite that asserts a -subprocess's stderr is byte-for-byte empty rather than matching a regex, so -it is the only one this unconditional diagnostic breaks; skip it on riscv64 -rather than patching OpenUSD's own startup diagnostic for one test. - -Upstream-Status: Inappropriate [riscv64 CI environment quirk; the ArchWarn itself is upstream's own intentional diagnostic and is not a bug] ---- - source/core/tests/unittest/testSettings.py | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/source/core/tests/unittest/testSettings.py b/source/core/tests/unittest/testSettings.py -index c51e902..89f222e 100644 ---- a/source/core/tests/unittest/testSettings.py -+++ b/source/core/tests/unittest/testSettings.py -@@ -3,8 +3,10 @@ - - import inspect - import os -+import platform - import subprocess - import sys -+import unittest - - import usdex.core - import usdex.test -@@ -50,6 +52,13 @@ class SettingsTest(usdex.test.TestCase): - else: - self.assertTrue(Tf.GetEnvSetting(usdex.core.enableTranscodingSetting)) - -+ @unittest.skipIf( -+ platform.machine() == "riscv64", -+ "OpenUSD's ARCH_CACHE_LINE_SIZE (pxr/base/arch/align.h) has no riscv64 case and falls back to x86_64's 64, which does not " -+ "match this runner's L1 cache line size, so every subprocess that imports pxr prints an unconditional " -+ "' ArchWarn: ARCH_CACHE_LINE_SIZE != Arch_ObtainCacheLineSize()' to stderr (pxr/base/arch/assumptions.cpp) that this test's " -+ "exact stderr-is-empty assertion cannot tolerate; see CLAUDE.md gotcha 630.", -+ ) - def testEnableTranscodingSetting(self): - # when enabled the transcoding algorithm is used to make valid identifiers - self.assertEnvSetting( --- -2.43.0 - diff --git a/patches/usd-exchange/3.0.0/0002-Skip-the-cache-line-size-assumption-check-on-riscv64.patch b/patches/usd-exchange/3.0.0/0002-Skip-the-cache-line-size-assumption-check-on-riscv64.patch new file mode 100644 index 00000000000..23a83b30b70 --- /dev/null +++ b/patches/usd-exchange/3.0.0/0002-Skip-the-cache-line-size-assumption-check-on-riscv64.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 4 Oct 2026 01:30:19 +0000 +Subject: [PATCH] Skip the cache-line size assumption check on riscv64 + +Arch_ValidateAssumptions() runs on every load of libusd_arch and compares +ARCH_CACHE_LINE_SIZE (pxr/base/arch/align.h: 128 on Apple ARM, 64 everywhere +else) with Arch_ObtainCacheLineSize(), i.e. sysconf(_SC_LEVEL1_DCACHE_LINESIZE) +on Linux. On a mismatch it prints a four-line " ArchWarn: ARCH_CACHE_LINE_SIZE +!= Arch_ObtainCacheLineSize()" block straight to stderr with fprintf +(arch/error.cpp), below the Tf diagnostics layer, so no environment setting or +diagnostic delegate can silence it. + +On riscv64 the comparison has nothing to check. The ISA defines no cache-line +size; it varies between implementations. glibc's riscv sysconf reads it from +the AT_L1D_CACHEGEOMETRY auxv entry, which the kernel fills from the +firmware's devicetree/ACPI cache description and sets to 0 when there is none +(arch/riscv/kernel/cacheinfo.c). Whatever value align.h picks for riscv64 is +therefore "wrong" on some machines, and the warning fires on every process +that imports pxr on those machines: every user of the wheel sees it, and +every test that checks a subprocess's stderr exactly fails because of it +(usd-exchange's testDiagnostics.py assertOutputStreams() and +testSettings.py's empty-stderr assertion). + +Keep ARCH_CACHE_LINE_SIZE at its 64 default (it only sizes padding and +alignment, so a mismatch costs performance, never correctness) and skip the +comparison when ARCH_CPU_RISCV is defined (added by the previous patch). The +other checks in Arch_ValidateAssumptions(), including the big-endian +ARCH_ERROR, are unchanged. + +Upstream-Status: To upstream [not submitted from this automated port run; belongs with the riscv64 arch support of the previous patch in a pull request against PixarAnimationStudios/OpenUSD] +--- + pxr/base/arch/assumptions.cpp | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/pxr/base/arch/assumptions.cpp b/pxr/base/arch/assumptions.cpp +index 1029b32..f0c63df 100644 +--- a/pxr/base/arch/assumptions.cpp ++++ b/pxr/base/arch/assumptions.cpp +@@ -135,10 +135,18 @@ Arch_ValidateAssumptions() + /* + * Make sure that the ARCH_CACHE_LINE_SIZE constant is set as expected + * on the current hardware architecture. ++ * ++ * RISC-V has no architectural cache-line size: it varies between ++ * implementations, and Linux reports whatever the firmware describes ++ * (0 when it describes none), so there is no constant to validate. + */ ++#if !defined(ARCH_CPU_RISCV) + if (ARCH_CACHE_LINE_SIZE != cacheLineSize) { + ARCH_WARNING("ARCH_CACHE_LINE_SIZE != Arch_ObtainCacheLineSize()"); + } ++#else ++ (void)cacheLineSize; ++#endif + + /* + * Make sure that the machine is little-endian. We do not support +-- +2.43.0 + From 2c15b2b506e97045e8ac80b7e6571c8edbd7b583 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 4 Oct 2026 19:00:13 +0000 Subject: [PATCH 6/6] usd-exchange: Upgrade pip in the test venv before installing the wheel python -m venv seeds pip from the interpreter's ensurepip bundle, which is 23.0.1 for cp310 and 24.0 for cp311 in the manylinux image. Both vendor packaging 21.3, whose manylinux arch list has no riscv64, so pip rejected the manylinux_2_39_riscv64 wheel as unsupported on those two legs only. --- .github/workflows/build-usd-exchange.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-usd-exchange.yml b/.github/workflows/build-usd-exchange.yml index 2a78bb1373e..254c022d183 100644 --- a/.github/workflows/build-usd-exchange.yml +++ b/.github/workflows/build-usd-exchange.yml @@ -292,6 +292,7 @@ jobs: VENV=/tmp/venv "/opt/python/${PYTHON_TAG}-${PYTHON_TAG}/bin/python" -m venv "${VENV}" + "${VENV}/bin/python" -m pip install -U pip wheel="$(echo /work/dist/*.whl)" "${VENV}/bin/python" -m pip install "${wheel}[test]"