Build the Bonsai Viewer in CI with the Autodesk connector bundled

Compile the Bonsai Viewer as part of the Linux and Windows binary builds,
and ship the Autodesk connector alongside the viewer executable.

Qt6 dependencies:
- The viewer links Qt6::Svg for runtime icon tinting. Svg is a separate
  base-Qt archive, so aqt now installs "qtbase qtsvg" (plus icu on Linux)
  rather than qtbase alone, on both Linux and Windows.
- Qt6::CorePrivate is exposed differently across Qt versions: Qt 6.8 ships
  the target inside Qt6Core, while Qt 6.10 provides it only as a separate
  CorePrivate config package. The viewer CMakeLists requests it via
  OPTIONAL_COMPONENTS so it resolves on both.
- When cross-compiling Windows ARM64, windeployqt runs from the host x64
  Qt, so qtsvg is installed into the host Qt as well.

Windows build:
- build-all-win.py passed -DBUILD_IFCVIEWER, a flag since renamed to
  BUILD_BONSAIVIEWER, so the Windows build compiled no viewer at all. It
  now passes -DBUILD_BONSAIVIEWER.
- The Autodesk connector is bundled under connectors/ next to
  BonsaiViewer.exe in the packaged archive, mirroring the Linux builds.
- The Windows workflow builds the connector (PyInstaller) before the main
  build so it is available to bundle.

Connector bundling:
- The Linux rocky workflows build the connector and bundle it into the
  BonsaiViewer archive; the Windows build now does the same.

Generated with the assistance of an AI coding tool.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-05-21 13:23:27 +10:00
parent e8a93846dd
commit de7520418b
7 changed files with 96 additions and 10 deletions
+24 -1
View File
@@ -32,6 +32,10 @@ assert Path.cwd() == Path(__file__).parent, "Run this script from the 'win' dire
PYTHON_VERSIONS = ["3.10.3", "3.11.8", "3.12.1", "3.13.0", "3.14.0"]
REPO_PATH = Path(__file__).parent.parent
REPO_WIN = REPO_PATH / "win"
# Prebuilt Autodesk connector folder, produced by the connector's packaging
# build.py. The CI workflow builds it before invoking this script; it gets
# bundled next to BonsaiViewer.exe so the viewer can discover it at runtime.
CONNECTOR_DIR = REPO_PATH / "src" / "bonsaiviewer-autodesk" / "dist" / "autodesk"
VERSION = (REPO_PATH / "VERSION").read_text().strip()
if "GITHUB_SHA" in os.environ:
SHA = os.environ["GITHUB_SHA"][:7]
@@ -163,6 +167,21 @@ def collect_qt_deployment_files(install_dir: Path) -> dict[str, Path]:
return files
def collect_connector_files() -> dict[str, Path]:
"""Map the prebuilt Autodesk connector to ``connectors/autodesk/`` arcnames."""
if not CONNECTOR_DIR.is_dir():
raise RuntimeError(
f"Autodesk connector not found at {CONNECTOR_DIR}. Build it first with: "
"python src/bonsaiviewer-autodesk/packaging/build.py"
)
files: dict[str, Path] = {}
for file in CONNECTOR_DIR.rglob("*"):
if file.is_file():
arcname = Path("connectors") / "autodesk" / file.relative_to(CONNECTOR_DIR)
files[arcname.as_posix()] = file
return files
def write_zip(zip_path: Path, files: dict[str, Path], generated_files: dict[str, str] | None = None) -> None:
zip_path.parent.mkdir(exist_ok=True)
with ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
@@ -190,7 +209,7 @@ def build() -> None:
"-DENABLE_BUILD_OPTIMIZATIONS=ON",
"-DGLTF_SUPPORT=ON",
"-DBUILD_EXAMPLES=OFF",
"-DBUILD_IFCVIEWER=ON",
"-DBUILD_BONSAIVIEWER=ON",
]
)
restore_env(*OLD_ADD_COMMIT_SHA)
@@ -232,6 +251,10 @@ def archive_executables() -> None:
files[arcname] = dependency
generated_files["qt.conf"] = QT_CONF
# Bundle the Autodesk connector next to the Bonsai Viewer executable.
if file.stem == "BonsaiViewer":
files.update(collect_connector_files())
zip_name = ZIP_TEMPLATE.format(package_name=file.stem)
write_zip(OUTPUT_DIR / zip_name, files, generated_files)
print(f"{file} -> {zip_name}")
+14 -4
View File
@@ -804,11 +804,11 @@ IF DEFINED QT6_HOST_INSTALL_DIR (
)
set QT6_TARGET_INSTALLED=FALSE
IF EXIST "%QT6_INSTALL_DIR%\lib\cmake\Qt6\Qt6Config.cmake" IF EXIST "%QT6_INSTALL_DIR%\bin\%QT6_CONFIG_DLL%" set QT6_TARGET_INSTALLED=TRUE
IF EXIST "%QT6_INSTALL_DIR%\lib\cmake\Qt6\Qt6Config.cmake" IF EXIST "%QT6_INSTALL_DIR%\bin\%QT6_CONFIG_DLL%" IF EXIST "%QT6_INSTALL_DIR%\lib\cmake\Qt6Svg\Qt6SvgConfig.cmake" set QT6_TARGET_INSTALLED=TRUE
set QT6_HOST_INSTALLED=TRUE
IF DEFINED QT6_HOST_INSTALL_DIR (
set QT6_HOST_INSTALLED=FALSE
IF EXIST "%QT6_HOST_INSTALL_DIR%\lib\cmake\Qt6\Qt6Config.cmake" IF EXIST "%QT6_HOST_INSTALL_DIR%\bin\moc.exe" IF EXIST "%QT6_HOST_INSTALL_DIR%\bin\rcc.exe" set QT6_HOST_INSTALLED=TRUE
IF EXIST "%QT6_HOST_INSTALL_DIR%\lib\cmake\Qt6\Qt6Config.cmake" IF EXIST "%QT6_HOST_INSTALL_DIR%\bin\moc.exe" IF EXIST "%QT6_HOST_INSTALL_DIR%\bin\rcc.exe" IF EXIST "%QT6_HOST_INSTALL_DIR%\lib\cmake\Qt6Svg\Qt6SvgConfig.cmake" set QT6_HOST_INSTALLED=TRUE
)
IF "%QT6_TARGET_INSTALLED%"=="TRUE" IF "%QT6_HOST_INSTALLED%"=="TRUE" (
@@ -825,13 +825,18 @@ IF "%IFCOS_INSTALL_PYTHON%"=="TRUE" IF EXIST "%PYTHONHOME%\python.exe" set AQT_P
IF NOT %ERRORLEVEL%==0 GOTO :Error
IF NOT "%QT6_TARGET_INSTALLED%"=="TRUE" (
%AQT_PYTHON% -m aqt install-qt windows desktop %QT6_VERSION% %QT6_ARCH% -O "%QT6_AQT_OUTPUT_DIR%" --archives qtbase
REM Keep the install lean by filtering archives: qtbase provides
REM Core/Gui/Widgets (and the Qt6::CorePrivate target), qtsvg provides
REM Qt6::Svg. Both are base-Qt archives, not add-on modules.
%AQT_PYTHON% -m aqt install-qt windows desktop %QT6_VERSION% %QT6_ARCH% -O "%QT6_AQT_OUTPUT_DIR%" --archives qtbase qtsvg
IF ERRORLEVEL 1 GOTO :Error
)
IF DEFINED QT6_HOST_INSTALL_DIR (
IF NOT "%QT6_HOST_INSTALLED%"=="TRUE" (
%AQT_PYTHON% -m aqt install-qt windows desktop %QT6_VERSION% %QT6_HOST_ARCH% -O "%QT6_HOST_AQT_OUTPUT_DIR%" --archives qtbase
REM windeployqt runs from the host Qt when cross-compiling ARM64, so the
REM host Qt needs qtsvg too to deploy the Bonsai Viewer's Qt6Svg dependency.
%AQT_PYTHON% -m aqt install-qt windows desktop %QT6_VERSION% %QT6_HOST_ARCH% -O "%QT6_HOST_AQT_OUTPUT_DIR%" --archives qtbase qtsvg
IF ERRORLEVEL 1 GOTO :Error
)
)
@@ -846,6 +851,11 @@ IF NOT EXIST "%QT6_INSTALL_DIR%\bin\%QT6_CONFIG_DLL%" (
GOTO :Error
)
IF NOT EXIST "%QT6_INSTALL_DIR%\lib\cmake\Qt6Svg\Qt6SvgConfig.cmake" (
call cecho.cmd 0 12 "Qt6 installation did not produce the Qt6 Svg module at %QT6_INSTALL_DIR%."
GOTO :Error
)
IF DEFINED QT6_HOST_INSTALL_DIR (
IF NOT EXIST "%QT6_HOST_INSTALL_DIR%\lib\cmake\Qt6\Qt6Config.cmake" (
call cecho.cmd 0 12 "Qt6 host installation did not produce Qt6Config.cmake at %QT6_HOST_INSTALL_DIR%."