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() # ── Fast test-build mode (lever #10, opt-in for CI-PR loops) ─────────────────── # # Compile the test targets with `-O0 -g` instead of the default `-O3`. # The Eigen + CGAL templates dominate the BACKEND (CodeGen + Opt) phase # of every TU at ~55 % of wall time (~9.3 s of a 17 s TU per # `clang -ftime-trace`). Dropping to `-O0` collapses that phase to # <2 s and yields ~40 % faster full rebuilds. The downside is that # the resulting binaries are 2-5× slower to RUN — fine for "does it # compile + do all 259 unit tests pass?" CI loops, NOT fine for any # scalability or benchmark workload. # # Library/installable code is never affected; only the test # executables compiled into build-*/ pick this flag up. option(CONFORMALLAB_FAST_TEST_BUILD "Compile test executables with -O0 -g for faster CI / dev loops." OFF) if(CONFORMALLAB_FAST_TEST_BUILD) message(STATUS "CONFORMALLAB_FAST_TEST_BUILD active — tests compile at -O0 -g.") 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 /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()