Files
IfcOpenShell/.github/workflows/build_rocky_arm.yml
T
Dion Moult 5739a24808 CI: make the Autodesk connector workflow a test gate, ship it on macOS
Three related cleanups to how the bonsaiviewer-autodesk connector is built
and shipped.

build-bonsaiviewer-autodesk.yml no longer builds a bundle. Its four-runner
matrix produced autodesk-<os>-<arch>.zip artifacts that nothing consumed —
shipping happens in the platform pipelines, which each invoke
packaging/build.py themselves. What is left is the crate's only lint and
test coverage, so the workflow is renamed to match what it does and a
header comment records where the shipped binary actually comes from.

build_osx.yml now builds and bundles the connector, which it never did:
macOS users have been getting a Bonsai Viewer with no Autodesk connector at
all. ConnectorDiscovery resolves applicationDirPath()/connectors, which
inside a bundle is Contents/MacOS, so that is where the folder lands.

The tkinter probes in the Windows and Linux workflows are dropped. They
guarded the old PyInstaller connector's Tk GUI (de7520418) and have been
dead since the Rust rewrite (9d9f4054f); python3.11-tkinter goes with them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:43:28 +10:00

280 lines
12 KiB
YAML

name: Build IfcOpenShell Linux ARM
on:
workflow_dispatch:
jobs:
build_ifcopenshell:
runs-on: ubuntu-22.04-arm
# Rocky 10 (glibc 2.39) — aqt's official Qt6 ARM binaries are built
# against glibc 2.38, which Rocky 9 (glibc 2.34) can't run (moc fails to
# load). The legacy arm64v8/rockylinux Docker image stopped at 9; Rocky 10
# is published under the rockylinux/rockylinux namespace.
container:
image: rockylinux/rockylinux:10
# The community rockylinux image omits PATH from its config (the old
# Docker Official arm64v8/rockylinux set it), so GitHub Actions `run:`
# steps fail with `exec: "sh": not found` — docker exec has no /usr/bin
# to resolve the shell. Restore a standard PATH; GITHUB_PATH prepends
# (uv, cargo) are still layered on top by the runner.
env:
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
steps:
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
- name: Install Python
# Installs latest Python version so it's preferred by uv over Rocky's system Python.
run: uv python install
- name: Install Dependencies
run: |
dnf update -y
dnf install -y epel-release
# --enablerepo=crb: libstdc++-static (libsupc++.a, needed by the
# bundled FLTK link) lives in Rocky's CodeReady Builder repo, which
# is disabled by default.
dnf install -y --enablerepo=crb gcc gcc-c++ git autoconf automake bison make zip cmake python3 python3-pip \
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
findutils xz byacc patchelf libxkbcommon-devel \
dbus-devel \
libXext-devel libXinerama-devel libXcursor-devel libXrender-devel \
libXfixes-devel libXft-devel pango-devel cairo-devel libstdc++-static
python3 -m pip install aqtinstall
git config --global --add safe.directory '*'
- name: Install Rust
# The bonsaiviewer-autodesk connector is a Rust crate; the "Package
# .zip archives" step below runs `cargo build --release` via
# packaging/build.py. Match the dedicated connector workflow's stable
# toolchain (dtolnay/rust-toolchain@stable).
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Install aws cli
run: |
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
./aws/install
rm -rf awscliv2.zip aws
aws --version
- name: Checkout Repository
uses: actions/checkout@v7
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v7
with:
repository: IfcOpenShell/build-outputs
path: ./build
ref: rockylinux9-arm64
lfs: true
token: ${{ secrets.BUILD_REPO_TOKEN }}
- name: Unpack Dependencies
run: |
cd build
uv run ../nix/cache_dependencies.py unpack
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.23
with:
key: ubuntu-22.04-${{ runner.arch }}-rockylinux10
- name: Run Build Script
shell: bash
run: |
set -o pipefail
CXXFLAGS="-O3" CFLAGS="-O3" ADD_COMMIT_SHA=1 BUILD_CFG=Release BUILD_BONSAIVIEWER=ON \
uv run --with aqtinstall ./nix/build-all.py \
-v --diskcleanup --shared 2>&1 \
| tee build.log
- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v7
with:
name: build-logs-rocky-arm64
path: |
build.log
build/*/*/logs/*.log
retention-days: 30
- name: Pack Dependencies
run: |
cd build
uv run ../nix/cache_dependencies.py pack
- name: Commit and Push Changes to Build Repository
run: |
cd build
git config user.name "IfcOpenBot"
git config user.email "ifcopenbot@ifcopenshell.org"
git add "$(find . -maxdepth 4 -name install)/*.tar.gz"
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
git push || true
- name: Package .zip archives
shell: bash
run: |
VERSION=v`cat VERSION`
# bonsaiviewer-autodesk is now a Rust connector. packaging/build.py
# invokes `cargo build --release` and stages the binary +
# connector.json into dist/autodesk/. Same on-disk shape as the
# old PyInstaller flow so the symlink + zip steps below
# continue to work unchanged.
python3 src/bonsaiviewer-autodesk/packaging/build.py
autodesk_connector_dir="$PWD/src/bonsaiviewer-autodesk/dist/autodesk"
test -d "$autodesk_connector_dir"
cd ./build/`uname`/*/install/ifcopenshell
mkdir -p ~/output
install_root="$PWD"
QT6_VERSION="${QT6_VERSION:-6.8.3}"
if [ -z "${QT_DIR:-}" ]; then
for qt_candidate in "$(dirname "$install_root")"/qt6-${QT6_VERSION}-*/${QT6_VERSION}/*; do
if [ -d "$qt_candidate/lib" ]; then
QT_DIR="$qt_candidate"
break
fi
done
fi
ensure_soname_links() {
dest="$1"
find "$dest" -maxdepth 1 -type f -name "*.so*" | while IFS= read -r shared_object; do
soname=$(readelf -d "$shared_object" 2>/dev/null | sed -n 's/.*(SONAME).*Shared library: \[\(.*\)\].*/\1/p' | head -n 1)
[ -n "$soname" ] || continue
[ -e "$dest/$soname" ] && continue
ln -s "$(basename "$shared_object")" "$dest/$soname"
done
}
stage_runtime_payload() {
dest="$1"
include_geometry_writers="${2:-1}"
while IFS= read -r runtime_file; do
if [ "$include_geometry_writers" != "1" ] && [[ "$(basename "$runtime_file")" == ifcopenshell.geometry.writer.* ]]; then
continue
fi
cp -P "$runtime_file" "$dest/"
done < <(
for runtime_dir in "$install_root/bin" "$install_root/lib" "$install_root/lib64"; do
[ -d "$runtime_dir" ] || continue
find "$runtime_dir" \( -type f -o -type l \) \( -name "*.so" -o -name "*.so.*" -o -name "*.dylib" -o -name "*.dll" \)
done
)
ensure_soname_links "$dest"
}
stage_qt_runtime_payload() {
exe_path="$1"
dest="$2"
[ -n "${QT_DIR:-}" ] && [ -d "$QT_DIR/lib" ] || return 0
if ! LD_LIBRARY_PATH="$QT_DIR/lib:${LD_LIBRARY_PATH:-}" ldd "$exe_path" 2>/dev/null | grep -q "libQt6"; then
return 0
fi
find "$QT_DIR/lib" -maxdepth 1 \( -type f -o -type l \) -name "*.so*" -exec cp -P {} "$dest/" \;
ensure_soname_links "$dest"
if [ -d "$QT_DIR/plugins" ]; then
pushd "$QT_DIR/plugins" > /dev/null
find . \( -type f -o -type l \) -name "*.so*" | while IFS= read -r plugin_file; do
mkdir -p "$dest/plugins/$(dirname "$plugin_file")"
cp -P "$plugin_file" "$dest/plugins/$plugin_file"
done
popd > /dev/null
if [ -d "$dest/plugins" ]; then
find "$dest/plugins" -type f -name "*.so*" -exec patchelf --set-rpath '$ORIGIN/../..:$ORIGIN' {} \;
fi
fi
find "$dest" -maxdepth 1 -type f -name "*.so*" -exec patchelf --set-rpath '$ORIGIN' {} \;
printf "[Paths]\nPrefix = .\n" > "$dest/qt.conf"
}
check_runtime_dependencies() {
package_dir="$1"
missing=0
while IFS= read -r binary_file; do
readelf -h "$binary_file" >/dev/null 2>&1 || continue
if ! env -u LD_LIBRARY_PATH ldd "$binary_file" > "$package_dir/.ldd.out" 2>&1; then
echo "ldd failed for $binary_file"
cat "$package_dir/.ldd.out"
missing=1
continue
fi
if grep -q "not found" "$package_dir/.ldd.out"; then
echo "Missing runtime dependencies for $binary_file"
grep "not found" "$package_dir/.ldd.out"
missing=1
fi
done < <(find "$package_dir" -type f \( -perm /111 -o -name "*.so" -o -name "*.so.*" \))
rm -f "$package_dir/.ldd.out"
if [ "$missing" -ne 0 ]; then
echo "Runtime dependency check found issues; continuing packaging."
fi
return 0
}
ls -d python-* | while read py_version; do
postfix=`echo ${py_version: -1} | sed s/[0-9]//`
numbers=`echo $py_version | grep -oE '[0-9]+\.[0-9]+' | tr -d '.'`
py_version_major=python-${numbers}$postfix
pushd . > /dev/null
cd $py_version
if [ ! -d ifcopenshell ]; then
mkdir ../ifcopenshell_
mv * ../ifcopenshell_
mv ../ifcopenshell_ ifcopenshell
fi
[ -d ifcopenshell/__pycache__ ] && rm -rf ifcopenshell/__pycache__
find ifcopenshell -name "*.pyc" -delete
stage_runtime_payload ifcopenshell
zip -y -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip ifcopenshell
mv *.zip ~/output
popd > /dev/null
done
rm -f "$install_root"/bin/*.zip
find "$install_root/bin" -maxdepth 1 -type f -perm /111 ! -name "*.zip" ! -name "*.so" ! -name "*.so.*" ! -name "*.dylib" ! -name "*.dll" | while read exe_path; do
exe=`basename "$exe_path"`
package_dir="$install_root/.package-${exe}"
rm -rf "$package_dir"
mkdir -p "$package_dir"
cp "$exe_path" "$package_dir/"
patchelf --set-rpath '$ORIGIN' "$package_dir/$exe"
stage_runtime_payload "$package_dir" 0
stage_qt_runtime_payload "$exe_path" "$package_dir"
if [ "$exe" = "BonsaiViewer" ]; then
mkdir -p "$package_dir/connectors"
cp -a "$autodesk_connector_dir" "$package_dir/connectors/"
fi
check_runtime_dependencies "$package_dir"
pushd "$package_dir" > /dev/null
zip -y -qq -r "$HOME/output/${exe}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip" .
popd > /dev/null
rm -rf "$package_dir"
done
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Upload .zip archives to S3
run: |
aws s3 cp ~/output s3://ifcopenshell-builds/ --recursive