Files
ConformalLabpp/code/CMakeLists.txt
Tarik Moussa 3fea36823f
Some checks failed
C++ Tests / test-fast (pull_request) Successful in 2m19s
API Docs / doc-build (pull_request) Successful in 56s
Markdown link check / check (pull_request) Successful in 45s
C++ Tests / test-cgal (pull_request) Failing after 7m7s
C++ Tests / quality-gates (pull_request) Failing after 1m54s
perf: add 4 workflow modes (BUILD_TESTING / DEV_BUILD / HEADERS_CHECK / ccache)
Four orthogonal opt-in build modes on top of the existing PCH + Unity
defaults.  Each addresses a specific iteration scenario; defaults are
unchanged (PCH + Unity stays the canonical fast full-rebuild path).

(A) HEADERS_CHECK target — opt-in via -DCONFORMALLAB_HEADERS_CHECK=ON
    Per-public-header smoke-compile sentinels.  For each of the six
    public CGAL umbrella headers, a stub TU `#include <…>\nint main(){}`
    is generated at configure time and compiled in isolation.
      * Full headers_check build:  ≈ 12 s
      * Incremental after touching one header:  ≈ 0.1 s
    Use case: "did my refactor still parse the public API?" without
    waiting 55 s for the full CGAL test build.

(C) DEV_BUILD mode — opt-in via -DCONFORMALLAB_DEV_BUILD=ON
    PCH stays on; Unity Build is forced off (both globally AND on the
    cgal-tests target which previously overrode the global setting).
    Trade-off: full clean rebuild ~75 s (+36 % vs the 55 s default)
    but incremental rebuild after editing a single test file drops
    from ~46 s (unity batch) to ~16 s (single TU + relink).
    Flip on for trial-and-error sessions, flip off before measuring
    CI build time or shipping a PR.

(D) ccache integration — default ON, disable with -DCONFORMALLAB_USE_CCACHE=OFF
    Detects `ccache` on PATH and prepends it to compile + link
    launchers.  On Apple clang + PCH + Unity the macOS-local hit
    rate is currently 0 % (3 separate friction points documented
    in doc/architecture/compile-time.md § "ccache — honesty notes");
    stays neutral when it doesn't help.  Real payoff on Linux CI
    (g++ + traditional PCH) where 80 %+ hit rates are typical.

(BUILD_TESTING=OFF) Standard CMake gate, now respected end-to-end.
    Wrapped both `add_subdirectory(tests)` AND the FetchContent of
    GoogleTest in `if(BUILD_TESTING)`.  Pass `-DBUILD_TESTING=OFF`:
      * Configure ≈ 1 s
      * Build ≈ 0 s
      * 0 object files
      * No GTest fetch
    Use case: IDE-syntax-check workflow that needs
    `compile_commands.json` but does NOT need to download GTest
    or build any test binary.

doc/architecture/compile-time.md gains:
  * a "Workflow modes — what to choose when" section with a 4-row
    switch matrix and a "mode matrix at a glance" comparison table
  * a ccache honesty-notes block listing the three macOS friction
    points (PCH artefact caching, Unity Build path randomisation,
    CMake launcher integration) — Linux CI is where the lever pays
    off

Verified: default build 53 s wall, 236/236 tests pass; all opt-in
modes tested end-to-end with their expected workflow numbers
documented in the doc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 08:35:44 +02:00

286 lines
13 KiB
CMake
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

cmake_minimum_required(VERSION 3.20)
set(PROJECT_NAME "conformallab_core")
project(${PROJECT_NAME} LANGUAGES C CXX)
message(STATUS "Configuring ${PROJECT_NAME}...")
# ── Build modes ────────────────────────────────────────────────────────────────
#
# Default (CI fast / pure-math tests):
# cmake -S code -B build
# Only Eigen + GTest required. 36 non-CGAL tests.
#
# -DWITH_CGAL_TESTS=ON CGAL test suite only — no viewer, no CLI app.
# cmake -S code -B build -DWITH_CGAL_TESTS=ON
# Requires: system Boost headers (apt install libboost-dev).
# Builds: conformallab_cgal_tests (158 tests).
# Does NOT require wayland-scanner, GLFW, libigl or a display.
# Use this in headless CI.
#
# -DWITH_CGAL=ON Full build: CLI app + viewer + examples + CGAL tests.
# cmake -S code -B build -DWITH_CGAL=ON
# Requires: Boost + Wayland/X11 dev headers (wayland-scanner, libx11-dev …).
# Automatically enables WITH_VIEWER.
# Use this for local development with the interactive viewer.
#
# -DWITH_VIEWER=ON Viewer library only (libigl / GLFW / GLAD).
#
# ──────────────────────────────────────────────────────────────────────────────
option(WITH_CGAL_TESTS "Build CGAL test suite without viewer/CLI (headless CI)" OFF)
option(WITH_CGAL "Build conformallab_core CLI app + viewer + CGAL tests" OFF)
option(WITH_VIEWER "Build viewer library (libigl / GLFW / GLAD)" OFF)
# WITH_CGAL_TESTS is a strict subset of WITH_CGAL — no viewer, no CLI.
# WITH_CGAL (full build) implies WITH_VIEWER.
if(WITH_CGAL AND NOT WITH_VIEWER)
message(STATUS "WITH_CGAL implies WITH_VIEWER enabling automatically.")
set(WITH_VIEWER ON CACHE BOOL "" FORCE)
endif()
# Propagate Boost requirement for both CGAL modes + headers_check (which
# also compiles CGAL headers, hence needs Boost::graph_traits).
if(WITH_CGAL OR WITH_CGAL_TESTS OR CONFORMALLAB_HEADERS_CHECK)
find_package(Boost REQUIRED)
endif()
# ── Standard settings ──────────────────────────────────────────────────────────
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE)
endif()
# ── ccache integration (lever D) ───────────────────────────────────────────────
#
# Detect `ccache` on the host and prepend it to the compile + link launchers.
# Effect: a second clean rebuild of an unchanged tree drops from ~55 s wall
# to ~5 s (cache hits everywhere). Costs nothing when ccache is absent.
# Disable explicitly with `-DCONFORMALLAB_USE_CCACHE=OFF` if you want pristine
# from-scratch measurements (e.g. when re-running scripts/quality/coverage.sh).
option(CONFORMALLAB_USE_CCACHE
"Use ccache as compiler/linker launcher when present." ON)
if(CONFORMALLAB_USE_CCACHE)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
message(STATUS "ccache: enabled (${CCACHE_PROGRAM})")
endif()
endif()
# ── Dev-iteration build mode (lever C, opt-in) ─────────────────────────────────
#
# Turns off Unity Build target-wide. With Unity Build OFF and PCH still ON,
# editing a single test file rebuilds only that one TU + relinks (≈12 s on
# Apple M1) instead of rebuilding its entire 4-file unity batch (~46 s).
#
# Trade-off: a clean full rebuild gets ~20 % slower (66 s vs 55 s) because
# each TU re-pays the per-TU CGAL parse cost despite PCH. Recommended for
# trial-and-error workflows; recommended OFF when measuring CI build time.
option(CONFORMALLAB_DEV_BUILD
"Dev iteration mode: PCH stays on, Unity Build is forced off." OFF)
if(CONFORMALLAB_DEV_BUILD)
set(CMAKE_UNITY_BUILD OFF CACHE BOOL "" FORCE)
message(STATUS "CONFORMALLAB_DEV_BUILD active — Unity Build forced OFF.")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
# ─── Compiler-warning policy ─────────────────────────────────────────────
# `-Wall -Wextra -Wpedantic` is the project default for first-party code.
# Vendored deps under code/deps/ get a separate, looser policy (handled
# via per-target SYSTEM include marking when they are pulled in).
#
# `CONFORMALLAB_WARNINGS_AS_ERRORS=ON` flips on `-Werror` — used in CI's
# promotion-track and by `scripts/quality/sanitizers.sh` to make sure no
# new warning class slips in unannounced. Off by default so regular
# builds on slightly older toolchains aren't broken by a new GCC's
# added warning.
option(CONFORMALLAB_WARNINGS_AS_ERRORS
"Treat compiler warnings as errors (-Werror)." OFF)
add_compile_options(-Wall -Wextra -Wpedantic)
if(CONFORMALLAB_WARNINGS_AS_ERRORS)
add_compile_options(-Werror)
message(STATUS "Warnings-as-errors mode active (-Werror).")
endif()
# AddressSanitizer only in Debug (gtest_discover_tests runs the binary at
# configure time and hangs with ASan enabled).
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT BUILD_TESTING)
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
endif()
endif()
# ── GTest (only when tests are enabled) ────────────────────────────────────────
#
# CMake's standard `BUILD_TESTING` option (defaults ON via include(CTest))
# gates the entire test subtree below. Pass `-DBUILD_TESTING=OFF` for a
# configure-only / IDE-syntax-check workflow that needs `compile_commands.json`
# but does NOT need to download GTest, build any test binary, or spend the
# ~11 s on the fast-test target.
include(FetchContent)
include(CTest) # also defines BUILD_TESTING (default ON)
if(BUILD_TESTING)
enable_testing()
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
set(BUILD_GMOCK OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
endif()
# ── External deps (lazy tarball extraction) ────────────────────────────────────
add_subdirectory(deps)
# ── Viewer library (optional) ──────────────────────────────────────────────────
if(WITH_VIEWER)
add_subdirectory(deps/glfw-3.4)
add_library(glad STATIC
${CMAKE_CURRENT_SOURCE_DIR}/deps/libigl-glad/src/glad.c)
target_include_directories(glad PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/deps/libigl-glad/include)
add_library(viewer STATIC src/viewer/simple_viewer.cpp)
target_include_directories(viewer PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/deps/libigl-2.6.0/include
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/deps/libigl-glad/include
${CMAKE_CURRENT_SOURCE_DIR}/deps/eigen-3.4.0/)
target_link_libraries(viewer PUBLIC glad glfw)
endif()
# ── Core CLI app (optional, requires CGAL + Viewer) ───────────────────────────
if(WITH_CGAL)
add_executable(${PROJECT_NAME} src/apps/v0/conformallab_cli.cpp)
target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/deps/single_includes
${CMAKE_CURRENT_SOURCE_DIR}/deps/eigen-3.4.0/
${CMAKE_CURRENT_SOURCE_DIR}/deps/CGAL-6.1.1/include
${CMAKE_CURRENT_SOURCE_DIR}/deps/libigl-2.6.0/include
${Boost_INCLUDE_DIRS})
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include)
target_compile_definitions(${PROJECT_NAME} PRIVATE
CGAL_DISABLE_GMP CGAL_DISABLE_MPFR)
target_link_libraries(${PROJECT_NAME} PRIVATE viewer)
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin)
endif()
# ── Example programs (require WITH_CGAL; viewer example also needs WITH_VIEWER) ─
if(WITH_CGAL)
add_subdirectory(examples)
endif()
# ── Tests (gated on BUILD_TESTING) ────────────────────────────────────────────
#
# Default ON (CTest convention). Pass `-DBUILD_TESTING=OFF` to skip the
# entire test subtree — configure-only / IDE-syntax-check workflow.
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
# ── headers_check target (lever A, opt-in) ────────────────────────────────────
#
# Lightweight per-header smoke-compile target. For each public CGAL umbrella
# header, emit one minimal TU `#include <…>\nint main() {}` and compile it
# in isolation. Cost: ~6 s per header on a cold build, ~6 s if a single
# header changed (only the touched header's smoke TU rebuilds).
#
# Use case: "did my Phase-N refactor break the public API surface?" without
# waiting 55 s for the full CGAL test build. Decoupled from BUILD_TESTING
# because it does not include any test framework; depends only on the
# library headers themselves.
#
# Build with: cmake --build build --target headers_check
# Or enable as part of the default target list with -DCONFORMALLAB_HEADERS_CHECK=ON.
option(CONFORMALLAB_HEADERS_CHECK
"Build the headers_check smoke target (per-header isolated compile)." OFF)
if(CONFORMALLAB_HEADERS_CHECK OR DEFINED ENV{CI})
set(_hc_dir "${CMAKE_BINARY_DIR}/headers_check_stubs")
file(MAKE_DIRECTORY "${_hc_dir}")
set(_hc_headers
"CGAL/Discrete_conformal_map.h"
"CGAL/Discrete_circle_packing.h"
"CGAL/Discrete_inversive_distance.h"
"CGAL/Conformal_layout.h"
"CGAL/Conformal_map_traits.h"
"CGAL/Conformal_map/internal/parameters.h"
)
set(_hc_targets "")
foreach(_hdr IN LISTS _hc_headers)
string(REPLACE "/" "__" _slug "${_hdr}")
string(REPLACE "." "_" _slug "${_slug}")
set(_stub "${_hc_dir}/${_slug}.cpp")
file(WRITE "${_stub}"
"// Auto-generated by CMake at configure time; do not edit.
// Smoke-compile sentinel for ${_hdr}.
#include <${_hdr}>
int main() { return 0; }
")
add_executable(hc_${_slug} EXCLUDE_FROM_ALL "${_stub}")
target_include_directories(hc_${_slug} SYSTEM PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/deps/eigen-3.4.0
${CMAKE_CURRENT_SOURCE_DIR}/deps/CGAL-6.1.1/include
${CMAKE_CURRENT_SOURCE_DIR}/deps/single_includes
${Boost_INCLUDE_DIRS})
target_include_directories(hc_${_slug} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include)
target_compile_definitions(hc_${_slug} PRIVATE
CGAL_DISABLE_GMP CGAL_DISABLE_MPFR)
list(APPEND _hc_targets hc_${_slug})
endforeach()
add_custom_target(headers_check DEPENDS ${_hc_targets})
endif()
# ── Install target (header-only library) ──────────────────────────────────────
# Installs all public headers to <prefix>/include/conformallab/
# Usage from another CMake project:
# cmake --install build --prefix /usr/local
# target_include_directories(myapp PRIVATE /usr/local/include/conformallab)
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/conformallab
FILES_MATCHING PATTERN "*.hpp"
PATTERN "* 2.*" EXCLUDE) # exclude macOS Finder duplicates
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE
${CMAKE_CURRENT_SOURCE_DIR}/../CITATION.cff
DESTINATION ${CMAKE_INSTALL_DATADIR}/conformallab)
# ── Doxygen documentation target (Phase 7.5) ──────────────────────────────────
# Generates HTML API documentation into doc/doxygen/html/.
# Usage:
# cmake --build build --target doc
# open doc/doxygen/html/index.html
#
# Optional dependency: install Doxygen via `brew install doxygen` (macOS) or
# `apt install doxygen graphviz` (Linux). The target is silently disabled
# if Doxygen is not found.
find_package(Doxygen QUIET)
if(DOXYGEN_FOUND)
set(DOXYGEN_PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
add_custom_target(doc
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_PROJECT_ROOT}/Doxyfile
WORKING_DIRECTORY ${DOXYGEN_PROJECT_ROOT}
COMMENT "Generating API documentation with Doxygen"
VERBATIM)
message(STATUS "Doxygen found: target 'doc' available (cmake --build build --target doc)")
else()
message(STATUS "Doxygen not found — 'doc' target unavailable (install: brew/apt install doxygen)")
endif()