From 9403c8f973fc044e67947cbcd4c251761ab84b08 Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Tue, 28 Apr 2026 11:55:18 -0600 Subject: [PATCH 1/6] Add docker build container for AI agents to use --- .../instructions/docker-build.instructions.md | 107 ++++++++++++ .github/workflows/docker-ci.yml | 96 +++++++++++ Makefile | 158 ++++++++++++++++++ docker/Dockerfile | 50 ++++++ docker/configure.sh | 84 ++++++++++ 5 files changed, 495 insertions(+) create mode 100644 .github/instructions/docker-build.instructions.md create mode 100644 .github/workflows/docker-ci.yml create mode 100644 Makefile create mode 100644 docker/Dockerfile create mode 100644 docker/configure.sh diff --git a/.github/instructions/docker-build.instructions.md b/.github/instructions/docker-build.instructions.md new file mode 100644 index 000000000..81bfb0fc5 --- /dev/null +++ b/.github/instructions/docker-build.instructions.md @@ -0,0 +1,107 @@ +--- +description: "Use when building, testing, or validating C++ changes. Covers how to compile the project inside the Docker build container using Makefile targets." +applyTo: "src/**/*.cpp,src/**/*.hpp,src/**/CMakeLists.txt,docker/**,Makefile" +--- + +# Building and Testing with the Docker Build Container + +All compilation and testing happens inside the Docker container. Never run +`cmake`, `conan`, `ninja`, or `ctest` directly on the host. Use `make` +targets from the project root instead. + +--- + +## Prerequisites + +Docker must be running. On a fresh checkout, build the image once: + +```bash +make image +``` + +This is slow (~20 min) because it bakes Qt 6.11.0 and the OpenStudio SDK into +the image. Re-run only when `docker/Dockerfile` changes. + +--- + +## Typical workflow + +### First time after checkout (or after `make clean`) + +```bash +make configure # Conan install + cmake configure +make build # Compile +make test # Run all CTest tests +``` + +### Incremental build after editing source files + +```bash +make build # Recompiles only changed translation units (ccache accelerated) +make test # Re-runs tests +``` + +### After changing `conanfile.py` or `CMakeLists.txt` + +```bash +make configure # Re-run conan + cmake before building +make build +make test +``` + +--- + +## All available targets + +| Target | When to use | +|--------|-------------| +| `make image` | Rebuild the Docker image (Dockerfile changed) | +| `make configure` | After checkout, after `conanfile.py`/`CMakeLists.txt` changes, or after `make clean` | +| `make build` | After any source file edit | +| `make test` | Validate correctness; runs `xvfb-run ctest -j` for headless Qt tests | +| `make cppcheck` | Static analysis; output written to `build/cppcheck-results.txt` | +| `make shell` | Interactive bash shell inside the container for debugging | +| `make clean` | Delete `build/` (keeps Conan + ccache volumes) | +| `make volumes-clean` | Destroy Conan and ccache named volumes (forces full dependency rebuild) | + +--- + +## Validating a refactoring step + +After every logical change (e.g. one item from `developer/doc/refactoring-ideas.md`): + +1. `make build` — must exit 0 with no new warnings. +2. `make test` — all previously passing tests must still pass. +3. If a header or CMakeLists.txt was modified: `make configure && make build && make test`. + +Do not proceed to the next refactoring step until both commands succeed. + +--- + +## Reading build output + +Build artefacts and logs land in `build/` (git-ignored). Key paths inside +the container (mapped to the same path on the host): + +| Path | Contents | +|------|----------| +| `build/compile_commands.json` | Compilation database (used by cppcheck and clangd) | +| `build/Testing/Temporary/LastTest.log` | CTest output from the most recent run | +| `build/cppcheck-results.txt` | Static analysis output from `make cppcheck` | + +--- + +## Troubleshooting + +**`make configure` fails with a Conan error about a missing package** +Run `make volumes-clean` then `make configure` again to rebuild the Conan cache from scratch. + +**`make image` fails to pull `ubuntu:22.04`** +Docker Desktop cannot reach `docker.io`. Restart Docker Desktop or check +proxy/VPN settings. Run `docker pull ubuntu:22.04` to confirm connectivity +before retrying. + +**Tests fail due to a missing display** +The container includes `xvfb`; `make test` already wraps ctest with +`xvfb-run`. If running commands manually inside `make shell`, prefix with +`xvfb-run` for any test that opens a Qt window. diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml new file mode 100644 index 000000000..24c2364ee --- /dev/null +++ b/.github/workflows/docker-ci.yml @@ -0,0 +1,96 @@ +# Docker-based CI for pull requests. +# +# Builds the project inside the osapp2-build Docker image (Ubuntu 22.04 with +# Qt 6.11.0, OpenStudio SDK 3.11.0, and Conan 2 pre-installed) and runs +# CTest. The Docker image layers are cached via the GitHub Actions cache +# backend so the slow Qt/SDK bake step is only repeated when the Dockerfile +# changes. +# +# Runs on: pull_request to master or develop (non-draft only) + +name: Docker CI + +on: + pull_request: + branches: [ master, develop ] + types: [ opened, reopened, synchronize, ready_for_review ] + +jobs: + docker-build-test: + name: Build & Test (Docker / Ubuntu 22.04) + if: ${{ !github.event.pull_request.draft }} + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + # BuildKit is required for the cache-from/cache-to directives below. + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Build the image and push layers into the GitHub Actions cache. + # The cache key is tied to the Dockerfile so the slow layers (Qt, SDK) + # are only rebuilt when the Dockerfile itself changes. + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: docker/ + load: true + tags: osapp2-build:latest + cache-from: type=gha,scope=osapp2-build + cache-to: type=gha,scope=osapp2-build,mode=max + + # Optional: cache Conan packages between runs to avoid re-building + # dependencies from source on every PR push. + - name: Cache Conan packages + uses: actions/cache@v4 + with: + path: ~/.conan2-ci-cache + key: conan-docker-${{ hashFiles('conanfile.py') }} + restore-keys: | + conan-docker- + + # Create named Docker volumes (idempotent; volumes persist for this job). + - name: Create cache volumes + run: make volumes + + # Inject the host-side Conan package cache into the named volume so that + # previously downloaded packages don't need to be rebuilt. + - name: Populate Conan volume from cache + run: | + if [ -d "$HOME/.conan2-ci-cache" ] && [ "$(ls -A $HOME/.conan2-ci-cache)" ]; then + docker run --rm \ + -v osapp2-conan-cache:/conan-cache \ + -v "$HOME/.conan2-ci-cache:/host-cache:ro" \ + ubuntu:22.04 \ + bash -c "cp -a /host-cache/. /conan-cache/ 2>/dev/null || true" + fi + + - name: Configure (Conan install + CMake configure) + run: make configure + + - name: Build + run: make build + + - name: Test + run: make test + + # Persist Conan packages back to the host cache directory for the next run. + - name: Save Conan packages to host cache + if: always() + run: | + mkdir -p "$HOME/.conan2-ci-cache" + docker run --rm \ + -v osapp2-conan-cache:/conan-cache:ro \ + -v "$HOME/.conan2-ci-cache:/host-cache" \ + ubuntu:22.04 \ + bash -c "cp -a /conan-cache/. /host-cache/ 2>/dev/null || true" + + - name: ccache stats + if: always() + run: | + docker run --rm \ + -v osapp2-ccache:/ccache \ + -e CCACHE_DIR=/ccache \ + osapp2-build:latest \ + ccache --show-stats || true diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..5285b787f --- /dev/null +++ b/Makefile @@ -0,0 +1,158 @@ +# ============================================================================= +# OpenStudio Application — Docker-based build system +# +# Prerequisites: Docker (with BuildKit enabled) + GNU make (or WSL/Git Bash +# on Windows). +# +# Quick start: +# make image # Build the Docker image (once, ~20 min) +# make configure # Install Conan deps + CMake configure +# make build # Compile +# make test # Run CTest +# make shell # Drop into an interactive container shell +# ============================================================================= + +IMAGE := osapp2-build +TAG := latest +BUILD_DIR := build + +# Named volumes – persist the Conan package cache and ccache between runs. +CONAN_VOL := osapp2-conan-cache +CCACHE_VOL := osapp2-ccache + +# TODO: consider making build dir a volume instead of a host mount for performance +# TODO: figure out how to launch the app from inside the volume (not sure this is possible on Windows) +# TODO: increase debug and warning verbosity of configure and build steps + +# Qt install dir inside the image (matches Dockerfile ENV). +QT_INSTALL_DIR := /opt/Qt/6.11.0/gcc_64 + +# --------------------------------------------------------------------------- +# Base docker run command (non-interactive, workspace mounted as /workspace). +# Runs as root so build artefacts in build/ have consistent ownership. +# --------------------------------------------------------------------------- +DOCKER_RUN := docker run --rm \ + -v "$(CURDIR):/workspace" \ + -v "$(CONAN_VOL):/conan-cache" \ + -v "$(CCACHE_VOL):/ccache" \ + -e CONAN_HOME=/conan-cache \ + -e CCACHE_DIR=/ccache \ + -e QT_INSTALL_DIR=$(QT_INSTALL_DIR) \ + -w /workspace \ + $(IMAGE):$(TAG) + +.PHONY: all image volumes configure build test cppcheck shell attach \ + clean image-clean volumes-clean help + +all: help + +# --------------------------------------------------------------------------- +# image — Build the Docker image (slow; only needed when Dockerfile changes). +# --------------------------------------------------------------------------- +image: + docker build -t $(IMAGE):$(TAG) docker/ + +# --------------------------------------------------------------------------- +# volumes — Ensure named cache volumes exist. +# --------------------------------------------------------------------------- +volumes: + docker volume inspect $(CONAN_VOL) > /dev/null 2>&1 || docker volume create $(CONAN_VOL) + docker volume inspect $(CCACHE_VOL) > /dev/null 2>&1 || docker volume create $(CCACHE_VOL) + +# --------------------------------------------------------------------------- +# configure — Bootstrap Conan, symlink SDK, run conan install + cmake. +# Re-run whenever conanfile.py or CMakeLists.txt changes. +# --------------------------------------------------------------------------- +configure: volumes + $(DOCKER_RUN) bash /workspace/docker/configure.sh + +# --------------------------------------------------------------------------- +# build — Compile (uses Ninja + ccache; incremental). +# --------------------------------------------------------------------------- +build: volumes + $(DOCKER_RUN) cmake --build --preset conan-release -j + +# --------------------------------------------------------------------------- +# test — Run CTest inside the build directory (xvfb-run provides a virtual +# display for headless Qt tests). +# Depends on build so an empty build/ gives a clear error message. +# --------------------------------------------------------------------------- +test: build + $(DOCKER_RUN) bash -c "cd build && xvfb-run ctest -j --output-on-failure --timeout 120" + +# --------------------------------------------------------------------------- +# cppcheck — Static analysis (matches CI cppcheck.yml flags). +# Requires build/ to exist for compile_commands.json. +# Output written to build/cppcheck-results.txt. +# --------------------------------------------------------------------------- +cppcheck: build + $(DOCKER_RUN) bash -c \ + "cppcheck \ + --std=c++20 \ + --suppress=useStlAlgorithm \ + --inline-suppr \ + --inconclusive \ + --enable=all \ + --library=qt \ + --project=build/compile_commands.json \ + 2>&1 | tee build/cppcheck-results.txt" + +# --------------------------------------------------------------------------- +# shell — Interactive bash shell inside the container (workspace mounted). +# --------------------------------------------------------------------------- +shell: volumes + docker run --rm -it \ + -v "$(CURDIR):/workspace" \ + -v "$(CONAN_VOL):/conan-cache" \ + -v "$(CCACHE_VOL):/ccache" \ + -e CONAN_HOME=/conan-cache \ + -e CCACHE_DIR=/ccache \ + -e QT_INSTALL_DIR=$(QT_INSTALL_DIR) \ + -w /workspace \ + $(IMAGE):$(TAG) bash + +# --------------------------------------------------------------------------- +# attach — /bin/sh inside the image with NO volume mounts. +# Use this to debug the image itself (inspect /opt/Qt, /opt/openstudio-sdk, etc.) +# --------------------------------------------------------------------------- +attach: + docker run --rm -it $(IMAGE):$(TAG) /bin/sh + +# --------------------------------------------------------------------------- +# clean — Remove the build directory (keeps Conan + ccache volumes). +# --------------------------------------------------------------------------- +clean: + rm -rf $(BUILD_DIR) + +# --------------------------------------------------------------------------- +# image-clean — Remove the Docker image. +# --------------------------------------------------------------------------- +image-clean: + docker rmi $(IMAGE):$(TAG) || true + +# --------------------------------------------------------------------------- +# volumes-clean — Destroy the Conan and ccache volumes (forces full rebuild). +# --------------------------------------------------------------------------- +volumes-clean: + docker volume rm $(CONAN_VOL) $(CCACHE_VOL) || true + +# --------------------------------------------------------------------------- +# help — List all targets. +# --------------------------------------------------------------------------- +help: + @echo "" + @echo "Available targets:" + @echo " image Build the Docker image (run once after Dockerfile changes)" + @echo " configure Bootstrap Conan + run cmake configure" + @echo " build Compile the project (incremental)" + @echo " test Run CTest" + @echo " cppcheck Static analysis (output -> build/cppcheck-results.txt)" + @echo " shell Interactive bash shell inside the build container" + @echo " attach /bin/sh in the image with no mounts (debug image contents)" + @echo " clean Remove build/ directory" + @echo " image-clean Remove the Docker image" + @echo " volumes-clean Destroy Conan + ccache named volumes" + @echo "" + @echo "Typical first-time workflow:" + @echo " make image && make configure && make build && make test" + @echo "" diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..829f3fb2a --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,50 @@ +FROM ubuntu:22.04 + +ARG DEBIAN_FRONTEND=noninteractive + +# ── Versions (change here when bumping) ───────────────────────────────────── +ARG QT_VERSION=6.11.0 +ARG QT_ARCH=linux_gcc_64 +# ───────────────────────────────────────────────────────────────────────────── + +# Layer 1: system packages +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential git curl wget ninja-build ccache \ + cmake python3 python3-pip python3-dev \ + mesa-common-dev libglu1-mesa-dev xvfb \ + libxkbcommon-x11-dev libgl1-mesa-dev chrpath \ + libxcb-icccm4 libxcb-keysyms1 libxcb-xkb1 libxcb-randr0 \ + libxcb-shape0 libxkbcommon-x11-0 libxcb-cursor0 \ + patchelf cppcheck ca-certificates lsb-release libglib2.0-0 \ + libfontconfig1-dev libdbus-1-dev \ + && rm -rf /var/lib/apt/lists/* + +# Layer 2: Python tools + Qt 6.11.0 +# Use the PyPI release of aqtinstall (not git master) — the git+https +# workaround in app_build.yml is only required for Windows Qt 6.11.0 due to +# a directory-structure change (github.com/miurahr/aqtinstall/issues/1007). +# Linux is unaffected, so the stable PyPI release works here. +RUN pip3 install --no-cache-dir setuptools --upgrade \ + && pip3 install --no-cache-dir 'conan>2' \ + && pip3 install --no-cache-dir aqtinstall \ + && aqt install-qt \ + --outputdir /opt/Qt \ + linux desktop ${QT_VERSION} ${QT_ARCH} \ + -m qtwebchannel qtwebengine qtwebview qtpositioning qtcharts \ + && rm -rf ~/.cache/pip + +# NOTE: The OpenStudio SDK is NOT baked into the image. +# It is downloaded once by configure.sh (make configure) into the host-side +# build/ directory, which is mounted at /workspace/build at runtime. +# FindOpenStudioSDK.cmake finds it there automatically. + +# Layer 3: Newer CMake +# Ubuntu 22.04 ships cmake 3.22, but Conan 2 generates CMakePresets.json +# version 4 which requires cmake >= 3.23. Install via pip (small layer, +# keeps the Qt layer above cached). +RUN pip3 install --no-cache-dir cmake + +ENV QT_INSTALL_DIR=/opt/Qt/${QT_VERSION}/gcc_64 +ENV PATH="/opt/Qt/${QT_VERSION}/gcc_64/bin:${PATH}" + +WORKDIR /workspace diff --git a/docker/configure.sh b/docker/configure.sh new file mode 100644 index 000000000..5b2534026 --- /dev/null +++ b/docker/configure.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# configure.sh — runs inside the build container. +# Called by: make configure +# Purpose: 1. Download the OpenStudio SDK into build/ if not already present +# 2. Bootstrap Conan home (first run only) +# 3. Run `conan install` to fetch/build dependencies and generate +# the CMake toolchain + CMakeUserPresets.json. +# 4. Run `cmake --preset conan-release` to configure the project. +set -euo pipefail + +# ── SDK paths (must match FindOpenStudioSDK.cmake) ─────────────────────────── +SDK_VERSION="3.11.0" +SDK_SHA="+241b8abb4d" +SDK_PLATFORM="Ubuntu-22.04-x86_64" # matches FindOpenStudioSDK.cmake: ${LSB_RELEASE_ID_SHORT}-${LSB_RELEASE_VERSION_SHORT}-${ARCH} +SDK_BASENAME="OpenStudio-${SDK_VERSION}${SDK_SHA}-${SDK_PLATFORM}" +SDK_DIR="build/OpenStudio-${SDK_VERSION}" # created in workspace +SDK_DEST="${SDK_DIR}/${SDK_BASENAME}" # where CMake looks +SDK_URL="https://github.com/NREL/OpenStudio/releases/download/v${SDK_VERSION}/${SDK_BASENAME}.tar.gz" + +# ── Qt ─────────────────────────────────────────────────────────────────────── +QT_INSTALL_DIR="${QT_INSTALL_DIR:-/opt/Qt/6.11.0/linux_gcc_64}" + +echo "==> [1/4] Checking OpenStudio SDK ..." +if [ -d "${SDK_DEST}" ]; then + echo " SDK already present at ${SDK_DEST}" +else + mkdir -p "${SDK_DIR}" + echo " Downloading ${SDK_URL} ..." + curl -fSL --retry 5 --retry-delay 10 --retry-connrefused "${SDK_URL}" -o "${SDK_DIR}/${SDK_BASENAME}.tar.gz" + echo " Extracting ..." + tar xzf "${SDK_DIR}/${SDK_BASENAME}.tar.gz" -C "${SDK_DIR}" + echo " SDK extracted to ${SDK_DEST}" +fi + +# ── Conan first-run bootstrap ───────────────────────────────────────────────── +echo "==> [2/4] Bootstrapping Conan ..." +CONAN_HOME="${CONAN_HOME:-${HOME}/.conan2}" +if [ ! -f "${CONAN_HOME}/profiles/default" ]; then + conan profile detect --force + # Enforce C++20 and Release build type in the default profile + sed -i 's/cppstd=.*$/cppstd=20/' "${CONAN_HOME}/profiles/default" + sed -i 's/build_type=.*$/build_type=Release/' "${CONAN_HOME}/profiles/default" + # NREL custom remote (hosts ruby/3.2.2 and other project packages) + conan remote add --force nrel-v2 \ + https://conan.openstudio.net/artifactory/api/conan/conan-v2 + echo " Conan profile created." +else + echo " Conan profile already exists." +fi + +# Always (re)write global.conf so stale values from prior runs are corrected. +mkdir -p "${CONAN_HOME}" +{ + echo "core:non_interactive = True" + echo "core.download:parallel = 4" + echo "core.sources:download_cache = ${CONAN_HOME}/.conan-download-cache" +} > "${CONAN_HOME}/global.conf" + +# ── ccache setup ────────────────────────────────────────────────────────────── +if command -v ccache &>/dev/null; then + ccache --max-size=500M + ccache --set-config=compression=true +fi + +# ── Conan install ───────────────────────────────────────────────────────────── +echo "==> [3/4] Running conan install ..." +conan install . \ + --output-folder=./build \ + --build=missing \ + -c tools.cmake.cmaketoolchain:generator=Ninja \ + -s compiler.cppstd=20 \ + -s build_type=Release + +# ── CMake configure ─────────────────────────────────────────────────────────── +echo "==> [4/4] Running cmake configure ..." +cmake --preset conan-release \ + -DQT_INSTALL_DIR:PATH="${QT_INSTALL_DIR}" \ + -DBUILD_DOCUMENTATION:BOOL=OFF \ + -DBUILD_PACKAGE:BOOL=OFF \ + -DBUILD_TESTING:BOOL=ON \ + -DBUILD_BENCHMARK:BOOL=ON + +echo "" +echo "Configure complete. Run 'make build' to compile." From c97fba5397bc4bd7eed9b8ffa7740bd78f4db3ac Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Tue, 28 Apr 2026 15:06:28 -0600 Subject: [PATCH 2/6] Add missing dependencies --- .github/workflows/docker-ci.yml | 4 ++-- docker/Dockerfile | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index 24c2364ee..ec7d18e18 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -37,8 +37,8 @@ jobs: context: docker/ load: true tags: osapp2-build:latest - cache-from: type=gha,scope=osapp2-build - cache-to: type=gha,scope=osapp2-build,mode=max + cache-from: type=gha,scope=osapp2-build-${{ hashFiles('docker/Dockerfile') }} + cache-to: type=gha,scope=osapp2-build-${{ hashFiles('docker/Dockerfile') }},mode=max # Optional: cache Conan packages between runs to avoid re-building # dependencies from source on every PR push. diff --git a/docker/Dockerfile b/docker/Dockerfile index 829f3fb2a..412f9b3fa 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -17,6 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libxcb-shape0 libxkbcommon-x11-0 libxcb-cursor0 \ patchelf cppcheck ca-certificates lsb-release libglib2.0-0 \ libfontconfig1-dev libdbus-1-dev \ + libasound2-dev libnss3-dev libnspr4-dev libxdamage-dev libxcomposite-dev libxrandr-dev libxtst-dev \ && rm -rf /var/lib/apt/lists/* # Layer 2: Python tools + Qt 6.11.0 From 6500ae74d941eb84a2c9960d6a1e0f33f0b1156e Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Sat, 2 May 2026 14:42:47 -0600 Subject: [PATCH 3/6] Fix build scripts --- .github/workflows/docker-ci.yml | 8 +- Makefile | 182 ++++++++++++++++++++++++++++---- docker/configure.sh | 102 ++++++++++++++++-- 3 files changed, 261 insertions(+), 31 deletions(-) diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index ec7d18e18..29b21e159 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -1,6 +1,6 @@ # Docker-based CI for pull requests. # -# Builds the project inside the osapp2-build Docker image (Ubuntu 22.04 with +# Builds the project inside the osapp-build Docker image (Ubuntu 22.04 with # Qt 6.11.0, OpenStudio SDK 3.11.0, and Conan 2 pre-installed) and runs # CTest. The Docker image layers are cached via the GitHub Actions cache # backend so the slow Qt/SDK bake step is only repeated when the Dockerfile @@ -36,9 +36,9 @@ jobs: with: context: docker/ load: true - tags: osapp2-build:latest - cache-from: type=gha,scope=osapp2-build-${{ hashFiles('docker/Dockerfile') }} - cache-to: type=gha,scope=osapp2-build-${{ hashFiles('docker/Dockerfile') }},mode=max + tags: osapp-build:latest + cache-from: type=gha,scope=osapp-build-${{ hashFiles('docker/Dockerfile') }} + cache-to: type=gha,scope=osapp-build-${{ hashFiles('docker/Dockerfile') }},mode=max # Optional: cache Conan packages between runs to avoid re-building # dependencies from source on every PR push. diff --git a/Makefile b/Makefile index 5285b787f..c7e3bbf82 100644 --- a/Makefile +++ b/Makefile @@ -12,16 +12,28 @@ # make shell # Drop into an interactive container shell # ============================================================================= -IMAGE := osapp2-build +# --------------------------------------------------------------------------- +# Git Bash/MSYS2: Prevent unwanted path conversion for Docker +# If running in Git Bash/MSYS2, export MSYS_NO_PATHCONV=1 to avoid issues with +# Docker volume and working directory paths. This is a no-op in other shells. +ifeq ($(shell uname -s | grep -iE 'mingw|msys|cygwin'),) +else +export MSYS_NO_PATHCONV=1 +endif + +IMAGE := osapp-build TAG := latest BUILD_DIR := build -# Named volumes – persist the Conan package cache and ccache between runs. -CONAN_VOL := osapp2-conan-cache -CCACHE_VOL := osapp2-ccache +# Named volumes – persist Conan packages, ccache, and build artefacts between runs. +# The build volume is mounted at /workspace/build inside the container, shadowing +# any host build/ directory. This gives Linux-native filesystem performance for +# incremental Ninja builds and avoids file-ownership noise on Windows hosts. +CONAN_VOL := osapp-conan-cache +CCACHE_VOL := osapp-ccache +BUILD_VOL := osapp-build -# TODO: consider making build dir a volume instead of a host mount for performance -# TODO: figure out how to launch the app from inside the volume (not sure this is possible on Windows) +# TODO: figure out how to launch the app from inside the build volume on Windows # TODO: increase debug and warning verbosity of configure and build steps # Qt install dir inside the image (matches Dockerfile ENV). @@ -29,10 +41,13 @@ QT_INSTALL_DIR := /opt/Qt/6.11.0/gcc_64 # --------------------------------------------------------------------------- # Base docker run command (non-interactive, workspace mounted as /workspace). -# Runs as root so build artefacts in build/ have consistent ownership. +# Runs as root so build artefacts have consistent ownership. +# The build volume is mounted over /workspace/build so the host never sees +# raw build output; use 'make shell' to inspect artefacts interactively. # --------------------------------------------------------------------------- DOCKER_RUN := docker run --rm \ -v "$(CURDIR):/workspace" \ + -v "$(BUILD_VOL):/workspace/build" \ -v "$(CONAN_VOL):/conan-cache" \ -v "$(CCACHE_VOL):/ccache" \ -e CONAN_HOME=/conan-cache \ @@ -41,8 +56,8 @@ DOCKER_RUN := docker run --rm \ -w /workspace \ $(IMAGE):$(TAG) -.PHONY: all image volumes configure build test cppcheck shell attach \ - clean image-clean volumes-clean help +.PHONY: all image volumes configure build test cppcheck run-app shell attach \ + clean image-clean volumes-clean build-clean help all: help @@ -53,11 +68,12 @@ image: docker build -t $(IMAGE):$(TAG) docker/ # --------------------------------------------------------------------------- -# volumes — Ensure named cache volumes exist. +# volumes — Ensure all named volumes exist. # --------------------------------------------------------------------------- volumes: docker volume inspect $(CONAN_VOL) > /dev/null 2>&1 || docker volume create $(CONAN_VOL) docker volume inspect $(CCACHE_VOL) > /dev/null 2>&1 || docker volume create $(CCACHE_VOL) + docker volume inspect $(BUILD_VOL) > /dev/null 2>&1 || docker volume create $(BUILD_VOL) # --------------------------------------------------------------------------- # configure — Bootstrap Conan, symlink SDK, run conan install + cmake. @@ -73,12 +89,36 @@ build: volumes $(DOCKER_RUN) cmake --build --preset conan-release -j # --------------------------------------------------------------------------- -# test — Run CTest inside the build directory (xvfb-run provides a virtual -# display for headless Qt tests). -# Depends on build so an empty build/ gives a clear error message. +# test — Run CTest inside the build directory. +# Xvfb is started manually so all parallel test processes share the +# same display (xvfb-run only wraps a single process, which is +# insufficient when ctest spawns many children in parallel). +# +# Per-test timeout notes: +# All tests have TIMEOUT 660 set in CTestTestfile.cmake. +# ctest --timeout only sets the *default* for tests that have no +# timeout property, so it does not override 660. +# cmake 3.27+ ctest --test-timeout DOES override per-test timeouts. +# We use --test-timeout 120 so no single test can block the suite. +# +# Excluded tests: +# GithubRelease* — make live HTTP calls to the GitHub releases API +# which hang or fail in a network-sandboxed container. # --------------------------------------------------------------------------- test: build - $(DOCKER_RUN) bash -c "cd build && xvfb-run ctest -j --output-on-failure --timeout 120" + $(DOCKER_RUN) bash -c "\ + Xvfb :99 -screen 0 1280x1024x24 -ac &\ + XVFB_PID=$$! ;\ + export DISPLAY=:99 ;\ + export QT_QPA_PLATFORM=xcb ;\ + sleep 1 ;\ + cd build && ctest -j4 \ + --output-on-failure \ + --test-timeout 120 \ + --exclude-regex 'GithubRelease' \ + ; CTEST_EXIT=$$? ;\ + kill $$XVFB_PID 2>/dev/null || true ;\ + exit $$CTEST_EXIT" # --------------------------------------------------------------------------- # cppcheck — Static analysis (matches CI cppcheck.yml flags). @@ -98,11 +138,95 @@ cppcheck: build 2>&1 | tee build/cppcheck-results.txt" # --------------------------------------------------------------------------- -# shell — Interactive bash shell inside the container (workspace mounted). +# run-app — Launch the compiled OpenStudioApp with GUI forwarded to the host. +# +# Automatically detects the host platform: +# +# WSL2/WSLg (Windows 11): +# Uses WSLg's X11 socket (/tmp/.X11-unix) and Wayland runtime +# (/mnt/wslg). No extra software needed. +# Override display: make run-app DISPLAY=:1 +# +# Linux (native): +# Mounts /tmp/.X11-unix and uses the host DISPLAY. Runs xhost +local:docker +# first to grant the container access. +# Override display: make run-app DISPLAY=:1 +# +# macOS: +# Requires XQuartz (https://xquartz.org). Start XQuartz and run: +# xhost + 127.0.0.1 +# Then: make run-app +# DISPLAY is set to host.docker.internal:0 automatically. +# +# If on native Linux and GPU passthrough is unavailable (--device /dev/dri +# fails), add: make run-app LIBGL_ALWAYS_SOFTWARE=1 +# --------------------------------------------------------------------------- +APP_BIN := /workspace/build/Products/OpenStudioApp + +# Detect host OS. 'uname -s' is available on Linux, macOS, and WSL. +# On native Windows (no WSL) this will be empty — unsupported directly. +_UNAME := $(shell uname -s 2>/dev/null) + +# Detect WSL2: /mnt/wslg exists only inside the WSL2 VM. +_IS_WSL := $(shell test -d /mnt/wslg && echo 1 || echo 0) + +ifeq ($(_UNAME),Darwin) + # macOS: XQuartz listens on host.docker.internal:0 + DISPLAY ?= host.docker.internal:0 + _X11_MOUNTS := + _WSLG_MOUNTS := + _DRI_DEVICE := + _DISPLAY_ENV := -e DISPLAY=$(DISPLAY) +else ifeq ($(_IS_WSL),1) + # WSL2 + WSLg: GPU is handled by the WSLg Wayland compositor via /dev/dxg; + # no --device /dev/dri needed (that device doesn't exist in the WSL2 VM). + DISPLAY ?= :0 + _X11_MOUNTS := -v /tmp/.X11-unix:/tmp/.X11-unix + _WSLG_MOUNTS := -v /mnt/wslg:/mnt/wslg \ + -e WAYLAND_DISPLAY=wayland-0 \ + -e XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir \ + -e PULSE_SERVER=/mnt/wslg/PulseServer + _DRI_DEVICE := + _DISPLAY_ENV := -e DISPLAY=$(DISPLAY) +else + # Native Linux + DISPLAY ?= $(shell echo $$DISPLAY) + _X11_MOUNTS := -v /tmp/.X11-unix:/tmp/.X11-unix + _WSLG_MOUNTS := + _DRI_DEVICE := --device /dev/dri + _DISPLAY_ENV := -e DISPLAY=$(DISPLAY) +endif + +run-app: volumes +ifeq ($(_UNAME),Linux) +ifneq ($(_IS_WSL),1) + xhost +local:docker 2>/dev/null || true +endif +endif + docker run --rm -it \ + -v "$(CURDIR):/workspace" \ + -v "$(BUILD_VOL):/workspace/build" \ + -v "$(CONAN_VOL):/conan-cache" \ + -v "$(CCACHE_VOL):/ccache" \ + $(_X11_MOUNTS) \ + $(_WSLG_MOUNTS) \ + $(_DISPLAY_ENV) \ + $(_DRI_DEVICE) \ + -e CONAN_HOME=/conan-cache \ + -e CCACHE_DIR=/ccache \ + -e QT_INSTALL_DIR=$(QT_INSTALL_DIR) \ + -e QT_QPA_PLATFORM=xcb \ + -w /workspace \ + $(IMAGE):$(TAG) \ + $(APP_BIN) + +# --------------------------------------------------------------------------- +# shell — Interactive bash shell inside the container (all volumes mounted). # --------------------------------------------------------------------------- shell: volumes docker run --rm -it \ -v "$(CURDIR):/workspace" \ + -v "$(BUILD_VOL):/workspace/build" \ -v "$(CONAN_VOL):/conan-cache" \ -v "$(CCACHE_VOL):/ccache" \ -e CONAN_HOME=/conan-cache \ @@ -119,9 +243,18 @@ attach: docker run --rm -it $(IMAGE):$(TAG) /bin/sh # --------------------------------------------------------------------------- -# clean — Remove the build directory (keeps Conan + ccache volumes). +# clean — Wipe the build volume (keeps Conan, ccache, and source). +# Equivalent to the old 'rm -rf build/'. +# Also removes any stale host-side build/ directory if present. +# --------------------------------------------------------------------------- +clean: build-clean + # --------------------------------------------------------------------------- -clean: +# build-clean — Destroy and recreate the build volume (empty slate). +# --------------------------------------------------------------------------- +build-clean: + docker volume rm $(BUILD_VOL) || true + docker volume create $(BUILD_VOL) rm -rf $(BUILD_DIR) # --------------------------------------------------------------------------- @@ -131,10 +264,11 @@ image-clean: docker rmi $(IMAGE):$(TAG) || true # --------------------------------------------------------------------------- -# volumes-clean — Destroy the Conan and ccache volumes (forces full rebuild). +# volumes-clean — Destroy all named volumes (forces complete rebuild). # --------------------------------------------------------------------------- volumes-clean: - docker volume rm $(CONAN_VOL) $(CCACHE_VOL) || true + docker volume rm $(BUILD_VOL) $(CONAN_VOL) $(CCACHE_VOL) || true + rm -rf $(BUILD_DIR) # --------------------------------------------------------------------------- # help — List all targets. @@ -147,12 +281,18 @@ help: @echo " build Compile the project (incremental)" @echo " test Run CTest" @echo " cppcheck Static analysis (output -> build/cppcheck-results.txt)" + @echo " run-app Launch OpenStudioApp GUI (WSLg/Linux/macOS+XQuartz)" @echo " shell Interactive bash shell inside the build container" @echo " attach /bin/sh in the image with no mounts (debug image contents)" - @echo " clean Remove build/ directory" + @echo " clean Wipe the build volume (equivalent to rm -rf build/)" + @echo " build-clean Alias for clean" @echo " image-clean Remove the Docker image" - @echo " volumes-clean Destroy Conan + ccache named volumes" + @echo " volumes-clean Destroy all named volumes (build + Conan + ccache)" @echo "" @echo "Typical first-time workflow:" @echo " make image && make configure && make build && make test" @echo "" + @echo "Note: build artefacts live in the '$(BUILD_VOL)' Docker volume." + @echo " Use 'make shell' to inspect them interactively." + @echo " 'make clean' wipes the build volume; Conan/ccache are preserved." + @echo "" diff --git a/docker/configure.sh b/docker/configure.sh index 5b2534026..89cf00a3d 100644 --- a/docker/configure.sh +++ b/docker/configure.sh @@ -8,6 +8,32 @@ # 4. Run `cmake --preset conan-release` to configure the project. set -euo pipefail +# Tee all output to a log file inside the build volume (osapp-build), not the +# host filesystem. Ensures the full log survives PowerShell Tee-Object SIGPIPE. +mkdir -p /workspace/build +exec > >(tee -a /workspace/build/configure-docker.log) 2>&1 + +# ── Verbose debug header ────────────────────────────────────────────────────── +echo "================================================================" +echo " configure.sh — $(date -u '+%Y-%m-%d %H:%M:%S UTC')" +echo " Host: $(uname -a)" +echo " User: $(id)" +echo " Workdir: $(pwd)" +echo " CONAN_HOME: ${CONAN_HOME:-}" +echo " CCACHE_DIR: ${CCACHE_DIR:-}" +echo " QT_INSTALL_DIR: ${QT_INSTALL_DIR:-}" +echo " PATH: ${PATH}" +echo "================================================================" + +echo "--- Tool versions ---" +echo " bash: $(bash --version | head -1)" +echo " cmake: $(cmake --version | head -1)" +echo " conan: $(conan --version 2>&1 | head -1)" +echo " ninja: $(ninja --version 2>&1 || echo 'not found')" +echo " ccache: $(ccache --version 2>&1 | head -1 || echo 'not found')" +echo " curl: $(curl --version | head -1)" +echo "---------------------" + # ── SDK paths (must match FindOpenStudioSDK.cmake) ─────────────────────────── SDK_VERSION="3.11.0" SDK_SHA="+241b8abb4d" @@ -17,68 +43,132 @@ SDK_DIR="build/OpenStudio-${SDK_VERSION}" # created in workspace SDK_DEST="${SDK_DIR}/${SDK_BASENAME}" # where CMake looks SDK_URL="https://github.com/NREL/OpenStudio/releases/download/v${SDK_VERSION}/${SDK_BASENAME}.tar.gz" +echo " SDK_DEST: ${SDK_DEST}" +echo " SDK_URL: ${SDK_URL}" + # ── Qt ─────────────────────────────────────────────────────────────────────── QT_INSTALL_DIR="${QT_INSTALL_DIR:-/opt/Qt/6.11.0/linux_gcc_64}" +echo " Qt: ${QT_INSTALL_DIR}" +if [ -d "${QT_INSTALL_DIR}" ]; then + echo " Qt dir exists: OK" +else + echo " WARNING: Qt dir not found at ${QT_INSTALL_DIR}" +fi echo "==> [1/4] Checking OpenStudio SDK ..." if [ -d "${SDK_DEST}" ]; then echo " SDK already present at ${SDK_DEST}" + echo " SDK contents (top-level):" + ls -lh "${SDK_DEST}" | head -20 else + echo " SDK not found — downloading ..." mkdir -p "${SDK_DIR}" echo " Downloading ${SDK_URL} ..." - curl -fSL --retry 5 --retry-delay 10 --retry-connrefused "${SDK_URL}" -o "${SDK_DIR}/${SDK_BASENAME}.tar.gz" + curl -fSL --retry 5 --retry-delay 10 --retry-connrefused \ + --progress-bar \ + "${SDK_URL}" -o "${SDK_DIR}/${SDK_BASENAME}.tar.gz" + echo " Download complete. Archive size: $(du -sh "${SDK_DIR}/${SDK_BASENAME}.tar.gz" | cut -f1)" echo " Extracting ..." - tar xzf "${SDK_DIR}/${SDK_BASENAME}.tar.gz" -C "${SDK_DIR}" + tar xzf "${SDK_DIR}/${SDK_BASENAME}.tar.gz" -C "${SDK_DIR}" --verbose 2>&1 | tail -5 echo " SDK extracted to ${SDK_DEST}" fi # ── Conan first-run bootstrap ───────────────────────────────────────────────── echo "==> [2/4] Bootstrapping Conan ..." CONAN_HOME="${CONAN_HOME:-${HOME}/.conan2}" +echo " CONAN_HOME resolved to: ${CONAN_HOME}" if [ ! -f "${CONAN_HOME}/profiles/default" ]; then + echo " No default profile found — running 'conan profile detect' ..." conan profile detect --force # Enforce C++20 and Release build type in the default profile sed -i 's/cppstd=.*$/cppstd=20/' "${CONAN_HOME}/profiles/default" sed -i 's/build_type=.*$/build_type=Release/' "${CONAN_HOME}/profiles/default" - # NREL custom remote (hosts ruby/3.2.2 and other project packages) - conan remote add --force nrel-v2 \ + echo " Profile after edits:" + cat "${CONAN_HOME}/profiles/default" + # NREL custom remote (hosts ruby/3.2.2 and other project packages). + # --insecure disables TLS certificate verification — needed while + # conan.openstudio.net has an expired certificate. Remove --insecure once + # the certificate is renewed. + conan remote add --force --insecure nrel-v2 \ https://conan.openstudio.net/artifactory/api/conan/conan-v2 echo " Conan profile created." else - echo " Conan profile already exists." + echo " Conan profile already exists:" + cat "${CONAN_HOME}/profiles/default" fi +# ── Ensure nrel-v2 remote is registered and insecure ───────────────────────── +# conan.openstudio.net currently has an expired TLS certificate; --insecure +# disables cert verification so the build is not blocked. Remove the +# --insecure flag (and this comment) once the certificate is renewed. +echo " Checking for nrel-v2 remote ..." +if conan remote list 2>/dev/null | grep -q 'nrel-v2'; then + echo " nrel-v2 remote found — ensuring it is enabled and insecure ..." + conan remote enable nrel-v2 + conan remote update nrel-v2 \ + --url https://conan.openstudio.net/artifactory/api/conan/conan-v2 \ + --insecure +else + echo " nrel-v2 remote not registered — adding with --insecure ..." + conan remote add --insecure nrel-v2 \ + https://conan.openstudio.net/artifactory/api/conan/conan-v2 +fi +echo " Active Conan remotes:" +conan remote list + # Always (re)write global.conf so stale values from prior runs are corrected. mkdir -p "${CONAN_HOME}" +echo " Writing ${CONAN_HOME}/global.conf ..." { echo "core:non_interactive = True" echo "core.download:parallel = 4" echo "core.sources:download_cache = ${CONAN_HOME}/.conan-download-cache" } > "${CONAN_HOME}/global.conf" +echo " global.conf written:" +cat "${CONAN_HOME}/global.conf" # ── ccache setup ────────────────────────────────────────────────────────────── if command -v ccache &>/dev/null; then + echo " ccache found — configuring ..." ccache --max-size=500M ccache --set-config=compression=true + echo " ccache stats before build:" + ccache --show-stats +else + echo " WARNING: ccache not found — builds will not be cached" fi # ── Conan install ───────────────────────────────────────────────────────────── echo "==> [3/4] Running conan install ..." +echo " conanfile.py: $(head -5 conanfile.py 2>/dev/null || echo 'not found')" conan install . \ --output-folder=./build \ --build=missing \ -c tools.cmake.cmaketoolchain:generator=Ninja \ -s compiler.cppstd=20 \ -s build_type=Release +echo " conan install exit code: $?" +echo " build/ contents after conan install:" +ls -lh build/ | grep -v "^total" | head -30 # ── CMake configure ─────────────────────────────────────────────────────────── echo "==> [4/4] Running cmake configure ..." +echo " Preset: conan-release" +echo " CMakeUserPresets.json:" +cat CMakeUserPresets.json 2>/dev/null || echo " (not found)" cmake --preset conan-release \ -DQT_INSTALL_DIR:PATH="${QT_INSTALL_DIR}" \ -DBUILD_DOCUMENTATION:BOOL=OFF \ -DBUILD_PACKAGE:BOOL=OFF \ -DBUILD_TESTING:BOOL=ON \ - -DBUILD_BENCHMARK:BOOL=ON + -DBUILD_BENCHMARK:BOOL=ON \ + --log-level=STATUS +echo " cmake configure exit code: $?" +echo " CMakeCache.txt key values:" +grep -E "^(CMAKE_BUILD_TYPE|CMAKE_CXX_COMPILER|CMAKE_MAKE_PROGRAM|QT_INSTALL_DIR|BUILD_TESTING|BUILD_BENCHMARK)" \ + build/CMakeCache.txt 2>/dev/null | sort || echo " (CMakeCache.txt not found)" echo "" +echo "================================================================" echo "Configure complete. Run 'make build' to compile." +echo "================================================================" From 52f3eb6d607385302325a2b2c0fa44f87dac6fc2 Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Sat, 2 May 2026 14:49:13 -0600 Subject: [PATCH 4/6] Remove todos --- Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile b/Makefile index c7e3bbf82..6569e0278 100644 --- a/Makefile +++ b/Makefile @@ -33,9 +33,6 @@ CONAN_VOL := osapp-conan-cache CCACHE_VOL := osapp-ccache BUILD_VOL := osapp-build -# TODO: figure out how to launch the app from inside the build volume on Windows -# TODO: increase debug and warning verbosity of configure and build steps - # Qt install dir inside the image (matches Dockerfile ENV). QT_INSTALL_DIR := /opt/Qt/6.11.0/gcc_64 From 7f4dda56e728081671b0a8f53b4745936e5f2c6d Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Sat, 2 May 2026 17:01:13 -0600 Subject: [PATCH 5/6] Try to fix ci, improve documentation --- .../instructions/docker-build.instructions.md | 108 +++++++++++++++--- .github/workflows/docker-ci.yml | 8 +- CMakeLists.txt | 2 +- Makefile | 20 ++-- docker/configure.sh | 3 - 5 files changed, 104 insertions(+), 37 deletions(-) diff --git a/.github/instructions/docker-build.instructions.md b/.github/instructions/docker-build.instructions.md index 81bfb0fc5..f32195ac3 100644 --- a/.github/instructions/docker-build.instructions.md +++ b/.github/instructions/docker-build.instructions.md @@ -5,9 +5,10 @@ applyTo: "src/**/*.cpp,src/**/*.hpp,src/**/CMakeLists.txt,docker/**,Makefile" # Building and Testing with the Docker Build Container -All compilation and testing happens inside the Docker container. Never run -`cmake`, `conan`, `ninja`, or `ctest` directly on the host. Use `make` -targets from the project root instead. +All compilation and testing happens inside the Docker container. +Use `make` targets from the project root to build, test, and validate changes. The Makefile handles all Docker invocation. + +Real builds are still made on native host machines directly. The docker builds are used for testing and validation, not for production releases. The Docker image is a convenient, consistent environment for all developers to run builds and tests without needing to set up Qt, Conan, or the OpenStudio SDK on their local machines. --- @@ -58,11 +59,59 @@ make test | `make image` | Rebuild the Docker image (Dockerfile changed) | | `make configure` | After checkout, after `conanfile.py`/`CMakeLists.txt` changes, or after `make clean` | | `make build` | After any source file edit | -| `make test` | Validate correctness; runs `xvfb-run ctest -j` for headless Qt tests | +| `make test` | Validate correctness; starts Xvfb at `:99`, runs `ctest -j4 --test-timeout 120 --exclude-regex GithubRelease` | | `make cppcheck` | Static analysis; output written to `build/cppcheck-results.txt` | -| `make shell` | Interactive bash shell inside the container for debugging | -| `make clean` | Delete `build/` (keeps Conan + ccache volumes) | -| `make volumes-clean` | Destroy Conan and ccache named volumes (forces full dependency rebuild) | +| `make run-app` | Launch the compiled OpenStudioApp GUI (requires WSLg/Linux/macOS+XQuartz) | +| `make check-build` | Bash shell with all volumes mounted — inspect build artifacts, run commands against your actual source and build output | +| `make attach` | `/bin/sh` with **no volumes mounted** — inspect the image itself (e.g. verify Qt at `/opt/Qt`, check `/opt/openstudio-sdk`) | +| `make clean` | Wipe the build volume (keeps Conan + ccache volumes); alias for `build-clean` | +| `make build-clean` | Destroy and recreate the build volume (empty slate) | +| `make image-clean` | Remove the Docker image | +| `make volumes-clean` | Destroy all named volumes — build, Conan, and ccache (forces complete rebuild) | + +--- + +## Running make commands on Windows + +All `make` targets require a Linux-compatible shell because they invoke Docker +with bash process substitution and Unix-style paths. On Windows, always run +`make` commands inside **WSL** (Windows Subsystem for Linux), not in +PowerShell or Command Prompt. + +When the user is on Windows and asks you to run a `make` target, use a WSL +shell. For example: + +```powershell +wsl make configure +wsl make build +wsl make test +``` + +Or open a persistent WSL session and run the commands there. Never run `make` targets directly in a PowerShell or `cmd.exe` terminal on Windows. + +`make build` and `make test` can run for many minutes. Follow these rules to avoid getting stuck not knowing whether a command finished. + +**Always capture the exit code explicitly** — append `; echo "EXIT: $?"` so the final line of output is unambiguous even when stdout is truncated: + +```bash +wsl bash -c 'cd /mnt/c/repos/osapp3 && make build; echo "EXIT: $?"' +``` + +**Never pipe Docker commands** — piping (e.g. `| tail -50`, `| grep`) causes SIGPIPE (exit 141) when the consumer exits before Docker finishes. Redirect stderr into stdout with `2>&1` instead: + +```bash +wsl bash -c 'cd /mnt/c/repos/osapp3 && make build 2>&1; echo "EXIT: $?"' +``` + +**Completion markers to look for in output:** + +| Command | Success indicator | +|---------|-------------------| +| `make configure` | `Install finished successfully` + `conan install exit code: 0` | +| `make build` | `cmake --build` exits and `EXIT: 0` is printed | +| `make test` | `100% tests passed` or `EXIT: 0` printed after ctest | + +**If output is truncated** and the completion marker is not visible, call `get_terminal_output` again — do not assume the command is still running until at least 5 minutes have elapsed without new output. --- @@ -70,18 +119,38 @@ make test After every logical change (e.g. one item from `developer/doc/refactoring-ideas.md`): -1. `make build` — must exit 0 with no new warnings. -2. `make test` — all previously passing tests must still pass. -3. If a header or CMakeLists.txt was modified: `make configure && make build && make test`. +1. **Determine which targets to run** based on what changed: + - Source files only (`.cpp`/`.hpp`) → `make build && make test` + - Added or removed source files from a `CMakeLists.txt` → `make configure && make build && make test` + - Changed `conanfile.py`, `CMakeLists.txt` contents, or any `*.cmake` file → `make configure && make build && make test` + +2. **`make build` must exit 0 with zero warnings.** The project builds with `-Werror`; any compiler warning is a build failure. Fix all warnings before proceeding. + +3. **`make test` must exit 0 with no regressions.** All tests that passed before the change must still pass. `GithubRelease*` tests are excluded by the Makefile and are not part of the baseline. If `make test` fails, read `build/Testing/Temporary/LastTest.log` to identify which test failed and why before attempting a fix. + +4. **Optionally run `make cppcheck`** after any non-trivial structural change (new class, moved logic, changed ownership patterns). Review `build/cppcheck-results.txt` for new issues introduced by the change. -Do not proceed to the next refactoring step until both commands succeed. +Do not proceed to the next refactoring step until steps 2 and 3 succeed. + +--- + +## Named Docker volumes + +Three named volumes persist data between runs: + +| Volume | Mounted at | Contents | +|--------|-----------|----------| +| `osapp-build` | `/workspace/build` | CMake build tree, compiled objects, Ninja database | +| `osapp-conan-cache` | `/conan-cache` | Downloaded Conan packages (`CONAN_HOME`) | +| `osapp-ccache` | `/ccache` | ccache object cache (`CCACHE_DIR`) | + +The build volume shadows the host `build/` directory — build output never lands on the host filesystem directly. Use `make check-build` to inspect volume contents interactively. `make clean` / `make build-clean` wipes only `osapp-build`; Conan and ccache volumes are preserved. `make volumes-clean` destroys all three. --- ## Reading build output -Build artefacts and logs land in `build/` (git-ignored). Key paths inside -the container (mapped to the same path on the host): +Build artifacts and logs land in `build/` (git-ignored). Key paths inside the container (mapped to the same path on the host): | Path | Contents | |------|----------| @@ -97,11 +166,12 @@ the container (mapped to the same path on the host): Run `make volumes-clean` then `make configure` again to rebuild the Conan cache from scratch. **`make image` fails to pull `ubuntu:22.04`** -Docker Desktop cannot reach `docker.io`. Restart Docker Desktop or check -proxy/VPN settings. Run `docker pull ubuntu:22.04` to confirm connectivity -before retrying. +Docker Desktop cannot reach `docker.io`. Restart Docker Desktop or check proxy/VPN settings. Run `docker pull ubuntu:22.04` to confirm connectivity before retrying. **Tests fail due to a missing display** -The container includes `xvfb`; `make test` already wraps ctest with -`xvfb-run`. If running commands manually inside `make shell`, prefix with -`xvfb-run` for any test that opens a Qt window. +The container includes `xvfb`; `make test` starts a dedicated Xvfb process at `DISPLAY=:99` before invoking ctest, then kills it afterward. If running ctest manually inside `make check-build`, start Xvfb first: +```bash +Xvfb :99 -screen 0 1280x1024x24 -ac & +export DISPLAY=:99 +cd build && ctest -j4 --output-on-failure --test-timeout 120 --exclude-regex GithubRelease +``` diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index 29b21e159..cfecbc573 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -60,7 +60,7 @@ jobs: run: | if [ -d "$HOME/.conan2-ci-cache" ] && [ "$(ls -A $HOME/.conan2-ci-cache)" ]; then docker run --rm \ - -v osapp2-conan-cache:/conan-cache \ + -v osapp-conan-cache:/conan-cache \ -v "$HOME/.conan2-ci-cache:/host-cache:ro" \ ubuntu:22.04 \ bash -c "cp -a /host-cache/. /conan-cache/ 2>/dev/null || true" @@ -81,7 +81,7 @@ jobs: run: | mkdir -p "$HOME/.conan2-ci-cache" docker run --rm \ - -v osapp2-conan-cache:/conan-cache:ro \ + -v osapp-conan-cache:/conan-cache:ro \ -v "$HOME/.conan2-ci-cache:/host-cache" \ ubuntu:22.04 \ bash -c "cp -a /conan-cache/. /host-cache/ 2>/dev/null || true" @@ -90,7 +90,7 @@ jobs: if: always() run: | docker run --rm \ - -v osapp2-ccache:/ccache \ + -v osapp-ccache:/ccache \ -e CCACHE_DIR=/ccache \ - osapp2-build:latest \ + osapp-build:latest \ ccache --show-stats || true diff --git a/CMakeLists.txt b/CMakeLists.txt index d34f5b643..d536578e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -384,7 +384,7 @@ if(UNIX) #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -fno-strict-aliasing -Winvalid-pch -Wnon-virtual-dtor") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fno-strict-aliasing -Winvalid-pch") # Treat all warnings as errors - #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") if(APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-overloaded-virtual -ftemplate-depth=1024") diff --git a/Makefile b/Makefile index 6569e0278..feb0da1ca 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ # make configure # Install Conan deps + CMake configure # make build # Compile # make test # Run CTest -# make shell # Drop into an interactive container shell +# make check-build # Drop into an interactive container shell # ============================================================================= # --------------------------------------------------------------------------- @@ -25,7 +25,7 @@ IMAGE := osapp-build TAG := latest BUILD_DIR := build -# Named volumes – persist Conan packages, ccache, and build artefacts between runs. +# Named volumes – persist Conan packages, ccache, and build artifacts between runs. # The build volume is mounted at /workspace/build inside the container, shadowing # any host build/ directory. This gives Linux-native filesystem performance for # incremental Ninja builds and avoids file-ownership noise on Windows hosts. @@ -38,9 +38,9 @@ QT_INSTALL_DIR := /opt/Qt/6.11.0/gcc_64 # --------------------------------------------------------------------------- # Base docker run command (non-interactive, workspace mounted as /workspace). -# Runs as root so build artefacts have consistent ownership. +# Runs as root so build artifacts have consistent ownership. # The build volume is mounted over /workspace/build so the host never sees -# raw build output; use 'make shell' to inspect artefacts interactively. +# raw build output; use 'make check-build' to inspect artifacts interactively. # --------------------------------------------------------------------------- DOCKER_RUN := docker run --rm \ -v "$(CURDIR):/workspace" \ @@ -53,7 +53,7 @@ DOCKER_RUN := docker run --rm \ -w /workspace \ $(IMAGE):$(TAG) -.PHONY: all image volumes configure build test cppcheck run-app shell attach \ +.PHONY: all image volumes configure build test cppcheck run-app check-build attach \ clean image-clean volumes-clean build-clean help all: help @@ -218,9 +218,9 @@ endif $(APP_BIN) # --------------------------------------------------------------------------- -# shell — Interactive bash shell inside the container (all volumes mounted). +# check-build — Interactive bash shell inside the container (all volumes mounted). # --------------------------------------------------------------------------- -shell: volumes +check-build: volumes docker run --rm -it \ -v "$(CURDIR):/workspace" \ -v "$(BUILD_VOL):/workspace/build" \ @@ -279,7 +279,7 @@ help: @echo " test Run CTest" @echo " cppcheck Static analysis (output -> build/cppcheck-results.txt)" @echo " run-app Launch OpenStudioApp GUI (WSLg/Linux/macOS+XQuartz)" - @echo " shell Interactive bash shell inside the build container" + @echo " check-build Interactive bash shell inside the build container" @echo " attach /bin/sh in the image with no mounts (debug image contents)" @echo " clean Wipe the build volume (equivalent to rm -rf build/)" @echo " build-clean Alias for clean" @@ -289,7 +289,7 @@ help: @echo "Typical first-time workflow:" @echo " make image && make configure && make build && make test" @echo "" - @echo "Note: build artefacts live in the '$(BUILD_VOL)' Docker volume." - @echo " Use 'make shell' to inspect them interactively." + @echo "Note: build artifacts live in the '$(BUILD_VOL)' Docker volume." + @echo " Use 'make check-build' to inspect them interactively." @echo " 'make clean' wipes the build volume; Conan/ccache are preserved." @echo "" diff --git a/docker/configure.sh b/docker/configure.sh index 89cf00a3d..ce14b19e6 100644 --- a/docker/configure.sh +++ b/docker/configure.sh @@ -8,10 +8,7 @@ # 4. Run `cmake --preset conan-release` to configure the project. set -euo pipefail -# Tee all output to a log file inside the build volume (osapp-build), not the -# host filesystem. Ensures the full log survives PowerShell Tee-Object SIGPIPE. mkdir -p /workspace/build -exec > >(tee -a /workspace/build/configure-docker.log) 2>&1 # ── Verbose debug header ────────────────────────────────────────────────────── echo "================================================================" From f46770248abd86a74314da48d6b8bb303b065a0e Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Sat, 2 May 2026 20:05:30 -0600 Subject: [PATCH 6/6] Fix CI --- .github/instructions/docker-build.instructions.md | 9 ++++++++- docker/configure.sh | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/instructions/docker-build.instructions.md b/.github/instructions/docker-build.instructions.md index f32195ac3..e10bf5857 100644 --- a/.github/instructions/docker-build.instructions.md +++ b/.github/instructions/docker-build.instructions.md @@ -126,7 +126,14 @@ After every logical change (e.g. one item from `developer/doc/refactoring-ideas. 2. **`make build` must exit 0 with zero warnings.** The project builds with `-Werror`; any compiler warning is a build failure. Fix all warnings before proceeding. -3. **`make test` must exit 0 with no regressions.** All tests that passed before the change must still pass. `GithubRelease*` tests are excluded by the Makefile and are not part of the baseline. If `make test` fails, read `build/Testing/Temporary/LastTest.log` to identify which test failed and why before attempting a fix. +3. **`make test` must exit 0 with no regressions.** All tests that passed before the change must still pass. The following tests are known baseline failures in the Docker environment and are **not** regressions: + + | Test | Reason | + |------|--------| + | `ModelEditorFixture.MorePath_Conversions` | Tests Windows-style backslash path conversion; always fails on Linux | + | `OpenStudioLibFixture.AnalyticsHelperSecrets` | Requires analytics API secrets injected by CI; always empty in local builds | + + `GithubRelease*` tests are excluded entirely by the Makefile (`--exclude-regex GithubRelease`) and are not part of the baseline. If `make test` fails, read `build/Testing/Temporary/LastTest.log` to identify which test failed and why before attempting a fix. 4. **Optionally run `make cppcheck`** after any non-trivial structural change (new class, moved logic, changed ownership patterns). Review `build/cppcheck-results.txt` for new issues introduced by the change. diff --git a/docker/configure.sh b/docker/configure.sh index ce14b19e6..581df16f1 100644 --- a/docker/configure.sh +++ b/docker/configure.sh @@ -56,7 +56,7 @@ echo "==> [1/4] Checking OpenStudio SDK ..." if [ -d "${SDK_DEST}" ]; then echo " SDK already present at ${SDK_DEST}" echo " SDK contents (top-level):" - ls -lh "${SDK_DEST}" | head -20 + ls -lah "${SDK_DEST}" || true else echo " SDK not found — downloading ..." mkdir -p "${SDK_DIR}" @@ -146,7 +146,7 @@ conan install . \ -s build_type=Release echo " conan install exit code: $?" echo " build/ contents after conan install:" -ls -lh build/ | grep -v "^total" | head -30 +ls -lah build/ | grep -v "^total" || true # ── CMake configure ─────────────────────────────────────────────────────────── echo "==> [4/4] Running cmake configure ..."