From 9403c8f973fc044e67947cbcd4c251761ab84b08 Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Tue, 28 Apr 2026 11:55:18 -0600 Subject: [PATCH 1/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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 ..." From 487840696ed7d6fe3abbf68c48ba4255074de1c4 Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Sun, 24 May 2026 22:41:39 -0600 Subject: [PATCH 7/9] Improve instructions --- .../instructions/docker-build.instructions.md | 186 +++++++++++++++--- .github/workflows/docker-ci.yml | 54 +---- Makefile | 98 ++++++--- docker/Dockerfile | 6 +- docker/configure.sh | 77 ++++++-- 5 files changed, 289 insertions(+), 132 deletions(-) diff --git a/.github/instructions/docker-build.instructions.md b/.github/instructions/docker-build.instructions.md index e10bf5857..e17efe7fc 100644 --- a/.github/instructions/docker-build.instructions.md +++ b/.github/instructions/docker-build.instructions.md @@ -5,10 +5,9 @@ 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. -Use `make` targets from the project root to build, test, and validate changes. The Makefile handles all Docker invocation. +Production release builds are made on native CI machines. The Docker container is used for local development validation — it provides a consistent environment without needing to install Qt, Conan, or the OpenStudio SDK locally. -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. +When using the Docker container builds, use `make` targets from the project root to build, test, and validate changes. The Makefile handles all further Docker invocations. --- @@ -62,12 +61,52 @@ make test | `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 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 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) | +| `make volumes-clean` | Destroy all named volumes - build, Conan, and ccache (forces complete rebuild) | + +--- + +## Agent Instructions + +The sections below contain behavioral rules for the agent. Follow "Platform-specific command execution" to choose the right syntax, then refer to "Common build validation rules" and "Validating a refactoring step" for testing requirements. All remaining sections are reference documentation. + +--- + +## Platform-specific command execution + +**Always determine the user's OS before generating `make` commands.** Look for these indicators: + +| OS | Indicators | +|----|-----------| +| **Windows** | PowerShell prompts, Windows paths like `C:\`, explicit mention of Windows | +| **Linux** | Unix paths like `/home/`, bash prompts, explicit mention of Linux/Ubuntu/Debian/Fedora | +| **macOS** | Unix paths like `/Users/`, explicit mention of macOS/Mac | + +**If the OS is unknown**, ask the user before generating commands. + +--- + +## Common build validation rules + +`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** by appending `; echo "EXIT: $?"` (bash/Linux/macOS) or ``; echo 'EXIT: '`$?`` (PowerShell/Windows) so the final line of output is unambiguous even when stdout is truncated. + +**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. + +**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 immediately. Continue polling every 30 seconds. Only conclude the command is still running (and stop polling) if no new output has appeared for 5 consecutive minutes. --- @@ -78,40 +117,127 @@ 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: +### Command syntax + +Wrap all `make` commands in `wsl bash -lc`. Set `PROJECT_ROOT` to the WSL path before running commands: ```powershell -wsl make configure -wsl make build -wsl make test +$env:PROJECT_ROOT = '/mnt/c/repos/osapp' +wsl bash -lc "cd $env:PROJECT_ROOT && make configure" +wsl bash -lc "cd $env:PROJECT_ROOT && make build" +wsl bash -lc "cd $env:PROJECT_ROOT && 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. +**Path conversion:** Windows path `c:\repos\osapp` maps to `/mnt/c/repos/osapp` in WSL. To convert any Windows path to WSL, replace the drive letter and colon with `/mnt/` and flip backslashes to forward slashes (e.g. `D:\projects\osapp` → `/mnt/d/projects/osapp`). + +**Windows command template** combining exit-code capture and stderr redirect: -`make build` and `make test` can run for many minutes. Follow these rules to avoid getting stuck not knowing whether a command finished. +```powershell +$env:PROJECT_ROOT = '/mnt/c/repos/osapp' +wsl bash -lc "cd $env:PROJECT_ROOT && make 2>&1; echo 'EXIT: '`$?" +``` -**Always capture the exit code explicitly** — append `; echo "EXIT: $?"` so the final line of output is unambiguous even when stdout is truncated: +Alternatively, open a persistent WSL session and run commands directly there. Never run `make` targets in PowerShell or `cmd.exe`. + +### Prerequisites: Install Ubuntu 22.04 in WSL + +This project's local Windows workflow should match the Linux CI environment (`ubuntu-22.04`). + +1. In PowerShell, install Ubuntu 22.04 in WSL: + ```powershell + wsl --install -d Ubuntu-22.04 + ``` +1. Confirm it is installed: + ```powershell + wsl -l -v + ``` +1. Set Ubuntu-22.04 as the default distro: + ```powershell + wsl --set-default Ubuntu-22.04 + ``` +1. Start Docker Desktop on Windows. +1. In Docker Desktop, enable WSL integration for Ubuntu-22.04: + - Open **Settings > Resources > WSL Integration**. + - Turn on **Enable integration with additional distros**. + - Enable **Ubuntu-22.04**. +1. In PowerShell, verify Docker Desktop is reachable from WSL: + ```powershell + wsl bash -lc 'docker version' + ``` +1. In PowerShell, run a container pull test from WSL: + ```powershell + wsl bash -lc 'docker pull ubuntu:22.04' + ``` +1. Open the distro: + ```powershell + wsl + ``` +1. In Ubuntu, update package indexes: + ```bash + sudo apt update + ``` +1. Install Make: + ```bash + sudo apt install -y make + ``` +1. Verify installation: + ```bash + make --version + ``` +1. Verify Docker access in Ubuntu: + ```bash + docker info + ``` + +If `docker info` fails with a permission error on `/var/run/docker.sock`, run: ```bash -wsl bash -c 'cd /mnt/c/repos/osapp3 && make build; echo "EXIT: $?"' +sudo usermod -aG docker $USER +newgrp docker +docker info ``` -**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: +--- + +## Running make commands on Linux/macOS + +On native Linux or macOS, run `make` targets directly in your terminal without any WSL wrapper. + +### Command syntax + +Navigate to the project root and run commands directly: ```bash -wsl bash -c 'cd /mnt/c/repos/osapp3 && make build 2>&1; echo "EXIT: $?"' +cd /path/to/osapp +make configure +make build +make test ``` -**Completion markers to look for in output:** +**Command template with exit code capture:** -| 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 | +```bash +cd /path/to/osapp && make ; echo "EXIT: $?" +``` -**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. +### Prerequisites + +**Linux (Ubuntu/Debian):** +- Docker must be installed and running +- Install `make`: `sudo apt install make` +- Add user to docker group: `sudo usermod -aG docker $USER && newgrp docker` + +**macOS:** +- Docker Desktop must be installed and running +- `make` is pre-installed via Xcode Command Line Tools + +**macOS GUI testing (optional):** + +To use `make run-app` on macOS: +1. Install XQuartz: `brew install --cask xquartz` +2. Open XQuartz preferences → enable "Allow connections from network clients" +3. Restart XQuartz +4. Set `DISPLAY=host.docker.internal:0` before running `make run-app` --- @@ -124,9 +250,11 @@ After every logical change (e.g. one item from `developer/doc/refactoring-ideas. - 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. + If changes fall into multiple categories, use the highest-priority category that applies, where priority order is: **(1)** `conanfile.py`/`CMakeLists.txt`/`*.cmake` changes → **(2)** `CMakeLists.txt` source additions/removals → **(3)** source-only. When in doubt, run `make configure && make build && make test`. + +1. **`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. The following tests are known baseline failures in the Docker environment and are **not** regressions: +1. **`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 | |------|--------| @@ -135,7 +263,9 @@ After every logical change (e.g. one item from `developer/doc/refactoring-ideas. `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. + If a test fails that is not in the known-baseline table and does not appear related to your change, re-run `make test` once to rule out flakiness. If it fails again, check `git log` on that test file. If the test was already failing on `main` before your change, document it and continue; otherwise treat it as a regression to fix. + +1. **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 steps 2 and 3 succeed. @@ -151,7 +281,7 @@ Three named volumes persist data between runs: | `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. +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. --- diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index cfecbc573..d6ddec3e4 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -1,7 +1,6 @@ # Docker-based CI for pull requests. # -# 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 +# Builds the project inside the osapp-build Docker image 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. @@ -37,34 +36,8 @@ jobs: context: docker/ load: true 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. - - 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 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" - fi + cache-from: type=gha,scope=osapp-build + cache-to: type=gha,scope=osapp-build,mode=max - name: Configure (Conan install + CMake configure) run: make configure @@ -75,22 +48,5 @@ jobs: - 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 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" - - - name: ccache stats - if: always() - run: | - docker run --rm \ - -v osapp-ccache:/ccache \ - -e CCACHE_DIR=/ccache \ - osapp-build:latest \ - ccache --show-stats || true + - name: CppCheck + run: make cppcheck diff --git a/Makefile b/Makefile index feb0da1ca..75f5d22cb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # ============================================================================= -# OpenStudio Application — Docker-based build system +# OpenStudio Application - Docker-based build system # # Prerequisites: Docker (with BuildKit enabled) + GNU make (or WSL/Git Bash # on Windows). @@ -25,7 +25,7 @@ IMAGE := osapp-build TAG := latest BUILD_DIR := build -# Named volumes – persist Conan packages, ccache, and build artifacts 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. @@ -36,6 +36,23 @@ BUILD_VOL := osapp-build # Qt install dir inside the image (matches Dockerfile ENV). QT_INSTALL_DIR := /opt/Qt/6.11.0/gcc_64 +# Extra commands after `make attach` are treated as a command to run in the image. +# Example: make attach env +ATTACH_ARGS := $(filter-out attach,$(MAKECMDGOALS)) +CHECK_BUILD_ARGS := $(filter-out check-build,$(MAKECMDGOALS)) + +# When `attach` or `check-build` is invoked with extra commands (eg: +# `make attach env` or `make check-build env`), prevent make from treating +# those commands as unknown targets. +ifneq (,$(filter attach,$(MAKECMDGOALS))) +$(foreach goal,$(ATTACH_ARGS),$(eval .PHONY: $(goal))) +$(foreach goal,$(ATTACH_ARGS),$(eval $(goal): ; @:)) +endif +ifneq (,$(filter check-build,$(MAKECMDGOALS))) +$(foreach goal,$(CHECK_BUILD_ARGS),$(eval .PHONY: $(goal))) +$(foreach goal,$(CHECK_BUILD_ARGS),$(eval $(goal): ; @:)) +endif + # --------------------------------------------------------------------------- # Base docker run command (non-interactive, workspace mounted as /workspace). # Runs as root so build artifacts have consistent ownership. @@ -59,13 +76,13 @@ DOCKER_RUN := docker run --rm \ all: help # --------------------------------------------------------------------------- -# image — Build the Docker image (slow; only needed when Dockerfile changes). +# image - Build the Docker image (slow; only needed when Dockerfile changes). # --------------------------------------------------------------------------- image: docker build -t $(IMAGE):$(TAG) docker/ # --------------------------------------------------------------------------- -# volumes — Ensure all named volumes exist. +# volumes - Ensure all named volumes exist. # --------------------------------------------------------------------------- volumes: docker volume inspect $(CONAN_VOL) > /dev/null 2>&1 || docker volume create $(CONAN_VOL) @@ -73,20 +90,20 @@ volumes: docker volume inspect $(BUILD_VOL) > /dev/null 2>&1 || docker volume create $(BUILD_VOL) # --------------------------------------------------------------------------- -# configure — Bootstrap Conan, symlink SDK, run conan install + cmake. +# 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 - Compile (uses Ninja + ccache; incremental). # --------------------------------------------------------------------------- build: volumes - $(DOCKER_RUN) cmake --build --preset conan-release -j + $(DOCKER_RUN) cmake --build --preset conan-docker -j # --------------------------------------------------------------------------- -# test — Run CTest inside the build directory. +# 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). @@ -99,7 +116,7 @@ build: volumes # 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 +# GithubRelease* - make live HTTP calls to the GitHub releases API # which hang or fail in a network-sandboxed container. # --------------------------------------------------------------------------- test: build @@ -118,7 +135,7 @@ test: build exit $$CTEST_EXIT" # --------------------------------------------------------------------------- -# cppcheck — Static analysis (matches CI cppcheck.yml flags). +# cppcheck - Static analysis (matches CI cppcheck.yml flags). # Requires build/ to exist for compile_commands.json. # Output written to build/cppcheck-results.txt. # --------------------------------------------------------------------------- @@ -135,7 +152,7 @@ cppcheck: build 2>&1 | tee build/cppcheck-results.txt" # --------------------------------------------------------------------------- -# run-app — Launch the compiled OpenStudioApp with GUI forwarded to the host. +# run-app - Launch the compiled OpenStudioApp with GUI forwarded to the host. # # Automatically detects the host platform: # @@ -161,7 +178,7 @@ cppcheck: build 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. +# 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. @@ -218,36 +235,53 @@ endif $(APP_BIN) # --------------------------------------------------------------------------- -# check-build — Interactive bash shell inside the container (all volumes mounted). +# check-build - Interactive bash shell inside the container (all volumes mounted). # --------------------------------------------------------------------------- check-build: 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 \ - -e CCACHE_DIR=/ccache \ - -e QT_INSTALL_DIR=$(QT_INSTALL_DIR) \ - -w /workspace \ - $(IMAGE):$(TAG) bash + @if [ -n "$(strip $(CHECK_BUILD_ARGS))" ]; then \ + docker run --rm -i \ + -v "$(CURDIR):/workspace" \ + -v "$(BUILD_VOL):/workspace/build" \ + -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) /bin/bash -lc "$(CHECK_BUILD_ARGS)" ; \ + else \ + 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 \ + -e CCACHE_DIR=/ccache \ + -e QT_INSTALL_DIR=$(QT_INSTALL_DIR) \ + -w /workspace \ + $(IMAGE):$(TAG) /bin/bash ; \ + fi # --------------------------------------------------------------------------- -# attach — /bin/sh inside the image with NO volume mounts. +# attach - /bin/bash 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 + @if [ -n "$(strip $(ATTACH_ARGS))" ]; then \ + docker run --rm -i $(IMAGE):$(TAG) /bin/bash -lc "$(ATTACH_ARGS)" ; \ + else \ + docker run --rm -it $(IMAGE):$(TAG) /bin/bash ; \ + fi # --------------------------------------------------------------------------- -# clean — Wipe the build volume (keeps Conan, ccache, and source). +# 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 # --------------------------------------------------------------------------- -# build-clean — Destroy and recreate the build volume (empty slate). +# build-clean - Destroy and recreate the build volume (empty slate). # --------------------------------------------------------------------------- build-clean: docker volume rm $(BUILD_VOL) || true @@ -255,20 +289,20 @@ build-clean: rm -rf $(BUILD_DIR) # --------------------------------------------------------------------------- -# image-clean — Remove the Docker image. +# image-clean - Remove the Docker image. # --------------------------------------------------------------------------- image-clean: docker rmi $(IMAGE):$(TAG) || true # --------------------------------------------------------------------------- -# volumes-clean — Destroy all named volumes (forces complete rebuild). +# volumes-clean - Destroy all named volumes (forces complete rebuild). # --------------------------------------------------------------------------- volumes-clean: docker volume rm $(BUILD_VOL) $(CONAN_VOL) $(CCACHE_VOL) || true rm -rf $(BUILD_DIR) # --------------------------------------------------------------------------- -# help — List all targets. +# help - List all targets. # --------------------------------------------------------------------------- help: @echo "" @@ -279,8 +313,8 @@ 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 " check-build Interactive bash shell inside the build container" - @echo " attach /bin/sh in the image with no mounts (debug image contents)" + @echo " check-build Mounted build shell; run command with: make check-build env" + @echo " attach Image-only /bin/bash (no mounts); run command with: make attach env" @echo " clean Wipe the build volume (equivalent to rm -rf build/)" @echo " build-clean Alias for clean" @echo " image-clean Remove the Docker image" diff --git a/docker/Dockerfile b/docker/Dockerfile index 412f9b3fa..d0a964648 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,10 +2,10 @@ FROM ubuntu:22.04 ARG DEBIAN_FRONTEND=noninteractive -# ── Versions (change here when bumping) ───────────────────────────────────── +# -- 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 \ @@ -21,7 +21,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && 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 +# 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. diff --git a/docker/configure.sh b/docker/configure.sh index 581df16f1..883b1af5f 100644 --- a/docker/configure.sh +++ b/docker/configure.sh @@ -1,18 +1,18 @@ #!/usr/bin/env bash -# configure.sh — runs inside the build container. +# 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. +# 4. Run `cmake --preset conan-docker` to configure the project. set -euo pipefail mkdir -p /workspace/build -# ── Verbose debug header ────────────────────────────────────────────────────── +# -- Verbose debug header ----------------------------------------------------- echo "================================================================" -echo " configure.sh — $(date -u '+%Y-%m-%d %H:%M:%S UTC')" +echo " configure.sh - $(date -u '+%Y-%m-%d %H:%M:%S UTC')" echo " Host: $(uname -a)" echo " User: $(id)" echo " Workdir: $(pwd)" @@ -31,7 +31,7 @@ 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 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} @@ -43,7 +43,7 @@ SDK_URL="https://github.com/NREL/OpenStudio/releases/download/v${SDK_VERSION}/${ echo " SDK_DEST: ${SDK_DEST}" echo " SDK_URL: ${SDK_URL}" -# ── Qt ─────────────────────────────────────────────────────────────────────── +# -- 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 @@ -58,7 +58,7 @@ if [ -d "${SDK_DEST}" ]; then echo " SDK contents (top-level):" ls -lah "${SDK_DEST}" || true else - echo " SDK not found — downloading ..." + echo " SDK not found - downloading ..." mkdir -p "${SDK_DIR}" echo " Downloading ${SDK_URL} ..." curl -fSL --retry 5 --retry-delay 10 --retry-connrefused \ @@ -70,12 +70,12 @@ else echo " SDK extracted to ${SDK_DEST}" fi -# ── Conan first-run bootstrap ───────────────────────────────────────────────── +# -- 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' ..." + 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" @@ -83,7 +83,7 @@ if [ ! -f "${CONAN_HOME}/profiles/default" ]; then 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 + # --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 \ @@ -94,19 +94,19 @@ else cat "${CONAN_HOME}/profiles/default" fi -# ── Ensure nrel-v2 remote is registered and insecure ───────────────────────── +# -- 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 ..." + 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 ..." + 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 @@ -124,18 +124,18 @@ echo " Writing ${CONAN_HOME}/global.conf ..." echo " global.conf written:" cat "${CONAN_HOME}/global.conf" -# ── ccache setup ────────────────────────────────────────────────────────────── +# -- ccache setup ------------------------------------------------------------- if command -v ccache &>/dev/null; then - echo " ccache found — configuring ..." + 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" + echo " WARNING: ccache not found - builds will not be cached" fi -# ── Conan install ───────────────────────────────────────────────────────────── +# -- Conan install ------------------------------------------------------------ echo "==> [3/4] Running conan install ..." echo " conanfile.py: $(head -5 conanfile.py 2>/dev/null || echo 'not found')" conan install . \ @@ -148,13 +148,48 @@ echo " conan install exit code: $?" echo " build/ contents after conan install:" ls -lah build/ | grep -v "^total" || true -# ── CMake configure ─────────────────────────────────────────────────────────── +DOCKER_PRESET_NAME="conan-docker" +echo " Renaming generated preset to: ${DOCKER_PRESET_NAME}" +python3 - <<'PY' +import json +from pathlib import Path + +preset_path = Path("build/CMakePresets.json") +if not preset_path.exists(): + raise SystemExit("ERROR: build/CMakePresets.json not found after conan install") + +with preset_path.open("r", encoding="utf-8") as f: + data = json.load(f) + +renames = { + "conan-release": "conan-docker", +} + +for section in ("configurePresets", "buildPresets", "testPresets", "packagePresets", "workflowPresets"): + for entry in data.get(section, []): + if isinstance(entry, dict): + for key in ("name", "configurePreset", "inherits"): + value = entry.get(key) + if isinstance(value, str) and value in renames: + entry[key] = renames[value] + elif isinstance(value, list): + entry[key] = [renames.get(v, v) if isinstance(v, str) else v for v in value] + +with preset_path.open("w", encoding="utf-8") as f: + json.dump(data, f, indent=2) + f.write("\n") +PY +echo " Updated build/CMakePresets.json preset names:" +grep -n '"name"\|"configurePreset"' build/CMakePresets.json || true + +# -- CMake configure ---------------------------------------------------------- echo "==> [4/4] Running cmake configure ..." -echo " Preset: conan-release" +echo " Preset: ${DOCKER_PRESET_NAME}" echo " CMakeUserPresets.json:" cat CMakeUserPresets.json 2>/dev/null || echo " (not found)" -cmake --preset conan-release \ +cmake --preset "${DOCKER_PRESET_NAME}" \ -DQT_INSTALL_DIR:PATH="${QT_INSTALL_DIR}" \ + -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON \ -DBUILD_DOCUMENTATION:BOOL=OFF \ -DBUILD_PACKAGE:BOOL=OFF \ -DBUILD_TESTING:BOOL=ON \ @@ -164,6 +199,8 @@ 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)" +grep -E "^CMAKE_EXPORT_COMPILE_COMMANDS" \ + build/CMakeCache.txt 2>/dev/null | sort || true echo "" echo "================================================================" From 608b57be5f5343256098c8e3ebfa0324270eb408 Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Sun, 24 May 2026 22:50:55 -0600 Subject: [PATCH 8/9] Remove insecure check --- docker/configure.sh | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/docker/configure.sh b/docker/configure.sh index 883b1af5f..7b5987986 100644 --- a/docker/configure.sh +++ b/docker/configure.sh @@ -83,10 +83,7 @@ if [ ! -f "${CONAN_HOME}/profiles/default" ]; then 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 \ + conan remote add --force nrel-v2 \ https://conan.openstudio.net/artifactory/api/conan/conan-v2 echo " Conan profile created." else @@ -94,20 +91,16 @@ else 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. +# -- Ensure nrel-v2 remote is registered ------------------------------------- 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 ..." + echo " nrel-v2 remote found - ensuring it is enabled ..." conan remote enable nrel-v2 conan remote update nrel-v2 \ - --url https://conan.openstudio.net/artifactory/api/conan/conan-v2 \ - --insecure + --url https://conan.openstudio.net/artifactory/api/conan/conan-v2 else - echo " nrel-v2 remote not registered - adding with --insecure ..." - conan remote add --insecure nrel-v2 \ + echo " nrel-v2 remote not registered - adding ..." + conan remote add nrel-v2 \ https://conan.openstudio.net/artifactory/api/conan/conan-v2 fi echo " Active Conan remotes:" From 3c3e383cf617b8ee7ffecbf912c5dd073e956786 Mon Sep 17 00:00:00 2001 From: Dan Macumber Date: Sun, 24 May 2026 23:24:44 -0600 Subject: [PATCH 9/9] reference build instructions from agents.md --- .../instructions/docker-build.instructions.md | 2 +- AGENTS.md | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md diff --git a/.github/instructions/docker-build.instructions.md b/.github/instructions/docker-build.instructions.md index e17efe7fc..8ce877ed3 100644 --- a/.github/instructions/docker-build.instructions.md +++ b/.github/instructions/docker-build.instructions.md @@ -1,5 +1,5 @@ --- -description: "Use when building, testing, or validating C++ changes. Covers how to compile the project inside the Docker build container using Makefile targets." +description: "Use when building, compiling, testing, or validating C++ changes; fixing build errors or compilation failures; running make configure, make build, make test, or cppcheck; working with CMake, Conan, Docker, or Makefile; or refactoring C++ code. Covers how to compile and test the project inside the Docker build container." applyTo: "src/**/*.cpp,src/**/*.hpp,src/**/CMakeLists.txt,docker/**,Makefile" --- diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..0f442fb74 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,62 @@ +# OpenStudio Application Development + +## Project Overview + +Cross-platform (Windows, Mac, Linux) graphical interface for OpenStudio energy models. Built on the [OpenStudio SDK](https://github.com/NREL/OpenStudio) using Qt 6.11.0 and C++20. + +## Quick Start + +See [BUILDING.md](BUILDING.md) for comprehensive build instructions. + +**Docker-based local builds** (for validation, not production): +```bash +# First time setup +make image && make configure && make build && make test + +# After source file edits +make build && make test + +# After CMakeLists.txt or conanfile.py changes +make configure && make build && make test +``` + +**Windows users:** Run `make` commands via WSL (Ubuntu 22.04), not PowerShell. See [docker-build instructions](.github/instructions/docker-build.instructions.md) for setup details. + +**Production builds** use native conan + cmake (see [BUILDING.md](BUILDING.md)). + +## Code Standards + +- **C++20** standard, compile with `-Werror` (warnings are errors) +- **Qt 6.11.0** for all GUI components +- **Unit tests required** with 90%+ coverage (see [CONTRIBUTING.md](CONTRIBUTING.md)) +- Run static analysis: `make cppcheck` + +## Testing + +**Known baseline failures** in Docker environment (expected, not regressions): +- `ModelEditorFixture.MorePath_Conversions` — Windows path test always fails on Linux +- `OpenStudioLibFixture.AnalyticsHelperSecrets` — Requires CI-injected secrets + +All other tests must pass. Zero warnings, zero regressions. + +## Architecture + +- **src/model_editor** — Core model editing functionality and Qt UI components +- **src/openstudio_lib** — Application library layer, main UI screens +- **src/openstudio_app** — Qt application entry point and lifecycle +- **src/shared_gui_components** — Reusable widgets and workflow controllers + +Supports OpenStudio Measures (Ruby/Python scripts for model transformations). + +## Key Conventions + +- Docker builds are for **local validation only**; CI uses native builds on ubuntu-22.04, macos-12, and windows-2022 +- Build artifacts live in a Docker volume (`osapp-build`), not on the host filesystem — use `make check-build` to inspect +- On Windows, Makefile targets require WSL bash; they will not work in PowerShell or cmd.exe + +## Resources + +- [Documentation](https://openstudiocoalition.org/) +- [Contributing Guidelines](CONTRIBUTING.md) +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Building Instructions](BUILDING.md)