Skip to content

build-xprof

build-xprof #51

Workflow file for this run

# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
#
# This workflow is based on:
# https://github.com/openxla/xprof/blob/main/README.md#build-from-source (bazel run
# plugin:build_pip_package) and plugin/build_pip_package.sh.
---
name: Build xprof wheels (riscv64)
on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/xprof.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-xprof.yml'
- 'docs/packages/xprof.yaml'
- 'patches/xprof/**'
push:
branches: [main]
paths:
- '.github/workflows/build-xprof.yml'
- 'docs/packages/xprof.yaml'
- 'patches/xprof/**'
run-name: build-xprof ${{ inputs.version && format('- {0}', inputs.version) || '' }}
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
# xprof's .bazelversion (7.4.1) predates the bazel_features/apple_support chain that
# forces bootstrapping a newer release here (see build-ai-edge-litert.yml), so it
# bootstraps directly.
BAZEL_VERSION: '7.4.1'
# versions bazel 7.4.1's MODULE.bazel pins; both need a riscv64 fix below
RULES_PYTHON_VERSION: '0.33.2'
RULES_JAVA_VERSION: '7.6.5'
HERMETIC_PYTHON_VERSION: '3.12'
jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: xprof
version: ${{ inputs.version }}
# xprof's frontend (trace_viewer_v2.wasm, bundle.js, ...) is an Angular/rollup build
# with no riscv64-specific code; node.js publishes no riscv64 release, so it is built
# once here with upstream's own hermetic toolchain, on the host arch node supports.
# Byte-identical output was confirmed across the upstream macOS/Linux/Windows wheels.
reference_build:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
name: Build xprof ${{ matrix.version }} reference package (frontend + python)
runs-on: ubuntu-24.04
timeout-minutes: 360
env:
XPROF_VERSION: ${{ matrix.version }}
steps:
- name: Checkout xprof xprof-v${{ env.XPROF_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: openxla/xprof
ref: xprof-v${{ env.XPROF_VERSION }}
fetch-depth: 1
persist-credentials: false
- name: Free disk space
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
- name: Install bazel ${{ env.BAZEL_VERSION }}
run: |
sudo curl -fsSLo /usr/local/bin/bazel \
"https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-linux-x86_64"
sudo chmod +x /usr/local/bin/bazel
- name: Build reference package
run: |
bazel run //plugin:build_pip_package -- --output "${GITHUB_WORKSPACE}/dist"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: xprof-${{ env.XPROF_VERSION }}-reference
path: dist
if-no-files-found: error
bazel:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
name: Bootstrap bazel (riscv64)
runs-on: ubuntu-24.04-riscv
timeout-minutes: 720
steps:
- name: Restore bazel binary
id: cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: bazel-bin
key: bazel-${{ env.BAZEL_VERSION }}-manylinux_riscv64
- name: Bootstrap bazel ${{ env.BAZEL_VERSION }}
if: steps.cache.outputs.cache-hit != 'true'
run: |
mkdir -p bazel-bin
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-w /work \
-e BAZEL_VERSION \
-e RULES_PYTHON_VERSION \
-e RULES_JAVA_VERSION \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT'
set -eux
dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel zip unzip
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
export JAVA_HOME
# rules_python 0.33.2's PLATFORMS has no riscv64 entry, aborting the bootstrap
# (bazelbuild/bazel#23018). Any linux entry is a safe stand-in: the toolchain it names
# is never selected on a riscv64 host. Fixed in bazel 8.2.0; the 7.x backport is open.
mkdir -p /tmp/rules_python
curl -fsSLo /tmp/rules_python.tar.gz "https://github.com/bazel-contrib/rules_python/releases/download/${RULES_PYTHON_VERSION}/rules_python-${RULES_PYTHON_VERSION}.tar.gz"
tar -xzf /tmp/rules_python.tar.gz -C /tmp/rules_python --strip-components=1
sed -i 's|fail("No platform declared for host OS {} on arch {}".format(os_name, arch))|return "x86_64-unknown-linux-gnu"|' \
/tmp/rules_python/python/private/toolchains_repo.bzl
# rules_java 7.x maps riscv64 to a stray-colon include path, so a JNI library
# can't find jni_md.h. Fixed in rules_java 8.x, never backported.
mkdir -p /tmp/rules_java
curl -fsSLo /tmp/rules_java.tar.gz "https://github.com/bazelbuild/rules_java/releases/download/${RULES_JAVA_VERSION}/rules_java-${RULES_JAVA_VERSION}.tar.gz"
tar -xzf /tmp/rules_java.tar.gz -C /tmp/rules_java
sed -i 's|\[":include/linux"\]|["include/linux"]|g' /tmp/rules_java/toolchains/BUILD
mkdir -p /tmp/bazel-src
cd /tmp/bazel-src
curl -fsSLo dist.zip "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-dist.zip"
unzip -q dist.zip
# java-21-openjdk's persistent Javac worker crashes under this image's glibc
# ("Fatal glibc error: pthread_mutex_lock.c: assertion failed:
# mutex->__data.__owner == 0") once several multiplex-worker threads race;
# standalone forks a fresh javac per action instead of reusing one worker JVM.
EXTRA_BAZEL_ARGS="--tool_java_runtime_version=local_jdk \
--override_module=rules_python=/tmp/rules_python \
--override_module=rules_java=/tmp/rules_java \
--strategy=Javac=standalone" \
bash ./compile.sh
install -m 0755 output/bazel /work/bazel-bin/bazel
SCRIPT
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: bazel-${{ env.BAZEL_VERSION }}-riscv64
path: bazel-bin/bazel
if-no-files-found: error
build_wheels:
name: Build xprof ${{ matrix.version }} py3-none-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 1440 # 24h
needs: [setup, bazel, reference_build]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
env:
XPROF_VERSION: ${{ matrix.version }}
steps:
- name: Checkout xprof xprof-v${{ env.XPROF_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: openxla/xprof
ref: xprof-v${{ env.XPROF_VERSION }}
path: xprof
fetch-depth: 1
persist-credentials: false
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: python-wheels
fetch-depth: 1
persist-credentials: false
- name: Download bazel
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: bazel-${{ env.BAZEL_VERSION }}-riscv64
path: bazel-bin
- name: Download reference package
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: xprof-${{ env.XPROF_VERSION }}-reference
path: dist-reference
- name: Build wheel
env:
HERMETIC_PYTHON_VERSION: ${{ env.HERMETIC_PYTHON_VERSION }}
run: |
mkdir -p wheelhouse
set -o pipefail
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-w /work \
-e HERMETIC_PYTHON_VERSION \
-e XPROF_VERSION \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT' 2>&1 | tee build.log
set -eux
# clang, because xprof (via @xla) ships no hermetic riscv64 C++ toolchain and
# --config=clang_local (already used upstream for non-x86_64/aarch64 hosts, see
# xla/tsl:ci_linux_aarch64) is the documented off-switch. clang_local only disables
# the hermetic/toolchain-resolution machinery though - it does not itself select
# clang, so CC must be set explicitly or Bazel's local_config_cc autodetection
# defaults to plain `gcc` (bazelbuild/bazel tools/cpp/unix_cc_configure.bzl
# find_cc()). Left unset, that silently built most of the tree with gcc and only
# broke on com_google_highway's riscv64 copts, which unconditionally add the
# Clang-only `-menable-experimental-extensions` flag (google/highway BUILD.bazel)
# that gcc has never implemented (absent from GCC's RISC-V Options docs, any
# version).
# lld too: profiler_plugin_c_api.so links with -Wl,--icf=all (upstream's hermetic
# toolchain is clang+lld), which GNU ld rejects; with lld present, Bazel's
# local_config_cc autodetects it and adds -fuse-ld=lld to every link.
dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel zip unzip clang lld
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
export JAVA_HOME
CC=clang
CXX=clang++
export CC CXX
install -m 0755 /work/bazel-bin/bazel /usr/local/bin/bazel
git config --global --add safe.directory '*'
cd /work/xprof
# Only the native extension: the frontend and every pure-Python file come
# from the reference package built in the reference_build job, which is
# architecture-independent (confirmed byte-identical across upstream's own
# macOS/Linux/Windows wheels). 2.23.1 predates xprof/convert/events_db (the
# Arrow/Parquet events database, added upstream after this release) - the
# real released wheel carries only profiler_plugin_c_api.so, confirmed by
# unzipping it.
BAZEL_FLAGS=(
-c opt --config=clang_local
--repo_env=HERMETIC_PYTHON_VERSION="${HERMETIC_PYTHON_VERSION}"
--repo_env=PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
--strategy=Javac=standalone
--curses=no --show_progress_rate_limit=60
)
TARGET=//xprof/pywrap:profiler_plugin_c_api.so
# patches/xprof/<version> are against @llvm-raw (the LLVM @xla pins), not
# xprof: the image's clang 21.1.8 crashes on MLIR's BuiltinDialectBytecode.cpp
# (llvm/llvm-project#188249). Fetch without building, then patch in place.
bazel build "${BAZEL_FLAGS[@]}" --nobuild "${TARGET}"
LLVM_RAW="$(bazel info output_base)/external/llvm-raw"
for patch in /work/python-wheels/patches/xprof/"${XPROF_VERSION}"/*.patch; do
git -C "${LLVM_RAW}" apply -v "${patch}"
done
bazel build "${BAZEL_FLAGS[@]}" "${TARGET}"
cp -a /work/dist-reference /work/dist
cp bazel-bin/xprof/pywrap/profiler_plugin_c_api.so \
/work/dist/xprof/convert/profiler_plugin_c_api.so
PYTHON_BIN="/opt/python/cp312-cp312/bin/python"
"${PYTHON_BIN}" -m pip install -q -U pip setuptools wheel auditwheel
cd /work/dist
"${PYTHON_BIN}" -m pip wheel . --no-deps --no-build-isolation -w /work/wheelhouse-raw
auditwheel repair --plat manylinux_2_39_riscv64 -w /work/wheelhouse \
/work/wheelhouse-raw/xprof-*.whl
"${PYTHON_BIN}" - <<'PY'
import glob, zipfile
whl = sorted(glob.glob("/work/wheelhouse/xprof-*.whl"))[0]
names = zipfile.ZipFile(whl).namelist()
for want in (
"xprof/convert/profiler_plugin_c_api.so",
"xprof/static/bundle.js",
"xprof/static/trace_viewer_v2.wasm",
):
if not any(want in n for n in names):
raise SystemExit(f"error: {want} missing from {whl}")
print(whl)
PY
SCRIPT
- name: Upload build log
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: xprof-${{ env.XPROF_VERSION }}-build-log
path: build.log
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: xprof-${{ env.XPROF_VERSION }}-py3-none-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error
- name: Test wheel
env:
PIP_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/
run: |
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-w /tmp \
-e PIP_EXTRA_INDEX_URL \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT'
set -eux
PYTHON_BIN="/opt/python/cp312-cp312/bin/python"
"${PYTHON_BIN}" -m pip install --only-binary=:all: /work/wheelhouse/*.whl
"${PYTHON_BIN}" - <<'PY'
from xprof.convert import _pywrap_profiler_plugin # noqa: F401
from xprof import version
print("xprof", version.__version__)
PY
SCRIPT
publish:
name: Publish xprof ${{ 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: xprof-${{ matrix.version }}-*-manylinux_riscv64