From 88e5f897337d81a44e51f7494218eff847dbae48 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 01/19] Make Approx relative for large values, handle NaN and inf --- src/core/tools/codac2_Approx.h | 20 ++++++++++++++++++- .../tube/codac2_tests_SlicedTube_integral.cpp | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/core/tools/codac2_Approx.h b/src/core/tools/codac2_Approx.h index 721da8249..ef0583af5 100644 --- a/src/core/tools/codac2_Approx.h +++ b/src/core/tools/codac2_Approx.h @@ -40,7 +40,25 @@ namespace codac2 friend bool operator==(const T& x1, const Approx& x2) { if constexpr(std::is_same_v) - return std::fabs(x1-x2._x) < x2._eps; + { + if(std::isnan(x1) && std::isnan(x2._x)) + return true; + + else if(x2._x == std::numeric_limits::max()) + return x1 == std::numeric_limits::max(); + + else if(x2._x == -std::numeric_limits::max()) + return x1 == -std::numeric_limits::max(); + + else if(std::isinf(x2._x)) + return x1 == x2._x; + + else if(x2._x < 1.0 && x2._x > -1.0) + return std::fabs(x1-x2._x) < x2._eps; // absolute error + + else + return std::fabs(x1-x2._x) < x2._eps*std::max(std::fabs(x1),std::fabs(x2._x)); // relative error + } else if(x1.size() != x2._x.size()) return false; diff --git a/tests/core/domains/tube/codac2_tests_SlicedTube_integral.cpp b/tests/core/domains/tube/codac2_tests_SlicedTube_integral.cpp index 4cba879ab..c5bd5294b 100644 --- a/tests/core/domains/tube/codac2_tests_SlicedTube_integral.cpp +++ b/tests/core/domains/tube/codac2_tests_SlicedTube_integral.cpp @@ -151,8 +151,8 @@ TEST_CASE("Computing integration from 0, interval argument") CHECK(Approx(x.integral(Interval(12.5))) == Interval(6.5,20.5)); CHECK(Approx(x.integral(Interval(14.5))) == Interval(7,23.5)); auto p_intv = x.partial_integral(Interval(12.5,14.5)); - CHECK(p_intv.first == Interval(6.,7.)); - CHECK(p_intv.second == Interval(20.5,23.5)); + CHECK(Approx(p_intv.first) == Interval(6.,7.)); + CHECK(Approx(p_intv.second) == Interval(20.5,23.5)); CHECK(Approx(x.integral(Interval(12.5,14.5))) == Interval(6.0,23.5)); CHECK(Approx(x.integral(Interval(0))) == Interval(0)); CHECK(Approx(x.integral(Interval(10.2))) == Interval(9.3,19.7)); From ee89eab48009054e4f3ab288096a278b2bfc4b96 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 02/19] Build Codac on GAOL from Jordan Ninin's fork instead of IBEX --- CMakeLists.txt | 88 +- .../tuto/cp_robotics/src/CMakeLists.txt | 12 +- examples/00_graphics/CMakeLists.txt | 4 - examples/01_batman/CMakeLists.txt | 4 - examples/02_centered_form/CMakeLists.txt | 4 - examples/03_sivia/CMakeLists.txt | 4 - examples/04_explored_area/CMakeLists.txt | 4 - examples/05_capd_solver/CMakeLists.txt | 11 - examples/06_graphics_3D/CMakeLists.txt | 4 - examples/07_centered_2D/CMakeLists.txt | 4 - examples/08_centered_3D/CMakeLists.txt | 4 - examples/09_robot_simu/CMakeLists.txt | 4 - examples/10_lie_groups/CMakeLists.txt | 4 - examples/11_peibos/CMakeLists.txt | 4 - examples/12_peibos_capd/CMakeLists.txt | 5 - examples/13_qinter/CMakeLists.txt | 4 - examples/14_lohner/CMakeLists.txt | 4 - examples/15_sympy/CMakeLists.txt | 5 - examples/16_visibility/CMakeLists.txt | 4 - examples/ellipsoid_example/CMakeLists.txt | 12 +- python/src/core/CMakeLists.txt | 2 +- python/src/graphics/CMakeLists.txt | 2 +- python/src/unsupported/CMakeLists.txt | 2 +- scripts/CMakeModules/FindGAOL.cmake | 208 +++++ scripts/CMakeModules/codac_gaol.cmake | 799 ++++++++++++++++++ src/CMakeLists.txt | 70 +- src/core/CMakeLists.txt | 2 +- src/extensions/capd/CMakeLists.txt | 2 +- src/extensions/sympy/CMakeLists.txt | 2 +- src/graphics/CMakeLists.txt | 2 +- tests/CMakeLists.txt | 6 +- tests/test_codac/CMakeLists.txt | 13 +- 32 files changed, 1168 insertions(+), 131 deletions(-) create mode 100644 scripts/CMakeModules/FindGAOL.cmake create mode 100644 scripts/CMakeModules/codac_gaol.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f65251466..cb078228f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,26 @@ #set(CMAKE_C_COMPILER "gcc-7") #set(CMAKE_CXX_COMPILER "/usr/bin/g++-7") - project(codac VERSION ${VERSION} LANGUAGES CXX) + # CMP0091 (CMake >=3.15) makes the CMAKE_MSVC_RUNTIME_LIBRARY variable set + # below actually control MSVC's runtime library selection, instead of + # CMake's older behavior of baking a hardcoded /MD.../MDd into + # CMAKE__FLAGS_. It must be set to NEW before project() -- + # afterwards is too late -- for that variable to have any effect at all. + if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) + endif() + + # C as well as C++: the codac sources are all C++, but mathlib, which GAOL + # depends on, is C, and codac_gaol_build() builds it when no GAOL is installed + # (see scripts/CMakeModules/codac_gaol.cmake). Enabled here, the C compiler + # and its flags are settled -- and cached -- by the first configuration, so + # that what is handed to that build is the same at every later one. Enabled + # further down by a dependency instead, as happened when only CXX was listed, + # the compiler went from the name it was given to its full path between the + # first configuration and the second, the build of GAOL took that for a + # change and was configured again, and everything in codac that includes an + # interval was recompiled. + project(codac VERSION ${VERSION} LANGUAGES C CXX) if(NOT VERSION_ID) set(PROJECT_VERSION_FULL ${PROJECT_VERSION}) @@ -84,6 +103,30 @@ add_compile_options(-Wall -Wextra -Wpedantic) endif() + if(MSVC) + # The gaol interval library is only ever linked as a Release binary: + # codac_gaol_build() (scripts/CMakeModules/codac_gaol.cmake) builds it in + # Release whatever the configuration of codac, and a gaol found on the + # system comes prebuilt that way too. gaol.lib is thus compiled with the + # Release CRT (/MD). MSVC's linker refuses + # to mix object files built against different CRT variants in the same + # binary -- linking that Release-CRT gaol.lib together with codac's own + # object files, which without this override would default to the Debug + # CRT (/MDd) in a Debug build, fails with error LNK2038 ("mismatch + # detected for 'RuntimeLibrary'/'_ITERATOR_DEBUG_LEVEL'") followed by + # fatal error LNK1319. + # + # Forcing every configuration onto the Release CRT keeps codac's own + # Debug build link-compatible with that Release gaol.lib without + # having to build gaol in Debug as well. Release + # itself already defaulted to /MD, so in practice this only changes + # what the Debug configuration uses. Debug builds still get no + # optimization (/Od) and full debug symbols (/Zi) -- what is traded away + # is only the *separate* debug-CRT extras that /MDd would otherwise add + # on top of that (its own heap debugging and iterator-debug-level checks). + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") + endif() + # Temporary attempts to fix errors similar to: # _ number of sections exceeded object file format limit. # _ out of memory allocating XXX bytes. @@ -118,12 +161,47 @@ ################################################################################ -# Looking for IBEX +# Looking for GAOL ################################################################################ - find_package(IBEX REQUIRED) - ibex_init_common() # IBEX should have installed this function - message(STATUS "Found IBEX version ${IBEX_VERSION}") + # GAOL is the interval arithmetic library codac2::Interval is built upon, and + # mathlib (libultim) the library GAOL computes its elementary functions with. + # Both used to come from IBEX, which bundles them, and they were all Codac + # needed IBEX for: they are now found, or built, by Codac itself. What comes + # from IBEX in doing so, and who wrote it, is said at the top of + # scripts/CMakeModules/codac_gaol.cmake and of FindGAOL.cmake next to it. + include(codac_gaol) + + # GAOL, found or built, as the imported target Codac::gaol: its include + # directories, its libraries, and the flags floating-point rounding depends + # on, which ibex_init_common() used to put on the command line -- without + # -frounding-math and its companions, the compiler is free to constant-fold in + # the wrong rounding mode, and an interval is no longer a bound. They are + # GAOL's to say: codac_gaol_find() takes them from the CMake package of GAOL + # or from its gaol.pc, and determines them itself only for a GAOL found by its + # files alone. The Codac libraries link Codac::gaol PUBLIC, so that every + # target linking them is compiled with those flags -- the tests, the examples + # and the Python bindings included. This comes before Codac's own flags are + # added to CMAKE_CXX_FLAGS below: a GAOL built by codac_gaol_build() is handed + # CMAKE_CXX_FLAGS as it is given to this build, and chooses its flags itself. + option(ENABLE_FIND_PACKAGE_GAOL "ENABLE_FIND_PACKAGE_GAOL" ON) + codac_gaol_find() + + # Consumers need those flags as much as this build does. What Codac::gaol + # gives is kept as lists, handed over through CODAC_CXX_FLAGS in + # codac-config.cmake, and through the Cflags and Libs lines of codac.pc, which + # cannot link a target (see src/CMakeLists.txt). + codac_gaol_usage(CODAC_GAOL_INCLUDE_DIRS CODAC_INTERVAL_CXX_FLAGS CODAC_GAOL_LINK_ITEMS) + message(STATUS "Interval arithmetic flags: ${CODAC_INTERVAL_CXX_FLAGS}") + message(STATUS "GAOL include directories: ${CODAC_GAOL_INCLUDE_DIRS}") + message(STATUS "GAOL libraries: ${CODAC_GAOL_LINK_ITEMS}") + + # The flags of ibex_init_common() that are not those of interval arithmetic, + # where ibex_init_common() put them, in CMAKE_CXX_FLAGS, so that every target + # is compiled with them; handed over to consumers with the others. + codac_gaol_portability_flags(CODAC_PORTABILITY_CXX_FLAGS) + string(REPLACE ";" " " _codac_portability_cxx_flags "${CODAC_PORTABILITY_CXX_FLAGS}") + string(APPEND CMAKE_CXX_FLAGS " ${_codac_portability_cxx_flags}") ################################################################################ diff --git a/doc/manual/tuto/cp_robotics/src/CMakeLists.txt b/doc/manual/tuto/cp_robotics/src/CMakeLists.txt index 51161a85e..ad8a88fc7 100644 --- a/doc/manual/tuto/cp_robotics/src/CMakeLists.txt +++ b/doc/manual/tuto/cp_robotics/src/CMakeLists.txt @@ -8,16 +8,6 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Adding IBEX - - # In case you installed IBEX in a local directory, you need - # to specify its path with the CMAKE_PREFIX_PATH option. - # set(CMAKE_PREFIX_PATH "~/ibex-lib/build_install") - - find_package(IBEX REQUIRED) - ibex_init_common() # IBEX should have installed this function - message(STATUS "Found IBEX version ${IBEX_VERSION}") - # Adding Codac # In case you installed Codac in a local directory, you need @@ -38,4 +28,4 @@ ) target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex) \ No newline at end of file + target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES}) \ No newline at end of file diff --git a/examples/00_graphics/CMakeLists.txt b/examples/00_graphics/CMakeLists.txt index cf4fae020..95b125893 100644 --- a/examples/00_graphics/CMakeLists.txt +++ b/examples/00_graphics/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/01_batman/CMakeLists.txt b/examples/01_batman/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/01_batman/CMakeLists.txt +++ b/examples/01_batman/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/02_centered_form/CMakeLists.txt b/examples/02_centered_form/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/02_centered_form/CMakeLists.txt +++ b/examples/02_centered_form/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/03_sivia/CMakeLists.txt b/examples/03_sivia/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/03_sivia/CMakeLists.txt +++ b/examples/03_sivia/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/04_explored_area/CMakeLists.txt b/examples/04_explored_area/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/04_explored_area/CMakeLists.txt +++ b/examples/04_explored_area/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/05_capd_solver/CMakeLists.txt b/examples/05_capd_solver/CMakeLists.txt index 4d720d238..2d3daae24 100644 --- a/examples/05_capd_solver/CMakeLists.txt +++ b/examples/05_capd_solver/CMakeLists.txt @@ -8,16 +8,6 @@ project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Adding IBEX - -# In case you installed IBEX in a local directory, you need -# to specify its path with the CMAKE_PREFIX_PATH option. -# set(CMAKE_PREFIX_PATH "~/ibex-lib/build_install") - -find_package(IBEX REQUIRED) -ibex_init_common() # IBEX should have installed this function -message(STATUS "Found IBEX version ${IBEX_VERSION}") - # Adding Codac # In case you installed Codac in a local directory, you need @@ -41,5 +31,4 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ${CODAC_LIBRARIES} ${CODAC_CAPD_LIBRARY} capd::capd - Ibex::ibex ) \ No newline at end of file diff --git a/examples/06_graphics_3D/CMakeLists.txt b/examples/06_graphics_3D/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/06_graphics_3D/CMakeLists.txt +++ b/examples/06_graphics_3D/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/07_centered_2D/CMakeLists.txt b/examples/07_centered_2D/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/07_centered_2D/CMakeLists.txt +++ b/examples/07_centered_2D/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/08_centered_3D/CMakeLists.txt b/examples/08_centered_3D/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/08_centered_3D/CMakeLists.txt +++ b/examples/08_centered_3D/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/09_robot_simu/CMakeLists.txt b/examples/09_robot_simu/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/09_robot_simu/CMakeLists.txt +++ b/examples/09_robot_simu/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/10_lie_groups/CMakeLists.txt b/examples/10_lie_groups/CMakeLists.txt index 41d3ccba3..87b2ee865 100644 --- a/examples/10_lie_groups/CMakeLists.txt +++ b/examples/10_lie_groups/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/11_peibos/CMakeLists.txt b/examples/11_peibos/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/11_peibos/CMakeLists.txt +++ b/examples/11_peibos/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/12_peibos_capd/CMakeLists.txt b/examples/12_peibos_capd/CMakeLists.txt index 9091ca50a..efb53fdfa 100644 --- a/examples/12_peibos_capd/CMakeLists.txt +++ b/examples/12_peibos_capd/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) @@ -35,5 +31,4 @@ ${CODAC_LIBRARIES} ${CODAC_CAPD_LIBRARY} capd::capd - Ibex::ibex ) \ No newline at end of file diff --git a/examples/13_qinter/CMakeLists.txt b/examples/13_qinter/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/13_qinter/CMakeLists.txt +++ b/examples/13_qinter/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/14_lohner/CMakeLists.txt b/examples/14_lohner/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/14_lohner/CMakeLists.txt +++ b/examples/14_lohner/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/15_sympy/CMakeLists.txt b/examples/15_sympy/CMakeLists.txt index 52197a061..336db6b62 100644 --- a/examples/15_sympy/CMakeLists.txt +++ b/examples/15_sympy/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) @@ -37,5 +33,4 @@ ${CODAC_LIBRARIES} ${CODAC_SYMPY_LIBRARY} # linking to the codac-sympy extension pybind11::embed # linking to pybind11 - Ibex::ibex ) \ No newline at end of file diff --git a/examples/16_visibility/CMakeLists.txt b/examples/16_visibility/CMakeLists.txt index cf9ef35ce..23d13fc6e 100644 --- a/examples/16_visibility/CMakeLists.txt +++ b/examples/16_visibility/CMakeLists.txt @@ -17,10 +17,6 @@ find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") -# Initializating Ibex - - ibex_init_common() - # Compilation if(FAST_RELEASE) diff --git a/examples/ellipsoid_example/CMakeLists.txt b/examples/ellipsoid_example/CMakeLists.txt index ce82f2152..ad0a314c7 100644 --- a/examples/ellipsoid_example/CMakeLists.txt +++ b/examples/ellipsoid_example/CMakeLists.txt @@ -8,16 +8,6 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Adding IBEX - - # In case you installed IBEX in a local directory, you need - # to specify its path with the CMAKE_PREFIX_PATH option. - # set(CMAKE_PREFIX_PATH "~/ibex-lib/build_install") - - find_package(IBEX REQUIRED) - ibex_init_common() # IBEX should have installed this function - message(STATUS "Found IBEX version ${IBEX_VERSION}") - # Adding Eigen3 # In case you installed Eigen3 in a local directory, you need @@ -41,4 +31,4 @@ add_executable(${PROJECT_NAME} main.cpp) target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex Eigen3::Eigen) + target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Eigen3::Eigen) diff --git a/python/src/core/CMakeLists.txt b/python/src/core/CMakeLists.txt index 9d83a210d..3507d6d5c 100644 --- a/python/src/core/CMakeLists.txt +++ b/python/src/core/CMakeLists.txt @@ -157,7 +157,7 @@ ) target_link_libraries(_core - PRIVATE ${PROJECT_NAME}-core ${PROJECT_NAME}-sympy ${LIBS} Ibex::ibex + PRIVATE ${PROJECT_NAME}-core ${PROJECT_NAME}-sympy ${LIBS} Codac::gaol ) # Copy the generated library in the package folder diff --git a/python/src/graphics/CMakeLists.txt b/python/src/graphics/CMakeLists.txt index 7b180a814..0b4386d75 100644 --- a/python/src/graphics/CMakeLists.txt +++ b/python/src/graphics/CMakeLists.txt @@ -28,7 +28,7 @@ ) target_link_libraries(_graphics - PRIVATE ${PROJECT_NAME}-graphics ${LIBS} Ibex::ibex + PRIVATE ${PROJECT_NAME}-graphics ${LIBS} Codac::gaol ) # Copy the generated library in the package folder diff --git a/python/src/unsupported/CMakeLists.txt b/python/src/unsupported/CMakeLists.txt index a413804dd..861175754 100644 --- a/python/src/unsupported/CMakeLists.txt +++ b/python/src/unsupported/CMakeLists.txt @@ -16,7 +16,7 @@ ) target_link_libraries(_unsupported - PRIVATE ${PROJECT_NAME}-unsupported ${LIBS} Ibex::ibex + PRIVATE ${PROJECT_NAME}-unsupported ${LIBS} Codac::gaol ) # Copy the generated library in the package folder diff --git a/scripts/CMakeModules/FindGAOL.cmake b/scripts/CMakeModules/FindGAOL.cmake new file mode 100644 index 000000000..a8f44ae50 --- /dev/null +++ b/scripts/CMakeModules/FindGAOL.cmake @@ -0,0 +1,208 @@ +# ================================================================== +# Codac - cmake module looking for an installed GAOL +# ================================================================== +# +# Looks for GAOL, the interval arithmetic library Codac is built upon +# (Frederic Goualard, https://frederic.goualard.net, +# https://github.com/goualard-f/GAOL), and for mathlib, the IBM Accurate +# Portable Mathematical Library GAOL computes its elementary functions with, +# which installs itself as libultim. +# +# Where to look, when GAOL is not installed in a directory CMake searches by +# default, is given by two cache entries -- or by CMAKE_PREFIX_PATH: +# +# GAOL_DIR prefix holding include/gaol/gaol.h and lib/ +# MATHLIB_DIR prefix holding include/MathLib.h and lib/; +# GAOL_DIR is searched for mathlib as well, the two being +# usually installed together +# +# What is found is written to the cache entries GAOL_INCDIR, GAOL_LIB, +# MATHLIB_INCDIR and MATHLIB_LIB, which can also be set by hand to bypass the +# search, and summarised in: +# +# GAOL_FOUND +# GAOL_VERSION when gaol/gaol_configuration.h states it +# GAOL_INCLUDE_DIRS +# GAOL_LIBRARIES gaol, then ultim, in link order +# +# No target is defined here. codac_gaol_find() (scripts/CMakeModules/codac_gaol.cmake) +# makes Codac::gaol out of these variables, with the flags of interval +# arithmetic it determines itself, for a GAOL that has neither a CMake package +# nor a gaol.pc, which it looks for first. +# +# Origin +# ------ +# This is the search IBEX runs before building a GAOL of its own, taken out of +# IBEX (https://github.com/ibex-team/ibex-lib, GNU LGPL v3) so that Codac can +# find GAOL without IBEX: +# +# - codac_find_header_custom() and codac_find_library_custom() are the +# functions find_header_custom() and find_library_custom() of IBEX's +# cmake.utils/IbexUtils.cmake, written by Cyril Bouvier for the CMake build +# of IBEX. They are renamed with a codac_ prefix so as not to clash with +# IBEX's own definitions in a project that loads both, and differ in +# two respects: they stay silent when find_package(GAOL QUIET) asks for it, +# and a header found on the default search path is returned as it is, +# rather than wrapped in $ -- that wrapping is how IBEX +# keeps such a directory out of the export files it generates, and Codac +# writes its configuration file by other means (see src/CMakeLists.txt). +# +# - The four calls below, the GAOL_DIR and MATHLIB_DIR hints and the names of +# the result variables are those of IBEX's +# interval_lib_wrapper/gaol/CMakeLists.txt, by Cyril Bouvier as well. +# +# - Reading the version out of gaol/gaol_configuration.h comes from +# interval_lib_wrapper/gaol/FindGaol.cmake of IBEX (Cyril Bouvier). It +# reads the GAOL_MAJOR_VERSION, GAOL_MINOR_VERSION and GAOL_MICRO_VERSION +# macros here, which both the autotools and the CMake builds of GAOL write. + + +################################################################################ +# Functions from IBEX (see above) +################################################################################ + + function(codac_find_header_custom prefix hdrname) + set(opt "") + set(oneArgs "") + set(multiArgs PATHS) + + cmake_parse_arguments(FHC "${opt}" "${oneArgs}" "${multiArgs}" ${ARGN}) + + if(FHC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to codac_find_header_custom(): \"${FHC_UNPARSED_ARGUMENTS}\"") + endif() + + set(MSG "Looking for ${hdrname}") + if(NOT GAOL_FIND_QUIETLY) + message(STATUS "${MSG}") + endif() + + # First look only in PATHS if given + if(FHC_PATHS) + find_path(${prefix}_INCDIR ${hdrname} PATHS ${FHC_PATHS} + DOC "Set to exact include directory to bypass internal test" + PATH_SUFFIXES include NO_DEFAULT_PATH) + endif() + + if(NOT ${prefix}_INCDIR) + # Now look with system and cmake paths + find_path(${prefix}_INCDIR ${hdrname} + DOC "Set to exact include directory to bypass internal test" + PATH_SUFFIXES include) + endif() + + if(NOT GAOL_FIND_QUIETLY) + if(${prefix}_INCDIR) + message(STATUS "${MSG} -- found at ${${prefix}_INCDIR}") + else() + message(STATUS "${MSG} -- not found") + endif() + endif() + + set(${prefix}_INCDIR ${${prefix}_INCDIR} PARENT_SCOPE) + mark_as_advanced(${prefix}_INCDIR) + endfunction() + + + function(codac_find_library_custom prefix libname) + set(opt "") + set(oneArgs "") + set(multiArgs PATHS) + + cmake_parse_arguments(FLC "${opt}" "${oneArgs}" "${multiArgs}" ${ARGN}) + + if(FLC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to codac_find_library_custom(): \"${FLC_UNPARSED_ARGUMENTS}\"") + endif() + + set(MSG "Looking for ${libname}") + if(NOT GAOL_FIND_QUIETLY) + message(STATUS "${MSG}") + endif() + + # First look only in PATHS if given + if(FLC_PATHS) + find_library(${prefix}_LIB ${libname} PATHS ${FLC_PATHS} + DOC "Set to exact lib directory to bypass internal test" + PATH_SUFFIXES lib NO_DEFAULT_PATH) + endif() + + if(NOT ${prefix}_LIB) + # Now look with system and cmake paths + find_library(${prefix}_LIB ${libname} + DOC "Set to exact lib directory to bypass internal test" + PATH_SUFFIXES lib) + endif() + + if(NOT GAOL_FIND_QUIETLY) + if(${prefix}_LIB) + message(STATUS "${MSG} -- found at ${${prefix}_LIB}") + else() + message(STATUS "${MSG} -- not found") + endif() + endif() + + set(${prefix}_LIB ${${prefix}_LIB} PARENT_SCOPE) + mark_as_advanced(${prefix}_LIB) + endfunction() + + +################################################################################ +# Options +################################################################################ + + set(MATHLIB_DIR "" CACHE PATH "Path to the Mathlib/ultim lib and include directories") + set(GAOL_DIR "" CACHE PATH "Path to the Gaol lib and include directories") + + +################################################################################ +# Looking for Mathlib/libultim +################################################################################ + + # Looking for MathLib.h, result is written in MATHLIB_INCDIR + codac_find_header_custom(MATHLIB "MathLib.h" PATHS "${MATHLIB_DIR}" "${GAOL_DIR}") + # Looking for ultim library, result is written in MATHLIB_LIB + codac_find_library_custom(MATHLIB "ultim" PATHS "${MATHLIB_DIR}" "${GAOL_DIR}") + + +################################################################################ +# Looking for Gaol +################################################################################ + + # Looking for gaol/gaol.h, result is written in GAOL_INCDIR + codac_find_header_custom(GAOL "gaol/gaol.h" PATHS "${GAOL_DIR}") + # Looking for gaol library, result is written in GAOL_LIB + codac_find_library_custom(GAOL "gaol" PATHS "${GAOL_DIR}") + + set(GAOL_VERSION "") + if(GAOL_INCDIR AND EXISTS "${GAOL_INCDIR}/gaol/gaol_configuration.h") + file(STRINGS "${GAOL_INCDIR}/gaol/gaol_configuration.h" _gaol_version_lines + REGEX "^#define GAOL_(MAJOR|MINOR|MICRO)_VERSION[ \t]+[0-9]+") + set(_gaol_version_parts "") + foreach(_gaol_part MAJOR MINOR MICRO) + string(REGEX MATCH "GAOL_${_gaol_part}_VERSION[ \t]+([0-9]+)" _gaol_match "${_gaol_version_lines}") + if(_gaol_match) + list(APPEND _gaol_version_parts ${CMAKE_MATCH_1}) + endif() + endforeach() + list(LENGTH _gaol_version_parts _gaol_version_length) + if(_gaol_version_length EQUAL 3) + string(REPLACE ";" "." GAOL_VERSION "${_gaol_version_parts}") + endif() + endif() + + +################################################################################ +# Result +################################################################################ + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(GAOL + REQUIRED_VARS GAOL_LIB GAOL_INCDIR MATHLIB_LIB MATHLIB_INCDIR + VERSION_VAR GAOL_VERSION) + + if(GAOL_FOUND) + set(GAOL_INCLUDE_DIRS ${GAOL_INCDIR} ${MATHLIB_INCDIR}) + list(REMOVE_DUPLICATES GAOL_INCLUDE_DIRS) + set(GAOL_LIBRARIES ${GAOL_LIB} ${MATHLIB_LIB}) + endif() diff --git a/scripts/CMakeModules/codac_gaol.cmake b/scripts/CMakeModules/codac_gaol.cmake new file mode 100644 index 000000000..631b63c8d --- /dev/null +++ b/scripts/CMakeModules/codac_gaol.cmake @@ -0,0 +1,799 @@ +# ================================================================== +# Codac - cmake module for GAOL, the interval arithmetic library +# ================================================================== +# +# codac2::Interval derives from gaol::interval (see +# src/core/domains/interval/codac2_Interval.h). GAOL is written by Frederic +# Goualard (https://frederic.goualard.net, https://github.com/goualard-f/GAOL) +# and distributed under the GNU LGPL v2. It computes its elementary functions +# with mathlib, the IBM Accurate Portable Mathematical Library (libultim), which +# Frederic Goualard distributes along with it, under the GNU GPL v2 or later. +# +# Codac builds the GAOL of Jordan Ninin's fork, https://github.com/Jordan08/GAOL, +# which adds to GAOL a CMake build, the fixes Codac depends on (those of the +# patch IBEX applies to GAOL, and those Visual C++, MinGW and ARM processors +# need) and tests of its bounds. Fixes of GAOL go into the fork rather than into +# Codac. The fork's CMake build downloads mathlib from Frederic Goualard's site, +# and refuses the compilers that do not compute GAOL's intervals right (see its +# README.md). +# +# GAOL's interval operations are inline: the code including its headers, Codac's +# and that of Codac's users, has to be compiled with the flags of interval +# arithmetic, and linked with GAOL and mathlib. Which flags and which libraries +# is GAOL's to say, and codac_gaol_find() takes them from the GAOL it finds or +# builds, in this order: +# +# 1. the CMake package of GAOL, which the CMake build of the fork installs: +# find_package(gaol CONFIG) gives gaol::gaol, which carries the include +# directory, the flags (PUBLIC), __GAOL_PUBLIC__= for Visual C++, and +# mathlib (gaol::ultim); +# 2. pkg-config: the gaol.pc the autotools and meson builds of the fork install +# carries the flags in its Cflags, and GAOL and mathlib in its Libs; +# 3. the files themselves (FindGAOL.cmake), for a GAOL installed with neither, +# whose flags Codac then determines itself (codac_gaol_interval_flags()); +# 4. when none is found, or with ENABLE_FIND_PACKAGE_GAOL OFF, codac_gaol_build() +# builds the fork with its CMake build and installs it with its installer, +# and find_package(gaol CONFIG) takes the package it installed, as in 1. +# +# In 1 to 3, a GAOL older than CODAC_GAOL_MIN_VERSION (see below), or whose +# version cannot be told, is passed over, with a message saying so, and the next +# way is tried, down to building the fork. In 4, a GAOL built too old stops the +# configuration. +# +# All four end in Codac::gaol, the imported target the Codac libraries link +# PUBLIC, and which codac-config.cmake defines again for their users +# (codac_gaol_config_snippet()). +# +# Origin +# ------ +# What follows comes from IBEX (https://github.com/ibex-team/ibex-lib, GNU +# LGPL v3): +# +# - codac_gaol_portability_flags() and codac_gaol_interval_flags() are, divided +# in two, the part of ibex_init_common() (cmake.utils/ibex-config-utils.cmake) +# that sets the floating-point flags, in the version of the IBEX fork +# maintained by Fabrice Le Bars, https://github.com/lebarsfa/ibex-lib, tag +# ibex-2.8.9.20260819, which Codac was built against until now. +# ibex_init_common() was written by Cyril Bouvier for the CMake build of IBEX; +# the flags it sets -- -frounding-math and the others that IEEE 754 double +# support depends on, and their Visual Studio counterparts -- were added to it +# by Fabrice Le Bars. The condition under which -ffloat-store is added is the +# one of the CMake build of the fork of GAOL (its CMakeLists.txt). +# +# - codac_gaol_build() is the "not found, install it" branch of +# interval_lib_wrapper/gaol/CMakeLists.txt, written by Cyril Bouvier: build +# mathlib and GAOL, and install the result next to the library it serves, as +# IBEX does in include/ibex/3rd and lib/ibex/3rd. The CMake build of GAOL and +# mathlib it runs is the fork's, which says where it comes from: from IBEX +# (Cyril Bouvier, Gilles Chabert), with the portability fixes for Visual +# C++, MinGW and ARM of the forks of GAOL and mathlib by Fabrice Le Bars. +# +# - codac_gaol_config_snippet() does, for Codac, what +# create_target_import_and_export() of cmake.utils/IbexUtils.cmake (Cyril +# Bouvier) does for IBEX: the lines that define the imported target of GAOL +# again for a consumer of the installed library. +# +# What differs from IBEX +# ---------------------- +# IBEX extracts GAOL and mathlib from archives kept in its own repository, and +# builds them as a part of itself (add_subdirectory()). Here GAOL is downloaded +# from the fork, at the head of its master branch, and built as a project of its +# own when Codac is configured, for the reasons given at codac_gaol_build(); the +# fork's build downloads mathlib, checked against the SHA256 of its archive. The +# other differences are explained where they occur. + + +# Where a GAOL built by codac_gaol_build() is installed, under the installation +# prefix. IBEX keeps the libraries it builds for itself in include/ibex/3rd and +# lib/ibex/3rd, out of the way of a GAOL installed separately; include/codac +# cannot play that part here, being the name of Codac's umbrella header. GAOL's +# CMake package goes in lib/codac-3rd/cmake/gaol, where the search of +# find_package() does not look: only codac-config.cmake finds it there. +set(CODAC_INSTALL_INCLUDEDIR_3RD "${CMAKE_INSTALL_INCLUDEDIR}/codac-3rd") +set(CODAC_INSTALL_LIBDIR_3RD "${CMAKE_INSTALL_LIBDIR}/codac-3rd") + +# Where codac_gaol_build() downloads, builds and installs GAOL in the build tree +set(CODAC_GAOL_WORK_DIR "${CMAKE_BINARY_DIR}/_deps/gaol") + +# The oldest GAOL Codac accepts: version 4.3.2 of the fork, the first one with all +# the fixes Codac relies on. Among them, the last one: the intersection of disjoint +# intervals is the canonical empty set [NaN, NaN], where GAOL gave reversed bounds, +# [3, 2] for [1, 2] & [3, 4], which is_empty() took for empty but the operations +# computing on the bounds did not ([3, 2] + [0, 1] gave [3, 3]). With an older +# GAOL, the results of Codac could be wrong without any error. The requirement +# holds for the GAOL Codac finds or builds (codac_gaol_find()), and for the users +# of the installed Codac too (codac_gaol_config_snippet()): Codac's interval +# operations are inline in its headers, so they are compiled against the GAOL of +# the program that includes them. The CMake package of the fork declares its +# version compatible with a requested one of the same major version +# (SameMajorVersion): find_package(gaol 4.3.2) accepts 4.3.2 up to, excluding, 5. +set(CODAC_GAOL_MIN_VERSION 4.3.2) + + +################################################################################ +# codac_gaol_portability_flags() +################################################################################ +# +# Returns in the flags of ibex_init_common() (see the top of this file) +# that are not those of interval arithmetic, and that Codac keeps whatever GAOL +# says: the top-level CMakeLists.txt puts them in CMAKE_CXX_FLAGS, where +# ibex_init_common() put them. They are returned as a list, one flag per +# element, because Codac also hands them over to its consumers, through +# CODAC_CXX_FLAGS and codac.pc; for the same reason, the Visual Studio "/D NAME" +# pairs of IBEX are spelt "/DNAME", which target_compile_options() cannot +# mistake for two duplicated "/D" and merge. The rest of ibex_init_common() is +# left out: the installation directories, the build type, the C++ standard and +# the uninstall target are Codac's own decisions, and the Debug-only -Wall +# -DDEBUG (/D DEBUG) have no use here, Codac choosing its own warnings and none +# of its code reading DEBUG. +# +# The two flags IBEX added for filib, which is not GAOL, are kept all the same, +# so that Codac goes on being compiled with the flags it had. +function(codac_gaol_portability_flags outvar) + + include(CheckCXXCompilerFlag) + set(flags "") + + if(MSVC) + list(APPEND flags /D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_WARNINGS /Zc:__cplusplus /Zc:strictStrings-) + elseif(APPLE) + # Due to warnings on macOS with filib + check_cxx_compiler_flag("-Wno-undefined-var-template" COMPILER_SUPPORTS_WNO_UNDEFINED_VAR_TEMPLATE) + if(COMPILER_SUPPORTS_WNO_UNDEFINED_VAR_TEMPLATE) + list(APPEND flags -Wno-undefined-var-template) + endif() + endif() + # Claim IEEE 754 double compatibility, for filib + list(APPEND flags -D__STDC_IEC_559__=1) + + set(${outvar} ${flags} PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_interval_flags() +################################################################################ +# +# Returns in the compilation flags interval arithmetic depends on, for +# a GAOL found by its files alone, without a CMake package or a gaol.pc to say +# them (see codac_gaol_find()). Without -frounding-math and its companions, the +# compiler is free to evaluate a floating-point expression at compile time in +# the default rounding mode, or to contract it, and a bound computed that way no +# longer encloses anything. +# +# From ibex_init_common() (see the top of this file), which appends the same +# flags to CMAKE_C_FLAGS and CMAKE_CXX_FLAGS, but for -ffloat-store, added only +# where the builds of GAOL add it. +function(codac_gaol_interval_flags outvar) + + include(CheckCXXCompilerFlag) + include(CheckCXXSourceCompiles) + set(flags "") + + if(MSVC) + list(APPEND flags /fp:strict) + else() + # Each flag is kept only where the compiler takes it. The check results are + # cached under the names ibex_init_common() gives them, e.g. + # COMPILER_SUPPORTS_FROUNDING_MATH for -frounding-math. + foreach(flag -frounding-math -fno-fast-math -ffp-contract=off + -ffp-mode=full -fp-model=strict -fp:strict -mpc64) + string(MAKE_C_IDENTIFIER "${flag}" _flag_id) + string(TOUPPER "COMPILER_SUPPORTS${_flag_id}" _flag_var) + check_cxx_compiler_flag("${flag}" ${_flag_var}) + if(${_flag_var}) + list(APPEND flags ${flag}) + endif() + endforeach() + + # Not from ibex_init_common(), but Codac's own: on a 32-bit x86 processor, + # doubles are computed in SSE2 rather than on the x87 FPU. Visual Studio, + # in the branch above, computes them in SSE2 already. + # + # Computed on the x87, GAOL's bounds and mathlib's results are only right + # while the precision of the x87 is set to 53 bits, and nothing keeps it + # so: mathlib's Init_Lib() sets it where mathlib has a version for 32-bit x86 + # (see cmake/mathlib/mathlib_configuration.h.in in the fork of GAOL), but + # GAOL, initialised right after, restores the default floating-point + # environment (gaol::init()), whose precision is 64 bits on Linux and + # with MinGW. Built for an i686 computing on the x87 (Clang 21 with + # -mcpu=i686), GAOL returned [1.99975, 1.99975] for exp([1,1]), and the + # bounds of exp, sin and cos missed the exact value for 4000, 3302 and 3913 + # of 4000 random arguments; built with the two flags below, the same + # program gave all of its 48000 bounds bit for bit as on x86_64. In SSE2, + # the precision is not a setting. SSE2 asks nothing more of the processor + # than GAOL's own builds do: they compile GAOL for SSE2 on these systems. + check_cxx_source_compiles(" + #if !defined(__i386__) + #error not a 32-bit x86 target + #endif + int main() { return 0; }" + CODAC_TARGET_IS_X86_32) + if(CODAC_TARGET_IS_X86_32) + check_cxx_compiler_flag("-msse2 -mfpmath=sse" COMPILER_SUPPORTS_MSSE2_MFPMATH_SSE) + if(COMPILER_SUPPORTS_MSSE2_MFPMATH_SSE) + list(APPEND flags -msse2 -mfpmath=sse) + endif() + endif() + + # -ffloat-store only where doubles are still computed in extended precision + # with the flags above (FLT_EVAL_METHOD not 0: a 32-bit x86 target computing + # on the x87), whose 80-bit registers keep more digits than a double: the + # condition under which the CMake, autotools and meson builds of the fork of + # GAOL add it. ibex_init_common() added it wherever the compiler took it. + # Where doubles are computed in double precision (SSE2, ARM and the other + # processors), it brings nothing to the bounds, and makes GCC store every + # floating-point variable in memory rather than in a register: with it, on + # x86_64 with GCC 9.4, x + y took 9.1 ns rather than 3.0, and sqrt(x) 41 ns + # rather than 8.2. + string(REPLACE ";" " " CMAKE_REQUIRED_FLAGS "${flags}") + check_cxx_source_compiles(" + #include + #if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD == 0 + #error doubles are computed in double precision + #endif + int main() { return 0; }" + CODAC_DOUBLES_IN_EXTENDED_PRECISION) + unset(CMAKE_REQUIRED_FLAGS) + if(CODAC_DOUBLES_IN_EXTENDED_PRECISION) + check_cxx_compiler_flag(-ffloat-store COMPILER_SUPPORTS_FFLOAT_STORE) + if(COMPILER_SUPPORTS_FFLOAT_STORE) + list(APPEND flags -ffloat-store) + endif() + endif() + + # Not from ibex_init_common() either: a warning when the compiler takes + # -frounding-math but says it does not honour the rounding direction on the + # target, as Clang does for 32-bit ARM processors ("overriding currently + # unsupported rounding mode on this target"). It then optimises the + # negations by which GAOL rounds downward with the rounding direction set + # upward, and no flag or change to GAOL can prevent it. Built by Clang 21 for + # 32-bit ARM, 4556 of 16000 random products, squares and cubes computed by + # GAOL did not enclose their exact value; built by GCC 15, none. The builds + # of the fork of GAOL refuse such a compiler, and so do its headers; the + # warning remains for a GAOL found without its CMake package or its gaol.pc. + if(COMPILER_SUPPORTS_FROUNDING_MATH) + set(CMAKE_REQUIRED_FLAGS "-frounding-math") + check_cxx_source_compiles("int main() { return 0; }" CODAC_COMPILER_HONOURS_ROUNDING_MATH + FAIL_REGEX "unsupported rounding mode") + unset(CMAKE_REQUIRED_FLAGS) + if(NOT CODAC_COMPILER_HONOURS_ROUNDING_MATH) + message(WARNING "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} does not honour the rounding " + "direction on this target (-frounding-math): the intervals computed by Codac may not " + "enclose the values they should. Use a compiler that does, such as GCC.") + endif() + endif() + endif() + + set(${outvar} ${flags} PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_step( ...) +################################################################################ +# +# Runs one step of codac_gaol_build(), its output written to , and +# stops the configuration with that output when the step fails, as +# LOG_OUTPUT_ON_FAILURE does for an ExternalProject. +function(codac_gaol_step description log) + execute_process(COMMAND ${ARGN} + OUTPUT_FILE "${log}" ERROR_FILE "${log}" + RESULT_VARIABLE result) + if(NOT result EQUAL 0) + file(READ "${log}" output) + message(FATAL_ERROR "GAOL: ${description} failed (${result}). Its output, in ${log}:\n${output}") + endif() +endfunction() + + +################################################################################ +# codac_gaol_build() +################################################################################ +# +# Downloads GAOL, builds it and mathlib in Release with the CMake build of the +# fork, and installs them in the build tree with its installer, all while Codac +# is configured; sets CODAC_GAOL_INSTALL_TREE, where they are installed, in the +# caller's scope, for codac_gaol_find() to find the CMake package of GAOL there. +# GAOL and mathlib are also installed with Codac, by the same installer, in +# CODAC_INSTALL_INCLUDEDIR_3RD and CODAC_INSTALL_LIBDIR_3RD, since the Codac +# libraries are of no use without them. +# +# While Codac is configured, and not while it is built, as with an +# ExternalProject: the flags and the libraries of GAOL are read from the CMake +# package it installs, which has to exist for find_package() to read it. +# +# A project of its own, as in IBEX's build of GAOL with its autotools, rather +# than the FetchContent that Eigen and Catch2 are brought in with, which would +# build GAOL as a part of this project. Kept apart, GAOL is compiled with the +# flags it chooses, and not with Codac's warnings, and it is always built in +# Release, whatever the configuration of Codac, which is what the MSVC runtime +# choice of the top-level CMakeLists.txt counts on. CMAKE_CXX_FLAGS and +# CMAKE_C_FLAGS are handed over as they are when this is called, before Codac +# adds its own flags to them. +# +# GAOL comes from the head of the master branch of the fork (version 4.3.1 of +# GAOL), so that the fixes pushed to the fork reach Codac without a change here. +# Cloned with Git, the sources are brought up to date with the branch at each +# configuration of Codac, which needs network access (without it, the sources +# already downloaded are built, with a warning), and only what a new commit +# changes is compiled again. Without Git, the archive GitHub makes of the +# branch is downloaded instead, only once per build directory: a new commit +# reaches such a build once _deps/gaol is deleted from it. The fork's CMake +# build downloads mathlib 2.1.1 from Frederic Goualard's site, checks its +# checksum, and builds and installs it along with GAOL. +function(codac_gaol_build) + + set(_work "${CODAC_GAOL_WORK_DIR}") + set(_source "${_work}/src") + set(_binary "${_work}/build") + set(_install "${_work}/install") + file(MAKE_DIRECTORY "${_work}") + + message(STATUS "GAOL: downloading the fork of GAOL, building it and installing it in ${_work}") + + # The sources + find_package(Git QUIET) + if(GIT_FOUND) + if(NOT EXISTS "${_source}/.git") + file(REMOVE_RECURSE "${_source}") + # The files as they are in the repository, whatever core.autocrlf says + # (true on the Windows runners of GitHub Actions). + codac_gaol_step("cloning https://github.com/Jordan08/GAOL.git" "${_work}/download.log" + "${GIT_EXECUTABLE}" clone --depth 1 --branch master --config core.autocrlf=false + https://github.com/Jordan08/GAOL.git "${_source}") + else() + execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${_source}" fetch --depth 1 origin master + OUTPUT_FILE "${_work}/update.log" ERROR_FILE "${_work}/update.log" + RESULT_VARIABLE _fetch_result) + if(_fetch_result EQUAL 0) + codac_gaol_step("updating the sources with the master branch" "${_work}/update.log" + "${GIT_EXECUTABLE}" -C "${_source}" reset --hard FETCH_HEAD) + else() + message(WARNING "GAOL could not be brought up to date with the master branch of " + "https://github.com/Jordan08/GAOL.git (see ${_work}/update.log): " + "the sources downloaded before are built.") + endif() + endif() + elseif(NOT EXISTS "${_source}/CMakeLists.txt") + file(DOWNLOAD https://github.com/Jordan08/GAOL/archive/refs/heads/master.zip "${_work}/GAOL-master.zip" + STATUS _download_status LOG _download_log) + list(GET _download_status 0 _download_code) + if(NOT _download_code EQUAL 0) + message(FATAL_ERROR "GAOL: downloading https://github.com/Jordan08/GAOL/archive/refs/heads/master.zip " + "failed (${_download_status}):\n${_download_log}") + endif() + file(REMOVE_RECURSE "${_work}/GAOL-master" "${_source}") + codac_gaol_step("extracting GAOL-master.zip" "${_work}/download.log" + "${CMAKE_COMMAND}" -E chdir "${_work}" "${CMAKE_COMMAND}" -E tar xf GAOL-master.zip) + file(RENAME "${_work}/GAOL-master" "${_source}") + endif() + + # The configuration of GAOL, written as an initial cache (-C) rather than on + # the command line, where the ";" of a list such as CMAKE_OSX_ARCHITECTURES + # would split an argument in two. FORCE, so that a value changed since the + # last configuration of Codac replaces the one in GAOL's cache. + # + # Every value below has to come out the same at each configuration of Codac + # that changes nothing: GAOL is configured again as soon as one of them + # differs, and everything in Codac that includes an interval may then be + # recompiled. This is why the top-level CMakeLists.txt enables C in project() + # rather than leaving it to a dependency, which changed CMAKE_C_COMPILER + # between the first configuration and the second. + set(_cache "") + macro(codac_gaol_cache_entry name type value) + string(APPEND _cache "set(${name} [==[${value}]==] CACHE ${type} \"\" FORCE)\n") + endmacro() + + codac_gaol_cache_entry(CMAKE_BUILD_TYPE STRING Release) + # Codac's Python modules link these archives into shared libraries. + codac_gaol_cache_entry(CMAKE_POSITION_INDEPENDENT_CODE BOOL ON) + # Where Codac is installed, which the gaol.pc GAOL installs with Codac names; + # the installation in the build tree gives its own prefix. + codac_gaol_cache_entry(CMAKE_INSTALL_PREFIX PATH "${CMAKE_INSTALL_PREFIX}") + codac_gaol_cache_entry(CMAKE_INSTALL_INCLUDEDIR PATH "${CODAC_INSTALL_INCLUDEDIR_3RD}") + codac_gaol_cache_entry(CMAKE_INSTALL_LIBDIR PATH "${CODAC_INSTALL_LIBDIR_3RD}") + # The fork's continuous integration runs its tests + codac_gaol_cache_entry(GAOL_BUILD_TESTS BOOL OFF) + # The mathlib the fork downloads and builds, never one installed on this + # machine: the installation of Codac counts on it + codac_gaol_cache_entry(GAOL_FIND_MATHLIB BOOL OFF) + # Without the fused multiply-add instructions of the processor, whose flags + # would reach every target linking GAOL: a library for any processor of the + # architecture + codac_gaol_cache_entry(GAOL_FMA BOOL OFF) + codac_gaol_cache_entry(CMAKE_CXX_FLAGS STRING "${CMAKE_CXX_FLAGS}") + codac_gaol_cache_entry(CMAKE_C_FLAGS STRING "${CMAKE_C_FLAGS}") + + # The generator is handed over below, with its platform (-A) and toolset (-T), + # but nothing else is: the compilers, the flags and the target have to be + # given explicitly, or GAOL would be built for another machine than the one + # Codac is built for. The Visual Studio and Xcode generators take their + # compilers from the toolset and ignore these. + if(NOT CMAKE_GENERATOR MATCHES "Visual Studio|Xcode") + codac_gaol_cache_entry(CMAKE_CXX_COMPILER FILEPATH "${CMAKE_CXX_COMPILER}") + codac_gaol_cache_entry(CMAKE_C_COMPILER FILEPATH "${CMAKE_C_COMPILER}") + codac_gaol_cache_entry(CMAKE_MAKE_PROGRAM FILEPATH "${CMAKE_MAKE_PROGRAM}") + endif() + foreach(_var CMAKE_TOOLCHAIN_FILE CMAKE_MSVC_RUNTIME_LIBRARY CMAKE_GENERATOR_INSTANCE + CMAKE_OSX_DEPLOYMENT_TARGET CMAKE_OSX_SYSROOT CMAKE_OSX_ARCHITECTURES) + # Quoted: a list, as CMAKE_OSX_ARCHITECTURES can be, would give if() as many + # arguments as elements + if(NOT "${${_var}}" STREQUAL "") + codac_gaol_cache_entry(${_var} STRING "${${_var}}") + endif() + endforeach() + # Only when this build is itself a cross-compilation (as the macOS jobs of + # .github/workflows are, by setting CMAKE_SYSTEM_NAME explicitly): passing + # the host's own name would make GAOL's build believe it cross-compiles. + if(CMAKE_CROSSCOMPILING) + codac_gaol_cache_entry(CMAKE_SYSTEM_NAME STRING "${CMAKE_SYSTEM_NAME}") + if(CMAKE_SYSTEM_PROCESSOR) + codac_gaol_cache_entry(CMAKE_SYSTEM_PROCESSOR STRING "${CMAKE_SYSTEM_PROCESSOR}") + endif() + endif() + file(WRITE "${_work}/initial-cache.cmake" "${_cache}") + + set(_generator -G "${CMAKE_GENERATOR}") + if(CMAKE_GENERATOR_PLATFORM) + list(APPEND _generator -A "${CMAKE_GENERATOR_PLATFORM}") + endif() + if(CMAKE_GENERATOR_TOOLSET) + list(APPEND _generator -T "${CMAKE_GENERATOR_TOOLSET}") + endif() + + codac_gaol_step("configuring" "${_work}/configure.log" + "${CMAKE_COMMAND}" ${_generator} -C "${_work}/initial-cache.cmake" -S "${_source}" -B "${_binary}") + # Release whatever the configuration of Codac: --config is what a + # multi-configuration generator reads, and what the others ignore. In + # parallel as CMAKE_BUILD_PARALLEL_LEVEL says, when it is set. + codac_gaol_step("building" "${_work}/build.log" + "${CMAKE_COMMAND}" --build "${_binary}" --config Release) + # The installer of GAOL's build (cmake_install.cmake, which cmake --install + # runs from CMake 3.15 on), with the prefix of the build tree + codac_gaol_step("installing" "${_work}/install.log" + "${CMAKE_COMMAND}" "-DCMAKE_INSTALL_PREFIX=${_install}" -DCMAKE_INSTALL_CONFIG_NAME=Release + -P "${_binary}/cmake_install.cmake") + + # And with Codac, by the same installer, with the prefix Codac is installed + # under, as the installation of Codac gives it (cmake --install --prefix, + # CPack), and into DESTDIR when it is set, which it inherits. + install(CODE " + # GAOL and mathlib, installed by the installer of their CMake build (see + # codac_gaol_build() in scripts/CMakeModules/codac_gaol.cmake) + execute_process(COMMAND \"${CMAKE_COMMAND}\" \"-DCMAKE_INSTALL_PREFIX=\${CMAKE_INSTALL_PREFIX}\" + -DCMAKE_INSTALL_CONFIG_NAME=Release -P \"${_binary}/cmake_install.cmake\" + RESULT_VARIABLE _codac_gaol_install_result) + if(NOT _codac_gaol_install_result EQUAL 0) + message(FATAL_ERROR \"The installation of GAOL failed (\${_codac_gaol_install_result})\") + endif() + ") + + set(CODAC_GAOL_INSTALL_TREE "${_install}" PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_find() +################################################################################ +# +# Finds GAOL in the order given at the top of this file, or builds it, and +# defines Codac::gaol, the imported target through which the Codac libraries, +# and the targets linking them, get GAOL's include directories, flags and +# libraries. Sets in the caller's scope: +# +# CODAC_GAOL_FROM "package", "pkg-config" or "files" +# CODAC_GAOL_BUILT_HERE TRUE when codac_gaol_build() built it +# CODAC_GAOL_INSTALL_TREE where codac_gaol_build() installed it +# GAOL_VERSION +# +# An INTERFACE library rather than GAOL's own target, whatever provided GAOL: +# CODAC_LIBRARIES names it, and codac-config.cmake defines it again +# (codac_gaol_config_snippet()), which it could not do under the name gaol::gaol +# without clashing with a find_package(gaol) of the consumer. +function(codac_gaol_find) + + include(CheckCXXCompilerFlag) + set(_from "") + set(_built_here FALSE) + set(_version "") + + if(ENABLE_FIND_PACKAGE_GAOL) + + # 1. The CMake package of GAOL (gaol_DIR, or CMAKE_PREFIX_PATH). A gaol_DIR + # left by an earlier configuration of this build, naming the GAOL + # codac_gaol_build() installed in it, is not a GAOL of this machine: it is + # forgotten, and that GAOL is built again, up to date, if none is found. + string(FIND "${gaol_DIR}" "${CODAC_GAOL_WORK_DIR}/" _in_work_dir) + if(_in_work_dir EQUAL 0) + unset(gaol_DIR CACHE) + endif() + # The version is checked by the package's gaolConfigVersion.cmake, before the + # package is loaded: an older one defines no target, which would clash with + # the gaol::gaol of the GAOL found or built next. + find_package(gaol ${CODAC_GAOL_MIN_VERSION} CONFIG QUIET) + if(gaol_FOUND) + set(_from package) + set(_version "${gaol_VERSION}") + message(STATUS "Found GAOL ${gaol_VERSION}, CMake package in ${gaol_DIR}") + elseif(gaol_CONSIDERED_VERSIONS) + message(STATUS "Found GAOL ${gaol_CONSIDERED_VERSIONS} (CMake package ${gaol_CONSIDERED_CONFIGS}), " + "older than ${CODAC_GAOL_MIN_VERSION}: not used") + endif() + + # 2. pkg-config (PKG_CONFIG_PATH, or CMAKE_PREFIX_PATH). Not with Visual C++, + # whose libraries pkg-config does not name. A gaol.pc whose Cflags lack + # -frounding-math, as the one the meson build of Frederic Goualard's GAOL + # installs, does not describe the flags GAOL needs, and is not used. + if(NOT _from AND NOT MSVC) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(CODAC_GAOL_PC QUIET IMPORTED_TARGET gaol) + if(CODAC_GAOL_PC_FOUND) + check_cxx_compiler_flag(-frounding-math COMPILER_SUPPORTS_FROUNDING_MATH) + if(NOT CODAC_GAOL_PC_VERSION OR CODAC_GAOL_PC_VERSION VERSION_LESS CODAC_GAOL_MIN_VERSION) + message(STATUS "Found gaol.pc ${CODAC_GAOL_PC_VERSION} in ${CODAC_GAOL_PC_PREFIX}, " + "older than ${CODAC_GAOL_MIN_VERSION}: not used") + elseif(COMPILER_SUPPORTS_FROUNDING_MATH AND NOT "-frounding-math" IN_LIST CODAC_GAOL_PC_CFLAGS_OTHER) + message(STATUS "Found gaol.pc in ${CODAC_GAOL_PC_PREFIX}, whose Cflags lack -frounding-math: not used") + else() + set(_from pkg-config) + set(_version "${CODAC_GAOL_PC_VERSION}") + message(STATUS "Found GAOL ${CODAC_GAOL_PC_VERSION}, gaol.pc in ${CODAC_GAOL_PC_PREFIX}") + endif() + endif() + endif() + endif() + + # 3. The files (GAOL_DIR and MATHLIB_DIR, or CMAKE_PREFIX_PATH) + if(NOT _from) + find_package(GAOL MODULE QUIET) + # FindGAOL.cmake reads the version in gaol/gaol_configuration.h; a GAOL that + # does not state it there is not known to be recent enough, and not used. + if(GAOL_FOUND AND (NOT GAOL_VERSION OR GAOL_VERSION VERSION_LESS CODAC_GAOL_MIN_VERSION)) + message(STATUS "Found GAOL ${GAOL_VERSION} in ${GAOL_INCDIR}, older than ${CODAC_GAOL_MIN_VERSION} " + "or of unknown version: not used") + elseif(GAOL_FOUND) + set(_from files) + set(_version "${GAOL_VERSION}") + message(STATUS "Found GAOL ${GAOL_VERSION} in ${GAOL_INCDIR}, without a CMake package or a gaol.pc: " + "compiled with the flags of interval arithmetic Codac determines") + endif() + endif() + endif() + + # 4. Built, and found as in 1 + if(NOT _from) + codac_gaol_build() + unset(gaol_DIR CACHE) + # The version is checked here too: without network access, codac_gaol_build() + # builds the sources it downloaded at an earlier configuration, which may be + # those of a version older than CODAC_GAOL_MIN_VERSION. Codac is then not + # configured, rather than configured on a GAOL it cannot trust. + find_package(gaol ${CODAC_GAOL_MIN_VERSION} CONFIG QUIET NO_DEFAULT_PATH + PATHS "${CODAC_GAOL_INSTALL_TREE}/${CODAC_INSTALL_LIBDIR_3RD}/cmake/gaol") + if(NOT gaol_FOUND AND gaol_CONSIDERED_VERSIONS) + message(FATAL_ERROR "The GAOL built in ${CODAC_GAOL_WORK_DIR} is version ${gaol_CONSIDERED_VERSIONS}, older " + "than ${CODAC_GAOL_MIN_VERSION}, the oldest Codac accepts: its sources could not be " + "brought up to date with the master branch of the fork (see the warning above). " + "Configure Codac again with network access (without Git, after deleting " + "${CODAC_GAOL_WORK_DIR}: the archive of the fork is only downloaded once per build " + "directory).") + elseif(NOT gaol_FOUND) + message(FATAL_ERROR "The CMake package of the GAOL built in ${CODAC_GAOL_WORK_DIR} was not found under " + "${CODAC_GAOL_INSTALL_TREE}/${CODAC_INSTALL_LIBDIR_3RD}/cmake/gaol.") + endif() + set(_from package) + set(_built_here TRUE) + set(_version "${gaol_VERSION}") + message(STATUS "GAOL ${gaol_VERSION} built and installed, CMake package in ${gaol_DIR}") + endif() + + add_library(Codac::gaol INTERFACE IMPORTED) + if(_from STREQUAL "package") + set_target_properties(Codac::gaol PROPERTIES INTERFACE_LINK_LIBRARIES gaol::gaol) + elseif(_from STREQUAL "pkg-config") + set_target_properties(Codac::gaol PROPERTIES INTERFACE_LINK_LIBRARIES PkgConfig::CODAC_GAOL_PC) + else() + codac_gaol_interval_flags(_interval_flags) + set_target_properties(Codac::gaol PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GAOL_INCLUDE_DIRS}" + INTERFACE_COMPILE_OPTIONS "${_interval_flags}" + INTERFACE_LINK_LIBRARIES "${GAOL_LIBRARIES}") + # For Visual C++, GAOL declares its classes and functions + # __declspec(dllimport), as for a DLL, unless __GAOL_PUBLIC__ is defined + # (gaol/gaol_config.h). None of GAOL's builds -- its autotools and meson + # builds, and the CMake build of the fork -- defines + # _COMPILING__GAOL_PUBLIC__, with which GAOL would export a DLL: the GAOL + # Visual C++ links is a static library, which every file including its + # headers has to be told. gaol::gaol, of the CMake package, tells it the + # same way. + if(MSVC) + set_target_properties(Codac::gaol PROPERTIES INTERFACE_COMPILE_DEFINITIONS "__GAOL_PUBLIC__=") + endif() + endif() + + set(CODAC_GAOL_FROM "${_from}" PARENT_SCOPE) + set(CODAC_GAOL_BUILT_HERE ${_built_here} PARENT_SCOPE) + set(CODAC_GAOL_INSTALL_TREE "${CODAC_GAOL_INSTALL_TREE}" PARENT_SCOPE) + set(GAOL_VERSION "${_version}" PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_usage( ) +################################################################################ +# +# Returns what Codac::gaol gives the targets linking it, walked out of it and of +# the targets it links: its include directories, its compilation flags (with +# its definitions, as -D or /D) and what goes on the link line (library files, +# and flags). This is for what cannot link Codac::gaol: CODAC_CXX_FLAGS, codac.pc, +# and codac-config.cmake when GAOL has no CMake package. Generator expressions +# are left out, but for $, which is unwrapped: GAOL's targets +# have no other. +function(codac_gaol_usage include_dirs_var flags_var link_var) + + set(include_dirs "") + set(flags "") + set(link "") + set(queue Codac::gaol) + set(seen "") + + while(queue) + list(GET queue 0 item) + list(REMOVE_AT queue 0) + if(item MATCHES "^\\$$") + set(item "${CMAKE_MATCH_1}") + endif() + if(item IN_LIST seen OR item MATCHES "\\$<") + continue() + endif() + list(APPEND seen "${item}") + + if(NOT TARGET "${item}") + # A library file, a flag, or the name of a library of the system + if(item MATCHES "^-" OR IS_ABSOLUTE "${item}") + list(APPEND link "${item}") + else() + list(APPEND link "-l${item}") + endif() + continue() + endif() + + get_target_property(_value "${item}" INTERFACE_INCLUDE_DIRECTORIES) + if(_value) + list(APPEND include_dirs ${_value}) + endif() + get_target_property(_value "${item}" INTERFACE_COMPILE_OPTIONS) + if(_value) + list(APPEND flags ${_value}) + endif() + get_target_property(_value "${item}" INTERFACE_COMPILE_DEFINITIONS) + if(_value) + foreach(_definition ${_value}) + if(MSVC) + list(APPEND flags "/D${_definition}") + else() + list(APPEND flags "-D${_definition}") + endif() + endforeach() + endif() + + # An imported archive names its file either outright or per configuration. + # An INTERFACE library has no file at all, and before CMake 3.19 merely + # asking one for IMPORTED_LOCATION is a fatal error rather than an empty + # answer ("INTERFACE_LIBRARY targets may only have whitelisted + # properties") -- which is what Debian Bullseye, on CMake 3.18.4, reported. + # TYPE is whitelisted, so it can be asked first, as can the INTERFACE_ + # properties read above and below. + get_target_property(_type "${item}" TYPE) + if(NOT _type STREQUAL "INTERFACE_LIBRARY") + get_target_property(_location "${item}" IMPORTED_LOCATION) + if(NOT _location) + get_target_property(_configurations "${item}" IMPORTED_CONFIGURATIONS) + if(_configurations) + list(GET _configurations 0 _configuration) + get_target_property(_location "${item}" IMPORTED_LOCATION_${_configuration}) + endif() + endif() + if(_location) + list(APPEND link "${_location}") + endif() + endif() + + get_target_property(_value "${item}" INTERFACE_LINK_LIBRARIES) + if(_value) + list(APPEND queue ${_value}) + endif() + get_target_property(_value "${item}" INTERFACE_LINK_OPTIONS) + if(_value) + list(APPEND link ${_value}) + endif() + endwhile() + + foreach(_list include_dirs flags link) + list(FILTER ${_list} EXCLUDE REGEX "\\$<") + endforeach() + if(include_dirs) + list(REMOVE_DUPLICATES include_dirs) + endif() + if(flags) + list(REMOVE_DUPLICATES flags) + endif() + + set(${include_dirs_var} ${include_dirs} PARENT_SCOPE) + set(${flags_var} ${flags} PARENT_SCOPE) + set(${link_var} ${link} PARENT_SCOPE) +endfunction() + + +################################################################################ +# codac_gaol_pkg_config_path() +################################################################################ +# +# When is a path of the GAOL codac_gaol_build() installed in the build +# tree, turns it into the path GAOL is installed at with Codac, under the +# ${prefix} of codac.pc. +function(codac_gaol_pkg_config_path var) + if(CODAC_GAOL_BUILT_HERE) + string(FIND "${${var}}" "${CODAC_GAOL_INSTALL_TREE}/" _position) + if(_position EQUAL 0) + string(LENGTH "${CODAC_GAOL_INSTALL_TREE}/" _length) + string(SUBSTRING "${${var}}" ${_length} -1 _rest) + set(${var} "\${prefix}/${_rest}" PARENT_SCOPE) + endif() + endif() +endfunction() + + +################################################################################ +# codac_gaol_config_snippet() +################################################################################ +# +# Returns the lines of codac-config.cmake that define Codac::gaol for a consumer +# of the installed Codac, which CODAC_LIBRARIES names -- the +# counterpart of the ibex-config-gaol.cmake and ibex-config-ultim.cmake files +# that create_target_import_and_export() writes for IBEX. +# +# - A GAOL with a CMake package is found again by find_package(gaol CONFIG): +# the GAOL Codac built under the prefix Codac is installed under, by a path +# relative to the configuration file, since the prefix may be moved; a GAOL +# found on this machine where it was found, before the usual search. +# - A GAOL found by pkg-config or by its files is named by the paths it was +# found at, with the flags it was compiled with. +# - find_package(gaol) asks for CODAC_GAOL_MIN_VERSION or later, as when Codac +# was built: the consumer compiles Codac's inline interval operations against +# the GAOL it finds, and a package search path of the consumer could lead to +# an older one. +function(codac_gaol_config_snippet outvar) + + if(CODAC_GAOL_FROM STREQUAL "package") + if(CODAC_GAOL_BUILT_HERE) + file(RELATIVE_PATH _to_prefix "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_CMAKE}" "${CMAKE_INSTALL_PREFIX}") + set(_find "get_filename_component(_codac_prefix \"\${CMAKE_CURRENT_LIST_DIR}/${_to_prefix}\" ABSOLUTE) + find_package(gaol ${CODAC_GAOL_MIN_VERSION} CONFIG REQUIRED NO_DEFAULT_PATH + PATHS \"\${_codac_prefix}/${CODAC_INSTALL_LIBDIR_3RD}/cmake/gaol\")") + else() + set(_find "find_package(gaol ${CODAC_GAOL_MIN_VERSION} CONFIG REQUIRED HINTS \"${gaol_DIR}\")") + endif() + set(_properties "INTERFACE_LINK_LIBRARIES gaol::gaol") + else() + codac_gaol_usage(_include_dirs _flags _link) + set(_find "") + set(_properties "INTERFACE_INCLUDE_DIRECTORIES \"${_include_dirs}\" + INTERFACE_COMPILE_OPTIONS \"${_flags}\" + INTERFACE_LINK_LIBRARIES \"${_link}\"") + endif() + + set(${outvar} " + # GAOL, the interval arithmetic library Codac is built upon, with mathlib + # (libultim) and the flags of interval arithmetic, as Codac::gaol, which + # CODAC_LIBRARIES names. + ${_find} + if(NOT TARGET Codac::gaol) + add_library(Codac::gaol INTERFACE IMPORTED) + set_target_properties(Codac::gaol PROPERTIES + ${_properties}) + endif() +" PARENT_SCOPE) +endfunction() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 28fd1c4fc..b8b0ab390 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,9 +24,45 @@ set(CODAC_PKG_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc) - set(CODAC_PKG_CONFIG_CFLAGS "-I\${includedir}/ibex") + set(CODAC_PKG_CONFIG_CFLAGS "") set(CODAC_PKG_CONFIG_LIBS "-L\${libdir}") + # Codac's own flags, which this build puts in CMAKE_CXX_FLAGS + # (codac_gaol_portability_flags() in scripts/CMakeModules/codac_gaol.cmake), + # and the interval arithmetic flags GAOL gives through Codac::gaol, + # CODAC_INTERVAL_CXX_FLAGS, set in the top-level CMakeLists.txt. A CMake + # consumer gets them through CODAC_CXX_FLAGS; a pkg-config consumer has no + # equivalent, and compiling Codac's headers without -frounding-math silently + # gives up the guarantee the whole library rests on. + foreach(_codac_flags CODAC_PORTABILITY_CXX_FLAGS CODAC_INTERVAL_CXX_FLAGS) + if(${_codac_flags}) + string(REPLACE ";" " " _codac_pkg_config_flags "${${_codac_flags}}") + string(APPEND CODAC_PKG_CONFIG_CFLAGS " ${_codac_pkg_config_flags}") + endif() + endforeach() + + # GAOL's include directories and libraries, as Codac::gaol gives them + # (codac_gaol_usage(), in the top-level CMakeLists.txt): where the GAOL found + # on this machine is, or, for the GAOL codac_gaol_build() built, where it is + # installed with Codac, under ${prefix}. They take the place of the + # "Requires: ibex" and -I\${includedir}/ibex of the time Codac reached GAOL + # through IBEX. GAOL itself is not named in "Requires:": a GAOL built with + # Codac installs its gaol.pc under lib/codac-3rd, where pkg-config does not + # look, and a GAOL found by its files has none. The directories the compiler + # searches of its own accord are left out, as CMake leaves them out of its + # command lines; the libraries are named by their full path, gaol before + # ultim, which it depends on. + foreach(dir ${CODAC_GAOL_INCLUDE_DIRS}) + if(NOT dir IN_LIST CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES) + codac_gaol_pkg_config_path(dir) + string(APPEND CODAC_PKG_CONFIG_CFLAGS " -I${dir}") + endif() + endforeach() + foreach(item ${CODAC_GAOL_LINK_ITEMS}) + codac_gaol_pkg_config_path(item) + string(APPEND CODAC_PKG_CONFIG_LIBS " ${item}") + endforeach() + file(GENERATE OUTPUT ${CODAC_PKG_CONFIG_FILE} CONTENT " prefix=${CMAKE_INSTALL_PREFIX} includedir=\${prefix}/${CMAKE_INSTALL_INCLUDEDIR} @@ -36,7 +72,7 @@ Description: ${PROJECT_DESCRIPTION} Url: ${PROJECT_HOMEPAGE_URL} Version: ${PROJECT_VERSION} - Requires: ibex + Requires: Cflags: ${CODAC_PKG_CONFIG_CFLAGS} Libs: ${CODAC_PKG_CONFIG_LIBS} ") @@ -49,6 +85,18 @@ set(CODAC_CMAKE_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake) + # The definition of Codac::gaol, written by + # scripts/CMakeModules/codac_gaol.cmake, which knows where GAOL came from. + codac_gaol_config_snippet(CODAC_GAOL_CONFIG_SNIPPET) + + # Codac's own flags (codac_gaol_portability_flags()) and the interval + # arithmetic flags GAOL gives (-frounding-math, etc.), for external projects. + # In that order, which is the order this build hands them to the compiler in + # -- the first through CMAKE_CXX_FLAGS, the second through Codac::gaol, whose + # options come after a target's own -- and decides which of two conflicting + # flags wins. + set(CODAC_CXX_FLAGS ${CODAC_PORTABILITY_CXX_FLAGS} ${CODAC_INTERVAL_CXX_FLAGS}) + file(WRITE ${CODAC_CMAKE_CONFIG_FILE} "# Try to find Codac # This file has been generated by CMake @@ -67,14 +115,24 @@ find_library(CODAC_UNSUPPORTED_LIBRARY NAMES ${PROJECT_NAME}-unsupported PATH_SUFFIXES lib) - find_package(IBEX REQUIRED) +${CODAC_GAOL_CONFIG_SNIPPET} + # Projects written for a Codac that depended on IBEX call ibex_init_common() + # after find_package(CODAC), as the manual used to tell them to, for the + # interval arithmetic flags. Those flags now come with CODAC_CXX_FLAGS, and + # this ibex_init_common() does nothing: it is only there so that such + # projects still configure. A project that finds IBEX itself, before or + # after Codac, gets IBEX's own function instead. + if(NOT COMMAND ibex_init_common) + function(ibex_init_common) + endfunction() + endif() set(CODAC_VERSION ${PROJECT_VERSION}) - set(CODAC_LIBRARIES \${CODAC_CORE_LIBRARY} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_UNSUPPORTED_LIBRARY} Ibex::ibex) + set(CODAC_LIBRARIES \${CODAC_CORE_LIBRARY} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_UNSUPPORTED_LIBRARY} Codac::gaol) set(CODAC_INCLUDE_DIRS \${CODAC_CORE_INCLUDE_DIR}/../ \${CODAC_CORE_INCLUDE_DIR}/../eigen3/ \${CODAC_CORE_INCLUDE_DIR} \${CODAC_GRAPHICS_INCLUDE_DIR} \${CODAC_UNSUPPORTED_INCLUDE_DIR}) set(CODAC_C_FLAGS \"\") - set(CODAC_CXX_FLAGS \"\") + set(CODAC_CXX_FLAGS \"${CODAC_CXX_FLAGS}\") ") if(WITH_PYTHON) @@ -111,7 +169,7 @@ endif() file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " - set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY}) + set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY} Codac::gaol) ") diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index db02e9526..3337a364b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -327,7 +327,7 @@ ${CMAKE_CURRENT_SOURCE_DIR}/tools ${CMAKE_CURRENT_SOURCE_DIR}/trajectory ) - target_link_libraries(${PROJECT_NAME}-core PUBLIC Ibex::ibex Eigen3::Eigen Threads::Threads) + target_link_libraries(${PROJECT_NAME}-core PUBLIC Codac::gaol Eigen3::Eigen Threads::Threads) ################################################################################ diff --git a/src/extensions/capd/CMakeLists.txt b/src/extensions/capd/CMakeLists.txt index 0214cb955..d5a8b2bd4 100644 --- a/src/extensions/capd/CMakeLists.txt +++ b/src/extensions/capd/CMakeLists.txt @@ -22,7 +22,7 @@ list(APPEND CODAC_CAPD_SRC #endif() add_library(${PROJECT_NAME}-capd ${CODAC_CAPD_SRC}) - target_link_libraries(${PROJECT_NAME}-capd PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen capd::capd) + target_link_libraries(${PROJECT_NAME}-capd PUBLIC ${PROJECT_NAME}-core Codac::gaol Eigen3::Eigen capd::capd) ################################################################################ diff --git a/src/extensions/sympy/CMakeLists.txt b/src/extensions/sympy/CMakeLists.txt index d92bae572..0bc1d9be7 100644 --- a/src/extensions/sympy/CMakeLists.txt +++ b/src/extensions/sympy/CMakeLists.txt @@ -31,7 +31,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(${PROJECT_NAME}-sympy ${CODAC_SYMPY_SRC}) - target_link_libraries(${PROJECT_NAME}-sympy PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen) + target_link_libraries(${PROJECT_NAME}-sympy PUBLIC ${PROJECT_NAME}-core Codac::gaol Eigen3::Eigen) target_link_libraries(${PROJECT_NAME}-sympy PRIVATE pybind11::pybind11) ################################################################################ diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index 453a547d1..f6d79ef6d 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -53,7 +53,7 @@ ${CMAKE_CURRENT_SOURCE_DIR}/paver # deprecated, to be removed ${CMAKE_CURRENT_SOURCE_DIR}/styles ) - target_link_libraries(${PROJECT_NAME}-graphics PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen ${PROJECT_NAME}-core) + target_link_libraries(${PROJECT_NAME}-graphics PUBLIC ${PROJECT_NAME}-core Codac::gaol Eigen3::Eigen ${PROJECT_NAME}-core) ################################################################################ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dff12a6f4..8dabdd6ad 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -107,7 +107,9 @@ list(APPEND SRC_TESTS # listing files without extension core/tools/codac2_tests_serialization core/tools/codac2_tests_transformations core/tools/codac2_tests_trunc - core/tools/ibex/codac2_tests_ibex + # core/tools/ibex/codac2_tests_ibex is not built: it includes the headers of + # IBEX, which Codac no longer depends on (the conversions it tests are only + # compiled when a program includes IBEX before them) ../doc/manual/manual/tools/src core/trajectory/codac2_tests_AnalyticTraj @@ -167,7 +169,7 @@ foreach(SRC_TEST ${SRC_TESTS}) add_executable(${TEST_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.cpp) set(CODAC_HEADERS_DIR ${CMAKE_CURRENT_BINARY_DIR}/../include) target_include_directories(${TEST_NAME} SYSTEM PUBLIC ${CODAC_HEADERS_DIR}) - target_link_libraries(${TEST_NAME} PUBLIC Ibex::ibex ${CODAC_LIBRARIES} PRIVATE Catch2::Catch2WithMain) + target_link_libraries(${TEST_NAME} PUBLIC ${CODAC_LIBRARIES} PRIVATE Catch2::Catch2WithMain) if(WITH_PYTHON AND SRC_TEST MATCHES "extensions/sympy/") target_link_libraries(${TEST_NAME} PRIVATE ${PROJECT_NAME}-sympy pybind11::embed) diff --git a/tests/test_codac/CMakeLists.txt b/tests/test_codac/CMakeLists.txt index 0f4479bc9..c94d7dfb7 100644 --- a/tests/test_codac/CMakeLists.txt +++ b/tests/test_codac/CMakeLists.txt @@ -8,17 +8,6 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Adding IBEX - - # In case you installed IBEX in a local directory, you need - # to specify its path with the CMAKE_PREFIX_PATH option. - # set(CMAKE_PREFIX_PATH "~/ibex-lib/build_install") - set(CMAKE_PREFIX_PATH "../ibex") - - find_package(IBEX REQUIRED) - ibex_init_common() # IBEX should have installed this function - message(STATUS "Found IBEX version ${IBEX_VERSION}") - # Adding Codac # In case you installed Codac in a local directory, you need @@ -39,4 +28,4 @@ add_executable(${PROJECT_NAME} main.cpp) target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex) + target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES}) From ed13cad6425fe7a1c58ce196fb45c396919b3773 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 03/19] Stop installing IBEX in the workflows, Docker scripts and packages --- .github/workflows/dockermatrix.yml | 13 +-------- .github/workflows/macosmatrix.yml | 6 ----- .github/workflows/tests.yml | 12 +++------ .github/workflows/unixmatrix.yml | 27 +++---------------- .github/workflows/vcmatrix.yml | 4 --- packages/choco/codac/codac.nuspec | 3 +-- packages/deb/control | 1 - packages/temporary/gennewcodacpi_armhf.sh | 6 ----- scripts/docker/build_pybinding.sh | 9 ++++--- .../docker/build_pybinding_codac4matlab.sh | 9 ++++--- 10 files changed, 19 insertions(+), 71 deletions(-) diff --git a/.github/workflows/dockermatrix.yml b/.github/workflows/dockermatrix.yml index 5045d0245..e7cde280d 100644 --- a/.github/workflows/dockermatrix.yml +++ b/.github/workflows/dockermatrix.yml @@ -72,15 +72,7 @@ jobs: if [ \"${{ matrix.cfg.deb }}\" = \"true\" ]; then \ sudo sh -c 'echo \"deb [trusted=yes] https://webperso.ensta.fr/packages/\$(if [ -z \"\$(. /etc/os-release && echo \$UBUNTU_CODENAME)\" ]; then echo debian/\$(. /etc/os-release && echo \$VERSION_CODENAME); else echo ubuntu/\$(. /etc/os-release && echo \$UBUNTU_CODENAME); fi) ./\" > /etc/apt/sources.list.d/ensta-bretagne.list' && \ #sudo apt-get -q update ; sudo apt-get -y install libeigen3-dev catch2 dpkg-dev || true && \\ - sudo apt-get -q update ; sudo apt-get -y install catch2 dpkg-dev || true && \ - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_\$(dpkg --print-architecture).deb --no-check-certificate -nv && \ - sudo dpkg -i libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_\$(dpkg --print-architecture).deb && \ - rm -Rf libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_\$(dpkg --print-architecture).deb ; \ - else \ - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip --no-check-certificate -nv && \ - unzip -q ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip && \ - rm -Rf ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip && \ - sudo cp -Rf ibex/* /usr/ ; \ + sudo apt-get -q update ; sudo apt-get -y install catch2 dpkg-dev || true ; \ fi && \ mkdir build ; cd build && \ cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D BUILD_TESTS=ON -D CMAKE_INSTALL_PREFIX="../codac" .. && \ @@ -88,9 +80,6 @@ jobs: cd .. && \ zip -q -r codac_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip codac && \ mkdir -p codac_standalone/example ; cd codac_standalone && \ - if [ \"${{ matrix.cfg.deb }}\" = \"true\" ]; then mkdir -p ibex/include ; mkdir -p ibex/lib ; mkdir -p ibex/share ; mkdir -p ibex/bin ; cp -Rf /usr/include/ibex* ibex/include/ ; cp -Rf /usr/lib/*ibex* ibex/lib/ ; cp -Rf /usr/share/*ibex* ibex/share/ ; cp -Rf /usr/share/pkgconfig ibex/share/ ; cp -Rf /usr/bin/ibex* ibex/bin/ ; \ - else cp -Rf ../ibex . ; \ - fi && \ cp -Rf ../codac . ; cp -Rf ../tests/test_codac/* ./example/ ; cd .. ; zip -q -r codac_standalone_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip codac_standalone && \ cd codac_standalone/example && \ cmake ${{ matrix.cfg.cmake_params }} . && \ diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 20ba35698..08231a92e 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -56,12 +56,6 @@ jobs: if: (runner.os=='macOS')&&(matrix.cfg.cross!=true) - run: brew install graphviz ; brew install --formula doxygen ; python -m pip install --upgrade pip ; pip install --upgrade wheel setuptools sphinx breathe sphinx_rtd_theme sphinx-tabs sphinx-issues sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy if: runner.os=='macOS' - - run: | - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip --no-check-certificate -nv - unzip -q ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip - rm -Rf ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip - sudo cp -Rf ibex/* /usr/local/ - shell: bash - run: | mkdir build ; cd build cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_SYSTEM_NAME=Darwin -D CMAKE_OSX_ARCHITECTURES=${{ matrix.cfg.arch }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D WITH_CAPD=OFF -D WITH_PYTHON=ON -D PYBIND11_FINDPYTHON=OFF .. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index afb301494..5a274af83 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,7 +45,7 @@ jobs: sudo sh -c 'echo "deb [trusted=yes] https://webperso.ensta.fr/packages/$(if [ -z "$(. /etc/os-release && echo $UBUNTU_CODENAME)" ]; then echo debian/$(. /etc/os-release && echo $VERSION_CODENAME); else echo ubuntu/$(. /etc/os-release && echo $UBUNTU_CODENAME); fi) ./" > /etc/apt/sources.list.d/ensta-bretagne.list' sudo apt update - sudo apt-get -y install flex bison catch2 pybind11-dev # libeigen3-dev + sudo apt-get -y install catch2 pybind11-dev # libeigen3-dev # For documentation pip install sphinx breathe sphinx-issues sphinx-tabs sphinx_rtd_theme sympy @@ -71,16 +71,12 @@ jobs: ls cd $ORIGIN_DIR - # IBEX - bash scripts/dependencies/install_ibex.sh - # CAPD # cancelled on 2023/05/09: bash scripts/dependencies/install_capd.sh if [ "${{ matrix.cfg.with_capd }}" = "ON" ]; then git clone -b master https://github.com/CAPDGroup/CAPD.git ; cd CAPD ; git checkout 380b117 ; mkdir build ; cd build ; cmake .. ; cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" sudo cmake --build . -j 4 --config Release --target install ; cd ../.. ; fi # Environment variables export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:$HOME/codac/build_install - export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:$HOME/ibex-lib/build_install export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib #py_version=$(python -c "import sys; print(sys.version[:3])") # default python version @@ -94,7 +90,7 @@ jobs: cd build # Building lib + tests - cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH=$HOME/ibex-lib/build_install -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DWITH_CAPD=${{ matrix.cfg.with_capd }} -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON .. + cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DWITH_CAPD=${{ matrix.cfg.with_capd }} -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON .. make -j 4 #make doc # todo make install @@ -121,7 +117,7 @@ jobs: cd ../examples cd 01_batman/ - mkdir build ; cd build ; cmake -DCMAKE_PREFIX_PATH="$HOME/ibex-lib/build_install;$HOME/codac/build_install" -DCMAKE_BUILD_TYPE=Debug .. ; make ; ./codac_example + mkdir build ; cd build ; cmake -DCMAKE_PREFIX_PATH="$HOME/codac/build_install" -DCMAKE_BUILD_TYPE=Debug .. ; make ; ./codac_example cd ../../02_centered_form/ - mkdir build ; cd build ; cmake -DCMAKE_PREFIX_PATH="$HOME/ibex-lib/build_install;$HOME/codac/build_install" -DCMAKE_BUILD_TYPE=Debug .. ; make ; ./codac_example \ No newline at end of file + mkdir build ; cd build ; cmake -DCMAKE_PREFIX_PATH="$HOME/codac/build_install" -DCMAKE_BUILD_TYPE=Debug .. ; make ; ./codac_example diff --git a/.github/workflows/unixmatrix.yml b/.github/workflows/unixmatrix.yml index d35535996..6c0b801fc 100644 --- a/.github/workflows/unixmatrix.yml +++ b/.github/workflows/unixmatrix.yml @@ -1,4 +1,4 @@ -# This file generates .deb (Unix) and .nupkg (Windows) packages (and zip for having Codac and IBEX binaries for several Visual Studio versions) +# This file generates .deb (Unix) and .nupkg (Windows) packages (and zip for having Codac binaries for several Visual Studio versions) on: push: branches: ['**'] @@ -165,34 +165,17 @@ jobs: # choco install -y -r --no-progress cmake --force # echo export BASHCMAKEPATH="/c/Program Files/CMake/bin">>%USERPROFILE%\.bashrc # if: (matrix.cfg.runtime=='vc18')&&(matrix.cfg.os!='windows-11-arm') - - run: | - rem choco install -y -r --no-progress eigen --version=3.4.0.20240224 ${{ matrix.cfg.choco_flags }} - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex.2.8.9.20260819.nupkg --no-check-certificate -nv - choco install -y -r --no-progress --ignore-dependencies -s . ibex --version=2.8.9.20260819 ${{ matrix.cfg.choco_flags }} --params "'/url:https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip'" - del /f /q ibex.2.8.9.20260819.nupkg - if: runner.os=='Windows' + #- run: choco install -y -r --no-progress eigen --version=3.4.0.20240224 ${{ matrix.cfg.choco_flags }} + # if: runner.os=='Windows' - run: | sudo sh -c 'echo "deb [trusted=yes] https://webperso.ensta.fr/packages/$(if [ -z "$(. /etc/os-release && echo $UBUNTU_CODENAME)" ]; then echo debian/$(. /etc/os-release && echo $VERSION_CODENAME); else echo ubuntu/$(. /etc/os-release && echo $UBUNTU_CODENAME); fi) ./" > /etc/apt/sources.list.d/ensta-bretagne.list' - # Replace this line by the next ones to test a specific binary package of IBEX. - #sudo apt-get -q update ; sudo apt-get -y install libibex-dev catch2 dpkg-dev || true # libeigen3-dev sudo apt-get -q update ; sudo apt-get -y install catch2 dpkg-dev || true # libeigen3-dev - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_$(dpkg --print-architecture).deb --no-check-certificate -nv - sudo dpkg -i libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_$(dpkg --print-architecture).deb - rm -Rf libibex-dev-2.8.9.20260819-0${{ matrix.cfg.runtime }}0_$(dpkg --print-architecture).deb shell: bash if: matrix.cfg.deb==true #- run: brew install eigen # if: runner.os=='macOS' - run: brew install catch2 # Issues with binary packages when cross-compiling... if: (runner.os=='macOS')&&(matrix.cfg.cross!=true) - - run: | - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip --no-check-certificate -nv - unzip -q ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip - rm -Rf ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip - sudo cp -Rf ibex/* /usr/local/ - if: runner.os=='macOS' -# - run: git clone --depth 1 -b master https://github.com/lebarsfa/ibex-lib.git ; cd ibex-lib ; mkdir build ; cd build ; cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../../ibex" .. ; cmake --build . --config Release --target install ; cd ../.. -# shell: bash - run: | if [ ${{ runner.os }} = Windows ]; then source ~/refreshenv.bashrc ; refreshenv ; export PATH=$BASHMINGWPATH:$BASHCMAKEPATH:$PATH ; fi mkdir build ; cd build @@ -207,10 +190,6 @@ jobs: if [ ${{ runner.os }} = Windows ]; then source ~/refreshenv.bashrc ; refreshenv ; export PATH=$BASHMINGWPATH:$BASHCMAKEPATH:$PATH ; fi mkdir -p codac_standalone/example ; cd codac_standalone #wget https://community.chocolatey.org/api/v2/package/eigen/3.4.0.20240224 --no-check-certificate -nv ; unzip -q 3.4.0.20240224 -d eigen ; rm -Rf 3.4.0.20240224 eigen/*.xml eigen/*.nuspec eigen/_* eigen/package eigen/tools - if [ ${{ runner.os }} = Windows ]; then cp -Rf /C/ProgramData/chocolatey/lib/ibex . ; rm -Rf ibex/tools ibex/ibex.* - elif [ ${{ matrix.cfg.deb }} = true ]; then mkdir -p ibex/include ; mkdir -p ibex/lib ; mkdir -p ibex/share ; mkdir -p ibex/bin ; cp -Rf /usr/include/ibex* ibex/include/ ; cp -Rf /usr/lib/*ibex* ibex/lib/ ; cp -Rf /usr/share/*ibex* ibex/share/ ; cp -Rf /usr/share/pkgconfig ibex/share/ ; cp -Rf /usr/bin/ibex* ibex/bin/ - else cp -Rf ../ibex . - fi cp -Rf ../codac . ; cp -Rf ../tests/test_codac/* ./example/ ; cd .. ; zip -q -r codac_standalone_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip codac_standalone shell: bash - run: | diff --git a/.github/workflows/vcmatrix.yml b/.github/workflows/vcmatrix.yml index 27ac4fd0c..e00aefb57 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -62,10 +62,6 @@ jobs: # if: runner.os=='Windows' - run: choco install -y -r --no-progress graphviz doxygen.install & python -m pip install --upgrade pip & pip install --upgrade wheel setuptools sphinx breathe sphinx-issues sphinx-tabs sphinx_rtd_theme sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy if: runner.os=='Windows' - - run: | - wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex.2.8.9.20260819.nupkg --no-check-certificate -nv - choco install -y -r --no-progress --ignore-dependencies -s . ibex --version=2.8.9.20260819 ${{ matrix.cfg.choco_flags }} --params "'/url:https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip'" - del /f /q ibex.2.8.9.20260819.nupkg - run: | mkdir build ; cd build cmake -E env CXXFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" CFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D WITH_CAPD=OFF -D WITH_PYTHON=ON -D PYBIND11_FINDPYTHON=OFF .. diff --git a/packages/choco/codac/codac.nuspec b/packages/choco/codac/codac.nuspec index 4b7c7933f..924073029 100644 --- a/packages/choco/codac/codac.nuspec +++ b/packages/choco/codac/codac.nuspec @@ -28,7 +28,7 @@ Codac is a library providing tools for constraint programming over reals, trajec ## Package parameters The following package parameters can be set: -- `/url:URL` - Will install the specified binary package (e.g. built for Visual Studio), see versions from https://github.com/codac-team/codac/releases (the Windows `PATH` might need to be updated manually with e.g. `C:\ProgramData\chocolatey\lib\codac\bin`, etc.). By default, only the MinGW libraries compatible with the corresponding MinGW Chocolatey package dependency are installed. Use the standard parameter `choco install --ignore-dependencies ...` to avoid installing the default MinGW and IBEX Chocolatey package dependencies if needed (you might want to install manually [IBEX](https://community.chocolatey.org/packages/ibex) package with the corresponding parameters, as well as the corresponding compiler). +- `/url:URL` - Will install the specified binary package (e.g. built for Visual Studio), see versions from https://github.com/codac-team/codac/releases (the Windows `PATH` might need to be updated manually with e.g. `C:\ProgramData\chocolatey\lib\codac\bin`, etc.). By default, only the MinGW libraries compatible with the corresponding MinGW Chocolatey package dependency are installed. Use the standard parameter `choco install --ignore-dependencies ...` to avoid installing the default MinGW Chocolatey package dependency if needed (you might want to install manually the corresponding compiler). - `/checksum:SHA256` - SHA256 checksum of the binary package specified by the `/url` parameter. If needed, use the standard parameter `choco install --ignore-checksums ...` for trusted sources. - `/urlX:URL` - Same as above, with X in [1,99], except this will not disable the installation of the MinGW libraries compatible with the corresponding MinGW Chocolatey package dependency. - `/checksumX:SHA256` - SHA256 checksum of the binary package specified by the `/urlX` parameter. If needed, use the standard parameter `choco install --ignore-checksums ...` for trusted sources. @@ -45,7 +45,6 @@ choco install -y --ignore-dependencies codac --params "'/url:https://github.com/ - diff --git a/packages/deb/control b/packages/deb/control index 7d652a59b..782232b34 100644 --- a/packages/deb/control +++ b/packages/deb/control @@ -1,7 +1,6 @@ Package: libcodac-dev Version: 1 Architecture: amd64 -Depends: libibex-dev Section: math Priority: optional Description: Codac is a library providing tools for constraint programming over reals, trajectories and sets diff --git a/packages/temporary/gennewcodacpi_armhf.sh b/packages/temporary/gennewcodacpi_armhf.sh index 7ec2b7a02..87f72fae0 100644 --- a/packages/temporary/gennewcodacpi_armhf.sh +++ b/packages/temporary/gennewcodacpi_armhf.sh @@ -27,12 +27,6 @@ fi && \ sudo apt-get -q update --allow-releaseinfo-change ; sudo apt-get -y install python3-dev patchelf python3-pip python3-wheel python3-setuptools || true && \ python3 -m pip install \$PIP_OPTIONS --upgrade patchelf --prefer-binary --extra-index-url https://www.piwheels.org/simple || true && \ python3 -m pip install \$PIP_OPTIONS --upgrade auditwheel --prefer-binary --extra-index-url https://www.piwheels.org/simple && \ -# wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_armhf_\$(lsb_release -cs).zip --no-check-certificate -nv is causing illegal instruction on a Mac M1... \\ -curl -L -O https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_armhf_\$(lsb_release -cs).zip --insecure && \ -unzip -q ibex_armhf_\$(lsb_release -cs).zip && \ -rm -Rf ibex_armhf_\$(lsb_release -cs).zip && \ -sudo cp -Rf ibex/* /usr/local/ && \ -\ git config --global --add safe.directory /io && \ cd /io && \ \ diff --git a/scripts/docker/build_pybinding.sh b/scripts/docker/build_pybinding.sh index ab98904f3..b89c0b428 100755 --- a/scripts/docker/build_pybinding.sh +++ b/scripts/docker/build_pybinding.sh @@ -2,10 +2,11 @@ set -e -x -wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_$(uname -m)_manylinux_2_28.zip --no-check-certificate -nv -unzip -q ibex_$(uname -m)_manylinux_2_28.zip -rm -Rf ibex_$(uname -m)_manylinux_2_28.zip -sudo cp -Rf ibex/* /usr/local/ +# Codac no longer depends on IBEX: its installation is kept below for reference only. +#wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_$(uname -m)_manylinux_2_28.zip --no-check-certificate -nv +#unzip -q ibex_$(uname -m)_manylinux_2_28.zip +#rm -Rf ibex_$(uname -m)_manylinux_2_28.zip +#sudo cp -Rf ibex/* /usr/local/ git config --global --add safe.directory /io cd /io diff --git a/scripts/docker/build_pybinding_codac4matlab.sh b/scripts/docker/build_pybinding_codac4matlab.sh index b25fd262d..1c18b1841 100644 --- a/scripts/docker/build_pybinding_codac4matlab.sh +++ b/scripts/docker/build_pybinding_codac4matlab.sh @@ -2,10 +2,11 @@ set -e -x -wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_$(uname -m)_manylinux_2_28.zip --no-check-certificate -nv -unzip -q ibex_$(uname -m)_manylinux_2_28.zip -rm -Rf ibex_$(uname -m)_manylinux_2_28.zip -sudo cp -Rf ibex/* /usr/local/ +# Codac no longer depends on IBEX: its installation is kept below for reference only. +#wget https://github.com/lebarsfa/ibex-lib/releases/download/ibex-2.8.9.20260819/ibex_$(uname -m)_manylinux_2_28.zip --no-check-certificate -nv +#unzip -q ibex_$(uname -m)_manylinux_2_28.zip +#rm -Rf ibex_$(uname -m)_manylinux_2_28.zip +#sudo cp -Rf ibex/* /usr/local/ git config --global --add safe.directory /io cd /io From 66aefbb28dc192ee85c6109377ba29175412d4f4 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 04/19] Document in the manual that Codac depends on GAOL instead of IBEX --- doc/manual/development/info_dev.rst | 31 +++--------- doc/manual/manual/extensions/capd/capd.rst | 3 +- doc/manual/manual/extensions/sympy/index.rst | 1 - doc/manual/manual/installation/cpp.rst | 52 ++++++-------------- 4 files changed, 23 insertions(+), 64 deletions(-) diff --git a/doc/manual/development/info_dev.rst b/doc/manual/development/info_dev.rst index e99bf5a24..4f18a4548 100644 --- a/doc/manual/development/info_dev.rst +++ b/doc/manual/development/info_dev.rst @@ -40,7 +40,7 @@ If you simply want to use the latest Codac release in Python, you can download t .. code-block:: bash - sudo apt-get install -y g++ gcc cmake git flex bison + sudo apt-get install -y g++ gcc cmake git - a supported version of Python (>=3.8). - a recent `Doxygen `_ version (for instance, release 1.16.1 or newest). On Linux systems, latest releases are not available as Debian packages, so we advise to install Doxygen from the sources: @@ -60,24 +60,7 @@ If you simply want to use the latest Codac release in Python, you can download t Doxygen software extracts C++ documentation from header files into XML format. We then convert this data into docstring format before embedding it into the binding binaries. In this way, the writing of the documentation is centralized in a single location in the C++ header files. -2. **Configure IBEX prior to compiling Codac**: - - We recall that IBEX sources can be obtained with: - - .. code-block:: bash - - git clone https://github.com/lebarsfa/ibex-lib.git $HOME/ibex-lib - cd $HOME/ibex-lib - - You will need to compile both IBEX and Codac using the ``-fPIC`` options. This can be done with the following CMake configuration: - - .. code-block:: bash - - mkdir build ; cd build - cmake -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_INSTALL_PREFIX=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release .. - make ; make install - -3. **Compile Codac with Python binding**: +2. **Compile Codac with Python binding**: We recall that Codac sources can be obtained with: @@ -96,15 +79,15 @@ If you simply want to use the latest Codac release in Python, you can download t Note that you will then have to ``import codac2`` instead of ``import codac`` in your Python scripts. - In addition to the ``-fPIC`` options, you will have to configure ``WITH_PYTHON=ON`` and ``PYBIND11_FINDPYTHON=OFF``. Note that CMake will automatically get the `pybind11 `_ files required for the binding. Also, you will have to configure ``BUILD_TESTS=ON`` if you want to run the unit tests. + You will need to compile Codac using the ``-fPIC`` options (a GAOL built by CMake along with Codac is compiled that way on its own; a GAOL installed on your system has to have been as well), and to configure ``WITH_PYTHON=ON`` and ``PYBIND11_FINDPYTHON=OFF``. Note that CMake will automatically get the `pybind11 `_ files required for the binding. Also, you will have to configure ``BUILD_TESTS=ON`` if you want to run the unit tests. .. code-block:: bash mkdir build ; cd build - cmake -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH="$HOME/ibex-lib/build_install;$HOME/doxygen/build_install" -DCMAKE_BUILD_TYPE=Release .. + cmake -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH="$HOME/doxygen/build_install" -DCMAKE_BUILD_TYPE=Release .. make ; make install -4. **Configure your Python environment**: +3. **Configure your Python environment**: Finally, you need to configure your system so that Python can find access to your Codac binding binaries: @@ -119,7 +102,7 @@ If you simply want to use the latest Codac release in Python, you can download t export PYTHONPATH="${PYTHONPATH}:$HOME/codac/build/python/python_package/" -5. **Verify the installation** (optional): +4. **Verify the installation** (optional): To ensure that the installation has worked properly, the unit tests of the library can be run: @@ -127,7 +110,7 @@ If you simply want to use the latest Codac release in Python, you can download t python -m unittest discover codac.tests -6. **Try an example** (optional): +5. **Try an example** (optional): You may want to try Codac in Python by running one of the proposed examples. After the installation, you can run the following commands: diff --git a/doc/manual/manual/extensions/capd/capd.rst b/doc/manual/manual/extensions/capd/capd.rst index 590fe4c98..20569f5e3 100644 --- a/doc/manual/manual/extensions/capd/capd.rst +++ b/doc/manual/manual/extensions/capd/capd.rst @@ -20,7 +20,7 @@ To install the ``codac-capd`` extension, you need to install the Codac library f .. code-block:: bash - cmake -DCMAKE_INSTALL_PREFIX=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release -DWITH_CAPD=ON .. + cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_BUILD_TYPE=Release -DWITH_CAPD=ON .. We highly recommend to test the installation of the library with the provided tests. To do so, you can use the following command: @@ -61,7 +61,6 @@ Furthermore, you need to link the extension to your project, for instance by upd ${CODAC_LIBRARIES} ${CODAC_CAPD_LIBRARY} # linking to the codac-capd extension capd::capd # linking to CAPD - Ibex::ibex ) You can use the functions ``to_capd`` and ``to_codac`` to convert between CAPD and Codac objects as follows: diff --git a/doc/manual/manual/extensions/sympy/index.rst b/doc/manual/manual/extensions/sympy/index.rst index c1e6b890c..e26be00f4 100644 --- a/doc/manual/manual/extensions/sympy/index.rst +++ b/doc/manual/manual/extensions/sympy/index.rst @@ -54,7 +54,6 @@ In C++ however, you need to link the extension to your project, for instance by ${CODAC_LIBRARIES} ${CODAC_SYMPY_LIBRARY} # linking to the codac-sympy extension pybind11::embed # linking to pybind11 - Ibex::ibex ) Finally, in order to include the features of the extension: diff --git a/doc/manual/manual/installation/cpp.rst b/doc/manual/manual/installation/cpp.rst index d92dc75b9..cc0069116 100644 --- a/doc/manual/manual/installation/cpp.rst +++ b/doc/manual/manual/installation/cpp.rst @@ -39,7 +39,7 @@ Linux Installation .. .. code-block:: bash -.. sudo apt remove libcodac-dev libibex-dev +.. sudo apt remove libcodac-dev .. sudo rm -f /etc/apt/sources.list.d/ensta-bretagne.list .. sudo apt update @@ -74,63 +74,41 @@ Steps sudo apt-get install -y build-essential cmake git -2. **Install the IBEX dependency**: - - Codac still uses some features of the `IBEX library `_ that you have to install first (currently, the only thing Codac uses from IBEX is a wrapper of the `GAOL library `_). The last version of IBEX is maintained on `this unofficial development repository `_: - - .. code-block:: bash - - # Requirements to compile IBEX - sudo apt-get install -y flex bison - - # Download IBEX sources from GitHub - git clone -b master https://github.com/lebarsfa/ibex-lib.git $HOME/ibex-lib - - # Configure IBEX before installation - cd $HOME/ibex-lib - mkdir build ; cd build - cmake -DCMAKE_INSTALL_PREFIX=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release .. - - # Building + installing - make - make install - cd ../.. - - For further CMake options, please refer to the IBEX documentation. - - .. warning:: + .. admonition:: The GAOL dependency - **GAOL prerequisite:** On some platforms, you might need to install manually `MathLib `_ and `GAOL `_ with CMake and `specify where they are `_ in order to build IBEX successfully and have accurate computations. + | The intervals of Codac are built upon `GAOL `_, the interval arithmetic library written by `Frédéric Goualard `_, which computes its elementary functions with the IBM Accurate Portable Mathematical Library (mathlib). You do not have to install them: CMake first looks for a GAOL installed on your system and, when it finds none, downloads GAOL from the master branch of `the fork of Jordan Ninin `_ while Codac is configured (brought up to date at each configuration when Git is installed), builds it with its CMake build, which downloads mathlib from `Frédéric Goualard's site `_, and installs both with its CMake installer, in the build directory and along with Codac. The fork adds to GAOL a CMake build, taken from the one of `IBEX `_ (which Codac used to depend on, and no longer does), the changes Codac depends on or which Visual Studio, MinGW and ARM processors need, and tests of the bounds it computes. Its README lists and explains them. + | If you install GAOL yourself, install the version of `the fork of Jordan Ninin `_, 4.3.2 or later: Codac does not use the original sources, of an older version, which lack the fixes it relies on. The fork comes with the CMake installer, which builds and installs GAOL and mathlib together (``cmake -S . -B build -DCMAKE_INSTALL_PREFIX=``, ``cmake --build build --config Release``, then ``cmake --install build --config Release``), with the CMake package from which Codac takes the compilation flags and the libraries GAOL needs, while the original sources only have autotools and meson builds. Bugs of GAOL are also fixed there: bounds that did not enclose the exact results (numbers such as ``interval("0.1")`` read with the C runtime of Windows or with musl on 64-bit ARM processors; hyperbolic functions with the libm of glibc 2.31, musl or MinGW-w64; square roots with Visual C++ for 32-bit x86), and powers with a real exponent (``pow([4], 0.5)`` returned ``[1]``, ``pow([-4,-1], [0.5])`` returned ``[-1, 2]``). + | CMake looks for an installed GAOL in three ways, in this order: its CMake package (``gaolConfig.cmake``, which the CMake build of the fork installs), its ``gaol.pc`` through ``pkg-config`` (which the autotools and meson builds of the fork install), then its files (``gaol/gaol.h``, ``MathLib.h`` and the ``gaol`` and ``ultim`` libraries). Codac is compiled with the flags and linked with the libraries the package or ``gaol.pc`` of GAOL gives; only for a GAOL found by its files does Codac determine the flags itself. A ``gaol.pc`` whose flags lack ``-frounding-math``, as the one of the meson build of the original sources, is not used. Nor is a GAOL older than version 4.3.2, the first version of the fork with all the fixes Codac relies on (the last one: the intersection of disjoint intervals is the empty set), or whose version cannot be told: CMake says so, and builds the fork instead. The projects using the installed Codac need GAOL 4.3.2 or later too, since they compile the interval operations of Codac, inline in its headers, against their GAOL. To use a GAOL installed in a custom location, add its installation prefix to ``CMAKE_PREFIX_PATH``, which the three searches read, or give ``-Dgaol_DIR=/lib/cmake/gaol`` for its CMake package, ``PKG_CONFIG_PATH`` for its ``gaol.pc``, or ``-DGAOL_DIR=`` (and ``-DMATHLIB_DIR=`` for mathlib, if it is installed elsewhere) for its files. To build the GAOL Codac is tested against even where another one is installed, configure Codac with ``-DENABLE_FIND_PACKAGE_GAOL=OFF``. + | On a 32-bit x86 processor, Codac, GAOL and mathlib are compiled with ``-msse2 -mfpmath=sse``, except by Visual Studio, which computes in SSE2 already: computed on the x87 FPU, GAOL's bounds and mathlib's results are only right while its precision stays set to 53 bits, which nothing guarantees. A processor with SSE2 is therefore required there. GAOL is not built with the compilers that do not compute its intervals right, or much too slowly, and its build stops with a message naming the ones to use instead: Clang for 32-bit ARM processors (use GCC), the compilers that say they do not honour the rounding direction, such as Clang 14 for 64-bit ARM processors, and the mingw-w64 runtimes older than version 13 (those of the MinGW-w64 GCC 11 to 14 of Chocolatey, for instance): before version 12, their math library is not accurate enough, and the ``fesetround()`` of version 12 makes the elementary functions of GAOL some 20 times slower. -3. **Install the Codac library**: +2. **Install the Codac library**: .. code-block:: bash - # The codac directory can be placed in your home, same level as IBEX + # The codac directory can be placed in your home git clone https://github.com/codac-team/codac $HOME/codac # Configure Codac before installation cd $HOME/codac mkdir build ; cd build - cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release .. + cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_BUILD_TYPE=Release .. # Building + installing make make install cd ../.. -4. **Configure your system to find Codac**: +3. **Configure your system to find Codac**: In case Codac and its dependencies have been installed locally on your system, you will have to configure your environment variables. This can be done temporarily with: .. code-block:: bash - export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:$HOME/ibex-lib/build_install export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:$HOME/codac/build_install ... or permanently by updating your ``.bashrc`` file by appending the above commands. -5. **Verify the installation** (optional): +4. **Verify the installation** (optional): To ensure that the installation has worked properly, the unit tests of the library can be run. For this, you have to configure CMake using the ``-DBUILD_TESTS=ON`` option, before compilation. Then, from the ``$HOME/codac/build`` directory: @@ -138,7 +116,7 @@ Steps make test -6. **Try an example** (optional): +5. **Try an example** (optional): You may want to try Codac by running one of the proposed examples. After the installation, you can run the following commands: @@ -169,7 +147,7 @@ Using MinGW .. Check https://community.chocolatey.org/packages/codac. -Install `Chocolatey package manager `_, run `choco install -y ibex cmake make qtcreator` in PowerShell and then download and extract *e.g.* ``codac_standalone_x64_mingw13.zip`` (for MinGW 13) from https://github.com/codac-team/codac/releases/latest, launch Qt Creator and choose Open Project, open ``example\CMakelists.txt``, ensure Desktop is selected and click Configure Project (might be hidden behind notifications at the bottom-right), wait 10 s then click on the big bottom-left green Run button, and finally check that the graphical output appears. +Install `Chocolatey package manager `_, run `choco install -y cmake make qtcreator` in PowerShell and then download and extract *e.g.* ``codac_standalone_x64_mingw15.zip`` (for MinGW 15) from https://github.com/codac-team/codac/releases/latest, launch Qt Creator and choose Open Project, open ``example\CMakelists.txt``, ensure Desktop is selected and click Configure Project (might be hidden behind notifications at the bottom-right), wait 10 s then click on the big bottom-left green Run button, and finally check that the graphical output appears. Note that in order to obtain graphical outputs, you will have to download and run https://github.com/ENSTABretagneRobotics/VIBES/releases/latest/download/VIBes-viewer_x86.exe before running the project. @@ -189,10 +167,10 @@ You will probably need to install these prerequisites (assuming you already inst .. code-block:: bash - choco install cmake git make patch winflexbison + choco install cmake git make choco install eigen -Then, install the desired compiler (*e.g.* ``choco install mingw --version=11.2.0.07112021``). +Then, install the desired compiler (*e.g.* ``choco install mingw --version=15.2.0``; MinGW-w64 older than version 13, which the MinGW-w64 GCC 11 to 14 packages come with, is not supported). Optionally, for Python binding (*e.g.* ``choco install python --version=3.10.4``) and documentation: From 411b4cc6c8438886cfc748faf0693b16e53692c9 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 05/19] Hand Threads to the users of codac-config.cmake --- CMakeLists.txt | 5 +++++ python/src/core/CMakeLists.txt | 2 +- python/src/graphics/CMakeLists.txt | 2 +- python/src/unsupported/CMakeLists.txt | 2 +- src/CMakeLists.txt | 13 +++++++++++-- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb078228f..58bcc1605 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,11 @@ # Looking for Threads ################################################################################ + # The same preference the generated codac-config.cmake asks of consumers, so + # that both sides of a link end up with the same answer: without it this + # build could settle on -lpthread while a consumer, which does set it, got + # -pthread. + set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) ################################################################################ diff --git a/python/src/core/CMakeLists.txt b/python/src/core/CMakeLists.txt index 3507d6d5c..679e254f0 100644 --- a/python/src/core/CMakeLists.txt +++ b/python/src/core/CMakeLists.txt @@ -157,7 +157,7 @@ ) target_link_libraries(_core - PRIVATE ${PROJECT_NAME}-core ${PROJECT_NAME}-sympy ${LIBS} Codac::gaol + PRIVATE ${PROJECT_NAME}-core ${PROJECT_NAME}-sympy Threads::Threads Codac::gaol ) # Copy the generated library in the package folder diff --git a/python/src/graphics/CMakeLists.txt b/python/src/graphics/CMakeLists.txt index 0b4386d75..79f7639f8 100644 --- a/python/src/graphics/CMakeLists.txt +++ b/python/src/graphics/CMakeLists.txt @@ -28,7 +28,7 @@ ) target_link_libraries(_graphics - PRIVATE ${PROJECT_NAME}-graphics ${LIBS} Codac::gaol + PRIVATE ${PROJECT_NAME}-graphics Threads::Threads Codac::gaol ) # Copy the generated library in the package folder diff --git a/python/src/unsupported/CMakeLists.txt b/python/src/unsupported/CMakeLists.txt index 861175754..15bccec3a 100644 --- a/python/src/unsupported/CMakeLists.txt +++ b/python/src/unsupported/CMakeLists.txt @@ -16,7 +16,7 @@ ) target_link_libraries(_unsupported - PRIVATE ${PROJECT_NAME}-unsupported ${LIBS} Codac::gaol + PRIVATE ${PROJECT_NAME}-unsupported Threads::Threads Codac::gaol ) # Copy the generated library in the package folder diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b8b0ab390..b40947da8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -127,8 +127,17 @@ ${CODAC_GAOL_CONFIG_SNIPPET} endfunction() endif() + # codac-core links PUBLIC against Threads::Threads (see src/core/CMakeLists.txt), + # e.g. for peibos/threading code. That requirement doesn't come back through + # find_library() above (a plain path has no transitive usage requirements), + # so it has to be re-declared here -- otherwise any downstream target that + # actually pulls in that code (such as the 11_peibos example) fails to link + # with an undefined reference to pthread_create. + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) + set(CODAC_VERSION ${PROJECT_VERSION}) - set(CODAC_LIBRARIES \${CODAC_CORE_LIBRARY} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_UNSUPPORTED_LIBRARY} Codac::gaol) + set(CODAC_LIBRARIES \${CODAC_CORE_LIBRARY} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_UNSUPPORTED_LIBRARY} Codac::gaol Threads::Threads) set(CODAC_INCLUDE_DIRS \${CODAC_CORE_INCLUDE_DIR}/../ \${CODAC_CORE_INCLUDE_DIR}/../eigen3/ \${CODAC_CORE_INCLUDE_DIR} \${CODAC_GRAPHICS_INCLUDE_DIR} \${CODAC_UNSUPPORTED_INCLUDE_DIR}) set(CODAC_C_FLAGS \"\") @@ -169,7 +178,7 @@ ${CODAC_GAOL_CONFIG_SNIPPET} endif() file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " - set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY} Codac::gaol) + set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY} Codac::gaol Threads::Threads) ") From 280d7658b5a589518f6d98fbdfc2a8e857cac880 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 06/19] Enable ASan/UBSan in Debug builds when the toolchain ships the runtime --- CMakeLists.txt | 206 +++++++++++++++++++- python/CMakeLists.txt | 4 +- python/src/core/CMakeLists.txt | 56 ++++++ scripts/CMakeModules/codac_gaol.cmake | 10 +- tests/CMakeLists.txt | 260 +++++++++++++++++++++++++- 5 files changed, 525 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 58bcc1605..ca3830f55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,12 +121,212 @@ # having to build gaol in Debug as well. Release # itself already defaulted to /MD, so in practice this only changes # what the Debug configuration uses. Debug builds still get no - # optimization (/Od) and full debug symbols (/Zi) -- what is traded away - # is only the *separate* debug-CRT extras that /MDd would otherwise add - # on top of that (its own heap debugging and iterator-debug-level checks). + # optimization (/Od), full debug symbols (/Zi) and AddressSanitizer + # (see CODAC_MSVC_ASAN_RUNTIME_DLL below) -- what is traded away is + # only the *separate* debug-CRT extras that /MDd would otherwise add on + # top of that (its own heap debugging and iterator-debug-level checks). set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") endif() + if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_CONFIGURATION_TYPES) + # CMAKE_BUILD_TYPE only has meaning for single-configuration generators + # (Makefiles, Ninja): multi-configuration generators (Visual Studio, + # Xcode, Ninja Multi-Config -- notably what this project's own CI + # drives MSVC through, via `-G "Visual Studio 17/18"` in + # .github/workflows/*matrix.yml) instead select the actual + # configuration later, at build time (cmake --build . --config + # Debug), not at configure time; CMAKE_BUILD_TYPE stays whatever it + # was defaulted to above ("Release") regardless of what configuration + # is later built. So this outer if() only decides whether it is worth + # *probing* for Debug-only capabilities at all -- true either when a + # single-config build was explicitly configured as Debug, or when a + # multi-config generator defers that choice (CMAKE_CONFIGURATION_TYPES + # is then non-empty) and so might still end up building a Debug + # configuration later. The actual compile/link options below are + # separately wrapped in a $ generator expression, which + # -- unlike CMAKE_BUILD_TYPE -- CMake evaluates correctly against + # whichever configuration is ultimately built, for both single- and + # multi-config generators alike. + if(MSVC) + # cl.exe silently ignores GCC/Clang-style "-f..."/"-g"/"-O0" flags + # with a D9002 warning rather than failing the build, so without this + # branch a Debug build under MSVC would quietly end up *unsanitized* + # instead of erroring -- a behavior gap invisible until someone + # actually diffs test coverage between platforms. + # + # MSVC's AddressSanitizer (/fsanitize=address) has shipped since + # Visual Studio 16.9; there is no UndefinedBehaviorSanitizer on this + # toolchain, so Debug builds here get ASan coverage only, not the + # ASan+UBSan combination GCC/Clang get below. Its runtime is a DLL + # shipped next to cl.exe, rather than a LD_PRELOAD-style shared + # object that the dynamic loader pulls in transparently: *every* + # executable and DLL built with /fsanitize=address needs that + # runtime DLL to be discoverable at load time (next to the binary, + # or on PATH) -- per Microsoft's own documentation, this holds even + # for /MT static-CRT builds, unlike every other CRT component. + # See tests/CMakeLists.txt and python/src/core/CMakeLists.txt for + # how CODAC_MSVC_ASAN_RUNTIME_DLL, set below, is used to make that + # DLL discoverable for the test suite and for the Python bindings. + # + # Source: https://learn.microsoft.com/en-us/cpp/sanitizers/asan-runtime + # + # Rather than enable /fsanitize=address unconditionally, this whole + # feature is opt-in on whether the matching runtime DLL can actually + # be located next to this cl.exe: if it can't (toolset too old, + # target architecture unsupported by MSVC ASan, or a packaging + # layout this check doesn't recognize), Debug builds simply stay + # unsanitized rather than either failing configuration or compiling + # binaries that are guaranteed to fail to load at run time with a + # missing-DLL error. + # + # MSVC ASan runtime binaries are named using Clang's architecture + # conventions (i386/x86_64/aarch64), not MSVC's own (X86/x64/ARM64) -- + # this is documented by Microsoft itself, not a codac convention. + # CMAKE_CXX_COMPILER_ARCHITECTURE_ID (a builtin CMake variable, MSVC + # only) reports the *target* architecture regardless of host, which + # is what a flag like /fsanitize=address needs to match; MSVC + # AddressSanitizer only supports x86, x64 and ARM64 (ARM64 as of + # VS 2022 17.9, as a preview) -- any other value (32-bit ARM, IA64, + # ARM64EC...) is simply left unmapped below, so the search for a + # runtime DLL naturally comes up empty and the feature is skipped, + # exactly as intended. + set(_msvc_asan_dll_arch "") + if(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "x64") + set(_msvc_asan_dll_arch "x86_64") + elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "X86") + set(_msvc_asan_dll_arch "i386") + elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "ARM64") + set(_msvc_asan_dll_arch "aarch64") + endif() + + set(CODAC_MSVC_ASAN_RUNTIME_DLL "") + if(_msvc_asan_dll_arch) + get_filename_component(_msvc_bin_dir "${CMAKE_CXX_COMPILER}" DIRECTORY) + # Visual Studio 2022 17.7 Preview 3 unified every CRT configuration + # (/MT, /MTd, /MD, /MDd) onto a single clang_rt.asan_dynamic-{arch}.dll. + # Older toolsets instead shipped a separate debug-CRT DLL + # (clang_rt.asan_dbg_dynamic-{arch}.dll) for /MDd specifically. + # CMAKE_MSVC_RUNTIME_LIBRARY is forced to the Release CRT (/MD) for + # every configuration above (Debug included), so in practice only + # the plain, non-debug DLL is ever needed here now; the /MDd-only + # name is kept as a fallback purely in case that override above is + # ever relaxed back to the CMake default. Rather than pin an exact + # MSVC_VERSION cutoff for the 17.7-era switch -- a compiler-rt + # packaging detail, not something reported by any compiler flag -- + # just look for whichever of the two names actually exists next to + # this cl.exe, newest first. + foreach(_dll_name + "clang_rt.asan_dynamic-${_msvc_asan_dll_arch}.dll" + "clang_rt.asan_dbg_dynamic-${_msvc_asan_dll_arch}.dll" + ) + if(EXISTS "${_msvc_bin_dir}/${_dll_name}") + set(CODAC_MSVC_ASAN_RUNTIME_DLL "${_msvc_bin_dir}/${_dll_name}") + break() + endif() + endforeach() + endif() + + if(CODAC_MSVC_ASAN_RUNTIME_DLL) + add_compile_options($<$:/fsanitize=address>) + # Incremental linking cannot be combined with ASan: the linker drops + # /INCREMENTAL as soon as an input module carries ASan metadata, and + # says so once per link (warning LNK4300, 870 times across the five + # MSVC Debug jobs of .github/workflows/windebugmatrix.yml, VS 2022 and + # VS 2026 alike). Asking for it off removes the cause rather than the + # message, and leaves LNK4300 free to report any other option ASan + # would silently disable. One item per generator expression, for the + # DIRECTORY-scope reason explained below. + add_link_options($<$:/INCREMENTAL:NO>) + # Enabling ASan on MSVC also switches on the MSVC STL's own + # "container annotation" instrumentation for std::string, + # std::vector and (recent toolsets) std::optional: it poisons + # their reserved-but-unused capacity so ASan can catch + # out-of-bounds access within it, and every translation unit + # linked together records, via #pragma detect_mismatch, whether it + # was compiled with that instrumentation on or off. gaol.lib + # (a Release build without ASan -- see the + # CMAKE_MSVC_RUNTIME_LIBRARY comment above for why it is not + # built in Debug here) was compiled without ASan, so its + # object files carry the instrumentation-off value; without this, + # codac's own ASan-instrumented Debug object files would carry the + # opposite value, and MSVC's linker would reject the mix with a + # second error LNK2038 ("mismatch detected for 'annotate_string' / + # 'annotate_vector' / 'annotate_optional'"), on top of the CRT one + # already addressed above. _DISABLE_STRING_ANNOTATION and + # _DISABLE_VECTOR_ANNOTATION are Microsoft's own documented way to + # turn this instrumentation back off so codac's object files match + # gaol.lib's; _DISABLE_OPTIONAL_ANNOTATION is added defensively for + # the same std::optional check (undocumented as of this writing, + # but following Microsoft's existing naming convention for the + # other two) -- worst case it is simply an unused macro if that + # name turns out to be wrong. Kept as three separate + # add_compile_definitions() calls, one flag per $ + # generator expression, for the same reason as the single-flag + # add_compile_options() calls further below: this is a + # DIRECTORY-scope property, which does not evaluate correctly when + # one such expression packs more than one ';'-separated item. + add_compile_definitions($<$:_DISABLE_STRING_ANNOTATION>) + add_compile_definitions($<$:_DISABLE_VECTOR_ANNOTATION>) + add_compile_definitions($<$:_DISABLE_OPTIONAL_ANNOTATION>) + message(STATUS "${COLOR_BLUE}MSVC AddressSanitizer will be enabled for Debug-configuration builds (runtime: ${CODAC_MSVC_ASAN_RUNTIME_DLL}).${COLOR_RESET}") + else() + message(STATUS "${COLOR_RED}No MSVC AddressSanitizer runtime found next to ${CMAKE_CXX_COMPILER} for architecture '${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}': Debug-configuration builds will not be sanitized.${COLOR_RESET}") + endif() + + # Cached so python/src/core/CMakeLists.txt and tests/CMakeLists.txt + # can reuse this result (empty string if not found) without + # repeating the detection above. + set(CODAC_MSVC_ASAN_RUNTIME_DLL ${CODAC_MSVC_ASAN_RUNTIME_DLL} CACHE INTERNAL "Path to the MSVC ASan runtime DLL, empty if unavailable") + else() + # add_compile_options() (a DIRECTORY-scope property, unlike + # target_compile_options()) does not evaluate correctly when a single + # $ generator expression's content packs more than one + # flag separated by ';' -- reproduced locally with both Ninja and + # Unix Makefiles (CMake 3.16 and 4.4.3 alike): the embedded ';' gets + # split before the genexpr is fully evaluated, corrupting it into + # literal, unrecognized compiler arguments (e.g. the literal text + # "$<1:-Wreturn-type" as one broken token). target_compile_options() + # is not affected, but this is DIRECTORY-scope by design (it must + # apply project-wide, not to one target), so instead each flag gets + # its own single-flag generator expression below. + add_compile_options($<$:-Wreturn-type>) + add_compile_options($<$:-fno-omit-frame-pointer>) + add_compile_options($<$:-g>) + add_compile_options($<$:-O0>) + + # -fsanitize=address,undefined is only added once we have actually + # confirmed that this compiler can *link* a sanitized binary, rather + # than unconditionally, the way this project used to. On Linux/macOS + # GCC/Clang, libasan/libubsan are essentially always installed + # alongside the compiler and this check is a formality; the case it + # actually guards against is MinGW-w64: the choco `mingw` package used + # by .github/workflows/windebugmatrix.yml does not ship libasan.a/ + # libubsan.a at all for some GCC versions/architectures, and neither + # x86 (i686-w64-mingw32) nor x86_64 (x86_64-w64-mingw32) is reliably + # covered across the versions that package offers -- there is no + # version/architecture combination worth hardcoding an exception list + # for. Probing capability directly, exactly like the MSVC ASan + # runtime-DLL search above, means + # a Debug configuration simply comes out unsanitized on a toolchain + # that lacks the runtime, instead of configuring successfully and then + # failing later at link time with "cannot find -lasan"/"-lubsan" on + # every single target. + include(CheckCXXSourceCompiles) + set(CMAKE_REQUIRED_FLAGS "-fsanitize=address,undefined") + check_cxx_source_compiles("int main() { return 0; }" CODAC_COMPILER_SUPPORTS_ASAN_UBSAN) + unset(CMAKE_REQUIRED_FLAGS) + + if(CODAC_COMPILER_SUPPORTS_ASAN_UBSAN) + add_compile_options($<$:-fsanitize=address>) + add_compile_options($<$:-fsanitize=undefined>) + add_link_options($<$:-fsanitize=address,undefined>) + message(STATUS "${COLOR_BLUE}ASan/UBSan will be enabled for Debug-configuration builds.${COLOR_RESET}") + else() + message(STATUS "${COLOR_RED}No ASan/UBSan runtime found for ${CMAKE_CXX_COMPILER}: Debug-configuration builds will not be sanitized.${COLOR_RESET}") + endif() + endif() + endif() + # Temporary attempts to fix errors similar to: # _ number of sections exceeded object file format limit. # _ out of memory allocating XXX bytes. diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index bab5e84ba..845097694 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -17,7 +17,9 @@ # Adds pybind11::headers, pybind11::module, pybind11::embed set(PYTHON_PACKAGE_NAME ${PROJECT_NAME}) - set(PYTHON_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_package") + set(PYTHON_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_package" + CACHE INTERNAL "Codac Python package build directory" + ) file(MAKE_DIRECTORY ${PYTHON_PACKAGE_DIR}) execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/${PYTHON_PACKAGE_NAME}/" "${PYTHON_PACKAGE_DIR}/${PYTHON_PACKAGE_NAME}") diff --git a/python/src/core/CMakeLists.txt b/python/src/core/CMakeLists.txt index 679e254f0..506b0f914 100644 --- a/python/src/core/CMakeLists.txt +++ b/python/src/core/CMakeLists.txt @@ -160,6 +160,62 @@ PRIVATE ${PROJECT_NAME}-core ${PROJECT_NAME}-sympy Threads::Threads Codac::gaol ) + # -------------------------------------------------------------- + # Sanitizer runtime as a shared library (Debug builds only) + # -------------------------------------------------------------- + # + # _core.so is loaded via dlopen() by an unsanitized python + # interpreter. By default Clang/GCC embed the ASan/UBSan runtime + # statically into shared libraries, which leaves symbols like + # __ubsan_vptr_type_cache or __asan_* unresolved at dlopen() time + # (python has no sanitizer runtime of its own to provide them). + # -shared-libsan makes _core.so depend on the sanitizer runtime as + # a proper shared library instead; that runtime is then LD_PRELOAD-ed + # for the python tests (see tests/CMakeLists.txt). + # Not gated behind CMAKE_BUILD_TYPE (see the matching note in the root + # CMakeLists.txt): that variable is meaningless for multi-configuration + # generators, so -shared-libsan is applied via a $ + # generator expression instead, which CMake evaluates correctly for + # both single- and multi-config generators. + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(_core PRIVATE $<$:-shared-libsan>) + target_link_options(_core PRIVATE $<$:-shared-libsan>) + endif() + + # -------------------------------------------------------------- + # MSVC AddressSanitizer runtime DLL (Debug builds only) + # -------------------------------------------------------------- + # + # /fsanitize=address (enabled in Debug mode only if a matching runtime + # was found -- see the MSVC branch in the root CMakeLists.txt, which + # sets CODAC_MSVC_ASAN_RUNTIME_DLL) makes _core.pyd import that runtime + # DLL. Unlike Clang/GCC on Linux, there is no LD_PRELOAD-style env var + # and no "must be the first library loaded" requirement to work around + # on Windows -- the loader simply needs to be able to find that DLL when + # Python calls LoadLibrary() on _core.pyd, which it does by searching + # the directory of the loading module before PATH. So it is enough to + # copy the runtime DLL next to the built _core.pyd, the same way + # _core.pyd itself is already copied into the package folder below. + # CODAC_MSVC_ASAN_RUNTIME_DLL is already empty (and this is then a + # no-op) whenever the root CMakeLists.txt couldn't locate that runtime, + # so /fsanitize=address was in that case never even added. + if(MSVC AND CODAC_MSVC_ASAN_RUNTIME_DLL) + # CODAC_MSVC_ASAN_RUNTIME_DLL is now found regardless of which + # configuration will be built (see the root CMakeLists.txt), since a + # multi-config generator does not know that yet at configure time. So + # the copy itself has to stay conditional on the configuration that is + # actually being built, which a plain if() cannot express: + # $,copy_if_different,echo> selects between the real + # "-E copy_if_different" and "-E echo", which merely prints the two + # paths, so the DLL is only copied for a Debug-configuration build, in + # any generator. "-E true" would read better, but it only exists since + # CMake 3.16 while this project accepts 3.14. + add_custom_command(TARGET _core POST_BUILD + COMMAND ${CMAKE_COMMAND} -E $,copy_if_different,echo> "${CODAC_MSVC_ASAN_RUNTIME_DLL}" "${PYTHON_PACKAGE_DIR}/${PYTHON_PACKAGE_NAME}" + ) + endif() + + # Copy the generated library in the package folder add_custom_command(TARGET _core POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$" "${PYTHON_PACKAGE_DIR}/${PYTHON_PACKAGE_NAME}" diff --git a/scripts/CMakeModules/codac_gaol.cmake b/scripts/CMakeModules/codac_gaol.cmake index 631b63c8d..83b58f0a8 100644 --- a/scripts/CMakeModules/codac_gaol.cmake +++ b/scripts/CMakeModules/codac_gaol.cmake @@ -306,11 +306,11 @@ endfunction() # A project of its own, as in IBEX's build of GAOL with its autotools, rather # than the FetchContent that Eigen and Catch2 are brought in with, which would # build GAOL as a part of this project. Kept apart, GAOL is compiled with the -# flags it chooses, and not with Codac's warnings, and it is always built in -# Release, whatever the configuration of Codac, which is what the MSVC runtime -# choice of the top-level CMakeLists.txt counts on. CMAKE_CXX_FLAGS and -# CMAKE_C_FLAGS are handed over as they are when this is called, before Codac -# adds its own flags to them. +# flags it chooses, and not with Codac's warnings and sanitizers, and it is +# always built in Release, whatever the configuration of Codac, which is what +# the MSVC runtime choice of the top-level CMakeLists.txt counts on. +# CMAKE_CXX_FLAGS and CMAKE_C_FLAGS are handed over as they are when this is +# called, before Codac adds its own flags to them. # # GAOL comes from the head of the master branch of the fork (version 4.3.1 of # GAOL), so that the fixes pushed to the fork reach Codac without a change here. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8dabdd6ad..828f6416e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -160,6 +160,249 @@ endif() #message(STATUS "Found IBEX version ${IBEX_VERSION}") #endif() +# ------------------------------------------------------------------ +# Sanitizer runtime preload for Python tests (Debug builds only) +# ------------------------------------------------------------------ +# +# In Debug mode, _core.so (the pybind11 module) is compiled with +# -fsanitize=address,undefined and depends on a *shared* sanitizer +# runtime rather than one statically embedded in the .so: +# - Clang needs an explicit -shared-libsan flag to get that (see +# python/src/core/CMakeLists.txt) -- its default is to embed the +# runtime statically into shared libraries. +# - GCC needs no flag at all: unless -static-libasan/-static-libubsan +# is passed (which codac never does), GCC already links libasan.so / +# libubsan.so dynamically by default. Passing Clang's -shared-libsan +# to GCC is not an option either way -- GCC's driver rejects it +# outright as an unrecognized flag. +# Either way, since python3 itself has no sanitizer runtime, that shared +# runtime must be preloaded before Python imports codac, otherwise symbols +# like __ubsan_vptr_type_cache/__asan_* stay unresolved -- or, on GCC, the +# module load aborts immediately with "ASan runtime does not come first in +# initial library list" (reproduced locally: a plain dlopen(), from an +# unsanitized host, of a GCC ASan-built .so fails exactly this way without +# LD_PRELOAD, and succeeds once libasan.so is preloaded). + +set(PYTHON_TEST_ENV_ARGS "PYTHONPATH=${PYTHON_PACKAGE_DIR}") + +# Not gated behind a plain CMAKE_BUILD_TYPE STREQUAL "Debug" check (see the +# matching note in the root CMakeLists.txt): that variable is meaningless +# for multi-configuration generators, which instead pick the actual +# configuration later, at build time. CMAKE_CONFIGURATION_TYPES is set +# precisely on those generators, so probing here also runs whenever it is +# non-empty -- the individual LD_PRELOAD/ASAN_OPTIONS/UBSAN_OPTIONS entries +# appended below are themselves wrapped in a $ generator +# expression (see how PYTHON_TEST_ENV_ARGS is consumed via the ENVIRONMENT +# test property further down), so they still only take effect for an +# actual Debug-configuration test run. +if(WITH_PYTHON AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_CONFIGURATION_TYPES) + AND CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU" AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + + # This whole block is Linux-specific by construction, not just by + # "is the compiler Clang or GCC": LD_PRELOAD is a Linux/glibc loader + # feature with no equivalent environment variable on Windows, and the + # shared runtime file names it looks for below only exist in that form + # on Linux -- macOS uses DYLD_INSERT_LIBRARIES and ships its runtimes + # under different names (e.g. Clang's combined, no-arch-suffix + # libclang_rt.asan_osx_dynamic.dylib), and Windows uses neither an + # env-var preload mechanism nor this naming (clang-cl names them + # clang_rt.asan_dynamic-.dll; MinGW-w64 GCC ships asan/ubsan as + # DLLs with their own layout). Real MSVC (cl.exe) is already excluded + # by the CMAKE_CXX_COMPILER_ID check above, but clang-cl (Windows) and + # AppleClang (macOS) both also match "Clang" (and MinGW GCC still + # matches "GNU"), so without this explicit Linux check they would + # silently fall through to the "no shared runtime found" warning below + # instead of making clear that the feature just does not apply there. + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + + # Note: `clang --print-runtime-dir` assumes a per-target-runtime-dir + # layout (lib//) which some distro packages (e.g. Ubuntu/ + # Debian's llvm.org apt packages) do not use, instead keeping the legacy + # lib/linux/ layout: the reported directory would not exist there, even + # though the runtime libraries are actually installed. `-print-file-name` + # asks the driver to resolve each file the way it would at link time, + # which works regardless of which layout the distro packaging uses. + # + # The runtime file names themselves are not portable either: Clang embeds + # its own architecture name in them (e.g. libclang_rt.asan-aarch64.so on + # an AArch64 Raspberry Pi OS, libclang_rt.asan-armhf.so on a 32-bit + # ARMv7/ARMv8 hard-float userland). Rather than maintaining our own + # CMAKE_SYSTEM_PROCESSOR -> Clang-runtime-name table (which would drift + # out of sync as LLVM adds targets), ask the compiler itself: + # `-dumpmachine` prints its default target triple (understood by both + # Clang and GCC, and already normalized -- e.g. a 32-bit x86 compiler + # reports "i386", never "i686"), and its first field is, for essentially + # every architecture, precisely the suffix Clang's runtime uses. + # + # The one documented exception is 32-bit ARM, where the suffix instead + # follows the float ABI recorded in the triple's environment field + # (.../gnueabihf -> "armhf" hard-float, vs. plain "arm" otherwise) rather + # than the architecture field (which is typically "arm"/"armv7"/...) -- + # this is an LLVM naming convention, not something any compiler flag + # reports directly, so it is the one case still special-cased below. + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -dumpmachine + OUTPUT_VARIABLE _clang_target_triple + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(_sanitizer_rt_arch "") + if(_clang_target_triple) + string(REPLACE "-" ";" _clang_triple_fields "${_clang_target_triple}") + list(GET _clang_triple_fields 0 _sanitizer_rt_arch) + if(_sanitizer_rt_arch MATCHES "^armv?[0-9]*l?$") + if(_clang_target_triple MATCHES "eabihf$") + set(_sanitizer_rt_arch "armhf") + else() + set(_sanitizer_rt_arch "arm") + endif() + endif() + endif() + + # The ASan runtime only, deliberately: built with -fsanitize=address, + # undefined, Clang puts the UndefinedBehaviorSanitizer inside the ASan + # runtime, and ships libclang_rt.ubsan_standalone-* for the builds that + # use UBSan on its own. Preloading both therefore loads two copies of the + # same initialisation and the same interceptors into one process. Clang 18 + # tolerated it; Clang 21 wedges the interpreter so thoroughly that even + # SIGKILL does not remove it, which is what made every Python test of the + # Clang jobs of .github/workflows/unixdebug.yml run into its timeout while + # the GCC jobs of the same matrix passed them in about two seconds -- + # GCC's libasan and libubsan being genuinely separate libraries, designed + # to be loaded together. Preloading ASan alone still gives both sets of + # checks, since one runtime implements them both. + set(_sanitizer_rt_candidates "") + if(_sanitizer_rt_arch) + set(_sanitizer_rt_candidates + libclang_rt.asan-${_sanitizer_rt_arch}.so + ) + endif() + + else() # GNU (GCC) + + # Unlike Clang, GCC's driver is built for one target only, so its + # runtime file names carry no architecture suffix -- `-print-file-name` + # already resolves to the right library for whatever target this GCC + # was built for, on every architecture. GCC also splits ASan and UBSan + # into two separately-named libraries (libasan.so / libubsan.so), + # rather than Clang's libclang_rt.asan-*/libclang_rt.ubsan_standalone-* + # naming. + set(_sanitizer_rt_candidates libasan.so libubsan.so) + + endif() + + set(SANITIZER_RT_LIBS "") + foreach(_rt_lib_name ${_sanitizer_rt_candidates}) + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=${_rt_lib_name} + OUTPUT_VARIABLE _rt_lib_path + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(EXISTS "${_rt_lib_path}") + list(APPEND SANITIZER_RT_LIBS "${_rt_lib_path}") + endif() + endforeach() + + if(SANITIZER_RT_LIBS) + list(JOIN SANITIZER_RT_LIBS ":" SANITIZER_RT_LIBS_JOINED) + list(APPEND PYTHON_TEST_ENV_ARGS "$<$:LD_PRELOAD=${SANITIZER_RT_LIBS_JOINED}>") + + # LeakSanitizer (part of ASan) flags many long-lived, process-lifetime + # allocations made by the Python interpreter and by pybind11 module + # init (interned strings, type objects, strdup'd docstrings...) as + # "leaks", since it can't trace Python's own object graph. This is a + # well-known false-positive source for embedded Python interpreters, + # not something codac's code controls. UBSan and ASan's other checks + # (use-after-free, buffer overflow, UB...) remain fully active. + list(APPEND PYTHON_TEST_ENV_ARGS "$<$:ASAN_OPTIONS=detect_leaks=0>") + list(APPEND PYTHON_TEST_ENV_ARGS "$<$:UBSAN_OPTIONS=print_stacktrace=1>") + message(STATUS "Python tests: LD_PRELOAD=${SANITIZER_RT_LIBS_JOINED}") + else() + message(WARNING + "Debug build with WITH_PYTHON: no shared ASan/UBSan runtime found " + "for ${CMAKE_CXX_COMPILER}. With Clang, make sure the _core target " + "is linked with -shared-libsan; with GCC, make sure it is not " + "linked with -static-libasan/-static-libubsan. Otherwise Python " + "tests will fail with 'undefined symbol' errors, or abort with " + "'ASan runtime does not come first in initial library list'.") + endif() + +endif() + +# ------------------------------------------------------------------ +# Sanitizer runtime discoverability for MSVC (Debug builds only) +# ------------------------------------------------------------------ +# +# CODAC_MSVC_ASAN_RUNTIME_DLL is set by the root CMakeLists.txt (empty if +# no matching runtime could be found, in which case /fsanitize=address was +# never even added -- nothing to do here either). Windows has no +# LD_PRELOAD-style env var and no "must load first" requirement: the +# loader just needs to be able to find the runtime DLL via PATH or next +# to the binary that imports it, when that binary is loaded. Python's +# _core.pyd gets its own copy placed next to it (see +# python/src/core/CMakeLists.txt); the plain C++ test executables and the +# python.exe launched below have no such fixed "next to" location, so +# their directory search is extended via PATH instead, the direct +# Windows analogue of prepending to LD_PRELOAD above. +set(CODAC_MSVC_ASAN_PATH_ENV "") +if(MSVC AND CODAC_MSVC_ASAN_RUNTIME_DLL) + get_filename_component(_msvc_asan_dll_dir "${CODAC_MSVC_ASAN_RUNTIME_DLL}" DIRECTORY) + # Both ctest's ENVIRONMENT test property and the COMMAND argument list + # passed to `cmake -E env` below are themselves ';'-separated CMake + # lists, so the ';' that Windows uses to separate PATH entries has to be + # escaped as '\;' here -- otherwise CMake would silently split this one + # "PATH=......" value into several unrelated list entries. + # + # NOTE: $ENV{PATH} is captured at CMake *configure* time, i.e. whatever + # PATH was active when `cmake` was run to configure the project. If + # ctest is later invoked from a shell with a materially different PATH, + # this won't reflect that -- a limitation inherent to baking an + # environment value into generated build files, not specific to this + # check. + string(REPLACE ";" "\\;" _msvc_asan_path_escaped "${_msvc_asan_dll_dir};$ENV{PATH}") + set(CODAC_MSVC_ASAN_PATH_ENV "PATH=${_msvc_asan_path_escaped}") + # Appended for every configuration rather than wrapped in a $ + # generator expression. `cmake -E env` reads its arguments as VAR=value + # assignments up to the first one that is not, which it then takes for the + # command to run: an element that a generator expression reduced to nothing + # leaves an empty argument in the middle of the list, and the command becomes + # the empty string. CODAC_MSVC_ASAN_RUNTIME_DLL is computed whatever the + # configuration (see the root CMakeLists.txt), so on MSVC the element was + # always there and always empty outside Debug -- every Python test of + # .github/workflows/vcmatrix.yml died on "no such file or directory" while + # the C++ ones passed. Prepending the sanitizer directory to PATH costs a + # Release run nothing, which is what the C++ tests below already rely on. + list(APPEND PYTHON_TEST_ENV_ARGS "${CODAC_MSVC_ASAN_PATH_ENV}") +endif() + + +# Environment of the C++ tests and examples +# ================================================================== +# +# The same environment is needed by every C++ binary this project runs under +# ctest, the examples of examples/CMakeLists.txt included, so it is built once +# here and exported to the parent scope rather than recomputed per test. +# +# UBSAN_OPTIONS only means something to the GCC/Clang single-configuration +# builds, where CMAKE_BUILD_TYPE holds the answer. The MSVC AddressSanitizer +# runtime is a DLL that has to be discoverable at load time by every binary +# compiled with /fsanitize=address: without it on PATH a Debug test cannot even +# start, Windows failing the process creation with 0xC0000135 +# (STATUS_DLL_NOT_FOUND) before a single assertion runs. It is added whatever +# the configuration, because the multi-configuration generators MSVC is driven +# through only choose theirs at build time, and prepending a toolchain +# directory to PATH costs a Release run nothing. + +set(CODAC_CPP_TEST_ENV "") + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND CODAC_CPP_TEST_ENV "UBSAN_OPTIONS=print_stacktrace=1") +endif() + +if(CODAC_MSVC_ASAN_PATH_ENV) + list(APPEND CODAC_CPP_TEST_ENV "${CODAC_MSVC_ASAN_PATH_ENV}") +endif() + foreach(SRC_TEST ${SRC_TESTS}) string(REPLACE "/" "_" TEST_NAME ${SRC_TEST}) string(REPLACE "codac2_tests_" "" TEST_NAME ${TEST_NAME}) @@ -177,9 +420,22 @@ foreach(SRC_TEST ${SRC_TESTS}) add_dependencies(check ${TEST_NAME}) add_test(NAME ${TEST_NAME}_cpp COMMAND ${TEST_NAME}) + if(CODAC_CPP_TEST_ENV) + set_tests_properties(${TEST_NAME}_cpp PROPERTIES + ENVIRONMENT "${CODAC_CPP_TEST_ENV}" + ) + endif() + # Python test - if(WITH_PYTHON) - add_test(NAME ${TEST_NAME}_py COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.py) + if(WITH_PYTHON AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.py") + add_test( + NAME ${TEST_NAME}_py + COMMAND + ${CMAKE_COMMAND} -E env + ${PYTHON_TEST_ENV_ARGS} + ${PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.py + ) endif() endforeach() From a350f5232fff32f599fa54d5813effdae748a817 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 07/19] Update the examples to axis() in set_axes and draw every subset --- examples/09_robot_simu/main.cpp | 4 ++-- examples/11_peibos/main.cpp | 4 ++-- examples/custom_sep/custom_sep.py | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/09_robot_simu/main.cpp b/examples/09_robot_simu/main.cpp index 5882ff8d8..e23a55d08 100644 --- a/examples/09_robot_simu/main.cpp +++ b/examples/09_robot_simu/main.cpp @@ -14,8 +14,8 @@ int main() Figure2D g("Robot simulation", GraphicOutput::VIBES | GraphicOutput::IPE); g.set_axes( - {0,x.codomain()[0].inflate(1.)}, - {1,x.codomain()[1].inflate(1.)} + axis(0,x.codomain()[0].inflate(1.)), + axis(1,x.codomain()[1].inflate(1.)) ).auto_scale(); g.draw_tank(x(5.), 0.5, {Color::black(),Color::yellow()}); diff --git a/examples/11_peibos/main.cpp b/examples/11_peibos/main.cpp index e55ad6d72..9d0701fa4 100644 --- a/examples/11_peibos/main.cpp +++ b/examples/11_peibos/main.cpp @@ -22,7 +22,7 @@ int main() Figure2D figure_2d ("Henon Map", GraphicOutput::VIBES); figure_2d.set_window_properties({25,50},{500,500}); - figure_2d.set_axes({0,{-1.4,2.2}}, {1,{-0.4,0.3}}); + figure_2d.set_axes(axis(0,{-1.4,2.2}), axis(1,{-0.4,0.3})); for (const auto& p : v_par_2d) { @@ -49,7 +49,7 @@ int main() Figure2D figure_3d_proj ("Conform projected", GraphicOutput::VIBES); figure_3d_proj.set_window_properties({25,600},{500,500}); - figure_3d_proj.set_axes({0,{-1.5,2.5}}, {1,{-2,2}}); + figure_3d_proj.set_axes(axis(0,{-1.5,2.5}), axis(1,{-2,2})); auto v_par_3d = PEIBOS(f_3d, psi0_3d, {id_3d,s1,s1*s1,s1.invert(),s2,s2.invert()}, 0.05, true); diff --git a/examples/custom_sep/custom_sep.py b/examples/custom_sep/custom_sep.py index 4bb71a10c..36cffa7bd 100644 --- a/examples/custom_sep/custom_sep.py +++ b/examples/custom_sep/custom_sep.py @@ -36,5 +36,6 @@ def separate(self, x): # # c = p.connected_subsets(outer_complem) -for bi in c[1].boxes(): - DefaultFigure.draw_box(bi,[Color.red(),Color.red()]) \ No newline at end of file +for ci in c: + for bi in ci.boxes(): + DefaultFigure.draw_box(bi,[Color.red(),Color.red()]) \ No newline at end of file From c2e335754924112673ee79394d1b1f387d8e23f9 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 08/19] Compile against the headers in src/ instead of build/include copies --- CMakeLists.txt | 36 +++++++++ python/src/core/CMakeLists.txt | 7 +- python/src/graphics/CMakeLists.txt | 7 +- python/src/unsupported/CMakeLists.txt | 7 +- src/CMakeLists.txt | 36 ++++++++- src/core/CMakeLists.txt | 55 ++++++++++--- src/extensions/capd/CMakeLists.txt | 35 ++++++-- src/extensions/sympy/CMakeLists.txt | 39 +++++++-- src/graphics/CMakeLists.txt | 36 +++++++-- src/unsupported/CMakeLists.txt | 43 +++++++--- tests/CMakeLists.txt | 112 ++++++++++++++++++++++++-- 11 files changed, 357 insertions(+), 56 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca3830f55..8c12977dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -404,6 +404,42 @@ string(APPEND CMAKE_CXX_FLAGS " ${_codac_portability_cxx_flags}") + # Records the directories in which a module keeps its public headers, in + # their canonical location under src/. Every consumer (the tests, the + # examples and the Python bindings) lists CODAC_SOURCE_INCLUDE_DIRS first, + # so that a header is opened at src/... rather than through a copy in the + # build tree. Both compile identically, but the path used is the path gcov + # records: reached both ways, a header ends up as two unrelated entries in + # the coverage report, each showing only what its own callers exercised, + # and is reported well below its real coverage. + # + # The variable is a cache entry because it is filled in by src/*/CMakeLists.txt + # and read from tests/, examples/ and python/, which are separate directory + # scopes; src/CMakeLists.txt empties it before the modules append to it, so + # that a reconfigure does not append to what the previous one left behind. + function(codac_publish_include_dirs) + set(CODAC_SOURCE_INCLUDE_DIRS ${CODAC_SOURCE_INCLUDE_DIRS} ${ARGN} + CACHE INTERNAL "Directories holding the public headers of the codac modules") + endfunction() + + + # The companion of the above, for the umbrella headers CMake generates rather + # than the ones kept in the source tree: codac-core.h, codac-graphics.h and + # the rest. Each module writes its own into its own binary directory, so + # ${CMAKE_BINARY_DIR}/src -- where the tests used to look, and the only place + # they looked -- holds none of them. Only the CAPD snippet of the manual + # includes them (every other test reaches for a codac2_*.h directly), which + # is why the omission surfaced as nothing but the CAPD jobs failing on + # "codac-core.h: No such file or directory". + # + # It is a cache entry, and emptied by src/CMakeLists.txt before the modules + # fill it, for the same reasons as CODAC_SOURCE_INCLUDE_DIRS above. + function(codac_publish_generated_include_dirs) + set(CODAC_GENERATED_INCLUDE_DIRS ${CODAC_GENERATED_INCLUDE_DIRS} ${ARGN} + CACHE INTERNAL "Directories holding the umbrella headers generated for the codac modules") + endfunction() + + ################################################################################ # Looking for Eigen3 ################################################################################ diff --git a/python/src/core/CMakeLists.txt b/python/src/core/CMakeLists.txt index 506b0f914..dca29011e 100644 --- a/python/src/core/CMakeLists.txt +++ b/python/src/core/CMakeLists.txt @@ -133,8 +133,13 @@ ) target_include_directories(_core + # First, so that a header is opened at src/... rather than through + # build/include: the path a translation unit used is the path gcov records, + # and the two spellings would split every header into two entries in the + # coverage report. See codac_publish_include_dirs() in the top-level CMakeLists.txt. + PRIVATE ${CODAC_SOURCE_INCLUDE_DIRS} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../docstring - PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../../include + PRIVATE ${CMAKE_BINARY_DIR}/src PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/actions/ diff --git a/python/src/graphics/CMakeLists.txt b/python/src/graphics/CMakeLists.txt index 79f7639f8..b35a6683c 100644 --- a/python/src/graphics/CMakeLists.txt +++ b/python/src/graphics/CMakeLists.txt @@ -18,8 +18,13 @@ ) target_include_directories(_graphics + # First, so that a header is opened at src/... rather than through + # build/include: the path a translation unit used is the path gcov records, + # and the two spellings would split every header into two entries in the + # coverage report. See codac_publish_include_dirs() in the top-level CMakeLists.txt. + PRIVATE ${CODAC_SOURCE_INCLUDE_DIRS} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../docstring - PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../../include + PRIVATE ${CMAKE_BINARY_DIR}/src PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../core/ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ diff --git a/python/src/unsupported/CMakeLists.txt b/python/src/unsupported/CMakeLists.txt index 15bccec3a..6b3e93ec6 100644 --- a/python/src/unsupported/CMakeLists.txt +++ b/python/src/unsupported/CMakeLists.txt @@ -9,8 +9,13 @@ ) target_include_directories(_unsupported + # First, so that a header is opened at src/... rather than through + # build/include: the path a translation unit used is the path gcov records, + # and the two spellings would split every header into two entries in the + # coverage report. See codac_publish_include_dirs() in the top-level CMakeLists.txt. + PRIVATE ${CODAC_SOURCE_INCLUDE_DIRS} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../docstring - PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../../include + PRIVATE ${CMAKE_BINARY_DIR}/src PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b40947da8..3f7123efb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,18 @@ # Compiling sources # ================= + # Emptied here, then appended to by each module below through + # codac_publish_include_dirs(). It is a cache entry, so it survives a + # reconfigure: without this reset it would grow a duplicate of every + # directory on each run, and would keep the directories of a module that has + # since been switched off (capd, sympy). + set(CODAC_SOURCE_INCLUDE_DIRS "" CACHE INTERNAL + "Directories holding the public headers of the codac modules") + + # Same treatment for the directories of the generated umbrella headers. + set(CODAC_GENERATED_INCLUDE_DIRS "" CACHE INTERNAL + "Directories holding the umbrella headers generated for the codac modules") + add_subdirectory(core) add_subdirectory(graphics) add_subdirectory(unsupported) @@ -189,10 +201,28 @@ ${CODAC_GAOL_CONFIG_SNIPPET} # ==================================== set(CODAC_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac) - file(WRITE ${CODAC_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_MAIN_HEADER} "#pragma once\n\n") + + set(CODAC_MAIN_HEADER_CONTENT "/* This file is generated by CMake */\n\n") + string(APPEND CODAC_MAIN_HEADER_CONTENT "#pragma once\n\n") foreach(header_path ${CODAC_MAIN_SUBHEADERS}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_MAIN_HEADER_CONTENT "#include <${header_name}>\n") endforeach() + + # Compare with the existing file, if any + if(EXISTS "${CODAC_MAIN_HEADER}") + file(READ "${CODAC_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_MAIN_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed: 24 examples #include , and an + # unconditional rewrite moves its timestamp on every cmake run, rebuilding + # all of them although nothing changed. + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_MAIN_HEADER}" "${CODAC_MAIN_HEADER_CONTENT}") + endif() install(FILES ${CODAC_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/) \ No newline at end of file diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3337a364b..4b0d41825 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -299,7 +299,20 @@ #endif() add_library(${PROJECT_NAME}-core ${CODAC_CORE_SRC}) - target_include_directories(${PROJECT_NAME}-core PUBLIC + + # The directories holding the public core headers, in their canonical + # location under src/core. Published as a cache variable so that the tests, + # the examples and the Python bindings all reach the headers by the same + # path as codac-core itself, instead of each keeping its own copy of this + # list (which had already drifted). + # + # The path a translation unit used is the path gcov records, so a header + # reached both through src/ and through a copy in the build tree ends up as + # two unrelated entries in the coverage report, each showing only the part + # its own callers exercised, and is reported well below its real coverage. + # Consumers therefore list these directories first; the build tree stays on + # the path for the generated umbrella headers (codac-core.h and the rest). + set(CODAC_CORE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/actions ${CMAKE_CURRENT_SOURCE_DIR}/contractors ${CMAKE_CURRENT_SOURCE_DIR}/domains @@ -325,8 +338,12 @@ ${CMAKE_CURRENT_SOURCE_DIR}/proj ${CMAKE_CURRENT_SOURCE_DIR}/separators ${CMAKE_CURRENT_SOURCE_DIR}/tools + ${CMAKE_CURRENT_SOURCE_DIR}/tools/ibex ${CMAKE_CURRENT_SOURCE_DIR}/trajectory - ) + CACHE INTERNAL "Directories holding the public headers of codac-core") + + target_include_directories(${PROJECT_NAME}-core PUBLIC ${CODAC_CORE_INCLUDE_DIRS}) + codac_publish_include_dirs(${CODAC_CORE_INCLUDE_DIRS}) target_link_libraries(${PROJECT_NAME}-core PUBLIC Codac::gaol Eigen3::Eigen Threads::Threads) @@ -337,36 +354,52 @@ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-core" PARENT_SCOPE) set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-core" PARENT_SCOPE) - + ################################################################################ -# Installation of libcodac-core files +# Installation / build include tree ################################################################################ - -# Getting header files from sources foreach(srcfile ${CODAC_CORE_SRC}) if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") list(APPEND CODAC_CORE_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) endif() endforeach() + # Generating the file codac-core.h +# ================================ set(CODAC_CORE_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-core.h) + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) set(CODAC_MAIN_SUBHEADERS ${CODAC_MAIN_SUBHEADERS} "codac-core.h" PARENT_SCOPE) - file(WRITE ${CODAC_CORE_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_CORE_MAIN_HEADER} "#pragma once\n\n") + + # Generate the content of the umbrella header + set(CODAC_CORE_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_CORE_HDR}) get_filename_component(header_name ${header_path} NAME) if((NOT header_name MATCHES "^.*_addons.*$") AND (NOT header_name MATCHES "^.*_impl.*$")) - file(APPEND ${CODAC_CORE_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_CORE_HEADER_CONTENT "#include <${header_name}>\n") endif() endforeach() - file(COPY ${CODAC_CORE_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_CORE_MAIN_HEADER}") + file(READ "${CODAC_CORE_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_CORE_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_CORE_MAIN_HEADER}" "${CODAC_CORE_HEADER_CONTENT}") + endif() # Install files in system directories +# ==================================== install(TARGETS ${PROJECT_NAME}-core DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(FILES ${CODAC_CORE_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-core) diff --git a/src/extensions/capd/CMakeLists.txt b/src/extensions/capd/CMakeLists.txt index d5a8b2bd4..c5e3a3758 100644 --- a/src/extensions/capd/CMakeLists.txt +++ b/src/extensions/capd/CMakeLists.txt @@ -24,6 +24,16 @@ list(APPEND CODAC_CAPD_SRC add_library(${PROJECT_NAME}-capd ${CODAC_CAPD_SRC}) target_link_libraries(${PROJECT_NAME}-capd PUBLIC ${PROJECT_NAME}-core Codac::gaol Eigen3::Eigen capd::capd) + # The directory holding this module's public headers, in its canonical + # location under src/. It had none declared at all: its headers were only + # reachable through their copy in build/include. Publishing it lets every + # consumer open them at their source path instead -- see + # codac_publish_include_dirs() in the top-level CMakeLists.txt for why that + # decides what the coverage report is worth. + set(CODAC_CAPD_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) + target_include_directories(${PROJECT_NAME}-capd PUBLIC ${CODAC_CAPD_INCLUDE_DIRS}) + codac_publish_include_dirs(${CODAC_CAPD_INCLUDE_DIRS}) + ################################################################################ # For the generation of the PKG file @@ -42,20 +52,35 @@ list(APPEND CODAC_CAPD_SRC foreach(srcfile ${CODAC_CAPD_SRC}) if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") list(APPEND CODAC_CAPD_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) endif() endforeach() # Generating the file codac-capd.h set(CODAC_CAPD_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-capd.h) - file(WRITE ${CODAC_CAPD_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_CAPD_MAIN_HEADER} "#pragma once\n\n") + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) + + # Generate the content of the umbrella header + set(CODAC_CAPD_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_CAPD_HDR}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_CAPD_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_CAPD_HEADER_CONTENT "#include <${header_name}>\n") endforeach() - file(COPY ${CODAC_CAPD_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_CAPD_MAIN_HEADER}") + file(READ "${CODAC_CAPD_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_CAPD_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_CAPD_MAIN_HEADER}" "${CODAC_CAPD_HEADER_CONTENT}") + endif() # Install files in system directories diff --git a/src/extensions/sympy/CMakeLists.txt b/src/extensions/sympy/CMakeLists.txt index 0bc1d9be7..99698751d 100644 --- a/src/extensions/sympy/CMakeLists.txt +++ b/src/extensions/sympy/CMakeLists.txt @@ -34,6 +34,16 @@ target_link_libraries(${PROJECT_NAME}-sympy PUBLIC ${PROJECT_NAME}-core Codac::gaol Eigen3::Eigen) target_link_libraries(${PROJECT_NAME}-sympy PRIVATE pybind11::pybind11) + # The directory holding this module's public headers, in its canonical + # location under src/. It had none declared at all: its headers were only + # reachable through their copy in build/include. Publishing it lets every + # consumer open them at their source path instead -- see + # codac_publish_include_dirs() in the top-level CMakeLists.txt for why that + # decides what the coverage report is worth. + set(CODAC_SYMPY_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) + target_include_directories(${PROJECT_NAME}-sympy PUBLIC ${CODAC_SYMPY_INCLUDE_DIRS}) + codac_publish_include_dirs(${CODAC_SYMPY_INCLUDE_DIRS}) + ################################################################################ # For the generation of the PKG file ################################################################################ @@ -45,18 +55,31 @@ # Installation of libcodac-sympy files ################################################################################ - foreach(srcfile ${CODAC_SYMPY_PUBLIC_HDR}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) - endforeach() - + # Generating the file codac-sympy.h set(CODAC_SYMPY_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-sympy.h) - file(WRITE ${CODAC_SYMPY_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_SYMPY_MAIN_HEADER} "#pragma once\n\n") + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) + + # Generate the content of the umbrella header + set(CODAC_SYMPY_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_SYMPY_PUBLIC_HDR}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_SYMPY_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_SYMPY_HEADER_CONTENT "#include <${header_name}>\n") endforeach() - file(COPY ${CODAC_SYMPY_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_SYMPY_MAIN_HEADER}") + file(READ "${CODAC_SYMPY_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_SYMPY_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_SYMPY_MAIN_HEADER}" "${CODAC_SYMPY_HEADER_CONTENT}") + endif() install(TARGETS ${PROJECT_NAME}-sympy DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(FILES ${CODAC_SYMPY_PUBLIC_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-sympy) diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index f6d79ef6d..a9e691253 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -46,13 +46,22 @@ #endif() add_library(${PROJECT_NAME}-graphics ${CODAC_GRAPHICS_SRC}) - target_include_directories(${PROJECT_NAME}-graphics PUBLIC + + # The directories holding the public graphics headers, in their canonical + # location under src/graphics. Published so that the tests, the examples and + # the Python bindings reach them by this path rather than through their link + # in build/include -- see codac_publish_include_dirs() in the top-level + # CMakeLists.txt for why that decides what the coverage report is worth. + set(CODAC_GRAPHICS_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/ipe ${CMAKE_CURRENT_SOURCE_DIR}/3rd/vibes ${CMAKE_CURRENT_SOURCE_DIR}/figures ${CMAKE_CURRENT_SOURCE_DIR}/paver # deprecated, to be removed ${CMAKE_CURRENT_SOURCE_DIR}/styles ) + + target_include_directories(${PROJECT_NAME}-graphics PUBLIC ${CODAC_GRAPHICS_INCLUDE_DIRS}) + codac_publish_include_dirs(${CODAC_GRAPHICS_INCLUDE_DIRS}) target_link_libraries(${PROJECT_NAME}-graphics PUBLIC ${PROJECT_NAME}-core Codac::gaol Eigen3::Eigen ${PROJECT_NAME}-core) @@ -73,21 +82,36 @@ foreach(srcfile ${CODAC_GRAPHICS_SRC}) if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") list(APPEND CODAC_GRAPHICS_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) endif() endforeach() # Generating the file codac-graphics.h set(CODAC_GRAPHICS_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-graphics.h) + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) set(CODAC_MAIN_SUBHEADERS ${CODAC_MAIN_SUBHEADERS} "codac-graphics.h" PARENT_SCOPE) - file(WRITE ${CODAC_GRAPHICS_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_GRAPHICS_MAIN_HEADER} "#pragma once\n\n") + + # Generate the content of the umbrella header + set(CODAC_GRAPHICS_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_GRAPHICS_HDR}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_GRAPHICS_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_GRAPHICS_HEADER_CONTENT "#include <${header_name}>\n") endforeach() - file(COPY ${CODAC_GRAPHICS_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_GRAPHICS_MAIN_HEADER}") + file(READ "${CODAC_GRAPHICS_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_GRAPHICS_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_GRAPHICS_MAIN_HEADER}" "${CODAC_GRAPHICS_HEADER_CONTENT}") + endif() # Install files in system directories diff --git a/src/unsupported/CMakeLists.txt b/src/unsupported/CMakeLists.txt index a0a0f8d8f..2f5f1b1a4 100644 --- a/src/unsupported/CMakeLists.txt +++ b/src/unsupported/CMakeLists.txt @@ -18,13 +18,17 @@ #endif() add_library(${PROJECT_NAME}-unsupported ${CODAC_UNSUPPORTED_SRC}) - target_include_directories(${PROJECT_NAME}-unsupported PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/ipe - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/vibes - ${CMAKE_CURRENT_SOURCE_DIR}/figures - ${CMAKE_CURRENT_SOURCE_DIR}/paver - ${CMAKE_CURRENT_SOURCE_DIR}/styles - ) + + # The directory holding the public unsupported headers. The list used to be + # a copy of the graphics one (3rd/ipe, figures, paver, styles), none of which + # exists here: this module keeps its single header at its own root, so that + # is what has to be on the include path -- and what consumers must reach it + # through rather than through its link in build/include, see + # codac_publish_include_dirs() in the top-level CMakeLists.txt. + set(CODAC_UNSUPPORTED_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) + + target_include_directories(${PROJECT_NAME}-unsupported PUBLIC ${CODAC_UNSUPPORTED_INCLUDE_DIRS}) + codac_publish_include_dirs(${CODAC_UNSUPPORTED_INCLUDE_DIRS}) target_link_libraries(${PROJECT_NAME}-unsupported PUBLIC ${PROJECT_NAME}-core Eigen3::Eigen) @@ -45,20 +49,35 @@ foreach(srcfile ${CODAC_UNSUPPORTED_SRC}) if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") list(APPEND CODAC_UNSUPPORTED_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) endif() endforeach() # Generating the file codac-unsupported.h set(CODAC_UNSUPPORTED_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-unsupported.h) - file(WRITE ${CODAC_UNSUPPORTED_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_UNSUPPORTED_MAIN_HEADER} "#pragma once\n\n") + codac_publish_generated_include_dirs(${CMAKE_CURRENT_BINARY_DIR}) + + # Generate the content of the umbrella header + set(CODAC_UNSUPPORTED_HEADER_CONTENT "/* This file is generated by CMake */\n\n#pragma once\n\n") foreach(header_path ${CODAC_UNSUPPORTED_HDR}) get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_UNSUPPORTED_MAIN_HEADER} "#include <${header_name}>\n") + string(APPEND CODAC_UNSUPPORTED_HEADER_CONTENT "#include <${header_name}>\n") endforeach() - file(COPY ${CODAC_UNSUPPORTED_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../include) + + # Compare with the existing file, if any + if(EXISTS "${CODAC_UNSUPPORTED_MAIN_HEADER}") + file(READ "${CODAC_UNSUPPORTED_MAIN_HEADER}" EXISTING_CONTENT) + string(MD5 EXISTING_HASH "${EXISTING_CONTENT}") + string(MD5 NEW_HASH "${CODAC_UNSUPPORTED_HEADER_CONTENT}") + else() + set(EXISTING_HASH "") + set(NEW_HASH "new") + endif() + + # Only rewrite it when it changed + if(NOT EXISTING_HASH STREQUAL NEW_HASH) + file(WRITE "${CODAC_UNSUPPORTED_MAIN_HEADER}" "${CODAC_UNSUPPORTED_HEADER_CONTENT}") + endif() # Install files in system directories diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 828f6416e..767f9781f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -160,6 +160,37 @@ endif() #message(STATUS "Found IBEX version ${IBEX_VERSION}") #endif() + + +# Common Codac include directories +# ================================================================== +# +# IMPORTANT: +# Use the source tree as the canonical header tree. +# +# The headers are now used directly from the source tree. + +# CODAC_SOURCE_INCLUDE_DIRS is filled in by the src/*/CMakeLists.txt of every +# module (core, graphics, unsupported, and capd/sympy when enabled), which are +# all configured before this directory. Using it rather than a second copy of +# the same list keeps the tests compiling against exactly the headers the +# libraries were built from; the copy that used to live here covered core only +# and had already drifted from it. +set(CODAC_CORE_SOURCE_INCLUDE_DIRS ${CODAC_SOURCE_INCLUDE_DIRS}) + + +# ${CMAKE_BINARY_DIR}/src holds the generated "codac" umbrella header; +# CODAC_GENERATED_INCLUDE_DIRS, filled in by the src/*/CMakeLists.txt of every +# module, holds the per-module ones (codac-core.h and the rest), each of which +# is written into its own module's binary directory rather than into this one. +set( + CODAC_HEADERS_DIR + ${CMAKE_BINARY_DIR}/src + ${CODAC_GENERATED_INCLUDE_DIRS} +) + + + # ------------------------------------------------------------------ # Sanitizer runtime preload for Python tests (Debug builds only) # ------------------------------------------------------------------ @@ -403,22 +434,84 @@ if(CODAC_MSVC_ASAN_PATH_ENV) list(APPEND CODAC_CPP_TEST_ENV "${CODAC_MSVC_ASAN_PATH_ENV}") endif() + +# Build every test +# ================================================================== + foreach(SRC_TEST ${SRC_TESTS}) string(REPLACE "/" "_" TEST_NAME ${SRC_TEST}) string(REPLACE "codac2_tests_" "" TEST_NAME ${TEST_NAME}) set(TEST_NAME codac2_tests_${TEST_NAME}) + + # --------------------------------------------------------------- # C++ test - add_executable(${TEST_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.cpp) - set(CODAC_HEADERS_DIR ${CMAKE_CURRENT_BINARY_DIR}/../include) - target_include_directories(${TEST_NAME} SYSTEM PUBLIC ${CODAC_HEADERS_DIR}) - target_link_libraries(${TEST_NAME} PUBLIC ${CODAC_LIBRARIES} PRIVATE Catch2::Catch2WithMain) + # --------------------------------------------------------------- + + add_executable( + ${TEST_NAME} + ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.cpp + ) + + + # Use the same canonical source headers as the libraries, and reach them by + # the same path: the source directories come first so that a header is + # opened at src/... Both compile identically, but the path used is the path gcov + # records, and different paths would split every header into two unrelated + # entries in the coverage report (see codac_publish_include_dirs() in the + # top-level CMakeLists.txt). + # + # CODAC_HEADERS_DIR is kept, after them, because it holds the generated + # umbrella headers: codac, codac-core.h, codac-graphics.h and the rest. + + target_include_directories( + ${TEST_NAME} + SYSTEM PUBLIC + + ${CODAC_CORE_SOURCE_INCLUDE_DIRS} + + ${CODAC_HEADERS_DIR} + ) + + + target_link_libraries( + ${TEST_NAME} + PUBLIC + + ${CODAC_LIBRARIES} + + PRIVATE + + Catch2::Catch2WithMain + ) + + + # Sympy embedded tests + if( + WITH_PYTHON + AND SRC_TEST MATCHES "extensions/sympy/" + ) + + target_link_libraries( + ${TEST_NAME} + PRIVATE + + ${PROJECT_NAME}-sympy + pybind11::embed + ) - if(WITH_PYTHON AND SRC_TEST MATCHES "extensions/sympy/") - target_link_libraries(${TEST_NAME} PRIVATE ${PROJECT_NAME}-sympy pybind11::embed) endif() - add_dependencies(check ${TEST_NAME}) - add_test(NAME ${TEST_NAME}_cpp COMMAND ${TEST_NAME}) + + + add_dependencies( + check + ${TEST_NAME} + ) + + add_test( + NAME ${TEST_NAME}_cpp + COMMAND ${TEST_NAME} + ) if(CODAC_CPP_TEST_ENV) set_tests_properties(${TEST_NAME}_cpp PROPERTIES @@ -426,7 +519,10 @@ foreach(SRC_TEST ${SRC_TESTS}) ) endif() + # --------------------------------------------------------------- # Python test + # --------------------------------------------------------------- + if(WITH_PYTHON AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.py") add_test( NAME ${TEST_NAME}_py From 91c65c8d6d28b424d2405817745117c077448629 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 09/19] Build and run the examples as ctest tests when TEST_EXAMPLES is ON --- examples/CMakeLists.txt | 185 +++++++++++++++++++++++++++++++++++++--- tests/CMakeLists.txt | 9 ++ 2 files changed, 180 insertions(+), 14 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2534bcf5f..69356542c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,17 +6,174 @@ if(BUILD_TESTS AND TEST_EXAMPLES) -# add_test(NAME cpp_01_getting_started -# COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tuto/01_getting_started/build/01_getting_started 0) -# if(WITH_PYTHON) -# add_test(NAME py_01_getting_started -# COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/tuto/01_getting_started/01_getting_started.py 0) -# endif() -# -# if(WITH_CAPD) -# # Lie group -# add_test(NAME lie_05 -# COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lie_group/05_loc/build/codac_lie_05 0) -# endif() - -endif() \ No newline at end of file + # Every example ships a standalone CMakeLists.txt, so that a user can build it + # on its own against an installed codac. Those projects cannot be pulled in + # with add_subdirectory(): each calls project() and find_package(CODAC), which + # would look for a codac that is precisely the one being built. The examples + # are therefore rebuilt here from the same sources, against the in-tree + # library, the way tests/CMakeLists.txt builds the unit tests. Only the public + # headers are visible to them -- the generated umbrella "codac" and the + # installed headers next to it -- so an example that stops compiling here is + # an example a user could no longer build either. + # + # The lists below mirror the add_executable() calls of those standalone + # CMakeLists.txt: an example directory may hold sources that its own project + # does not build (02_centered_form/main_parabolas.cpp, for instance), and + # those are deliberately left out rather than silently compiled. + + set(CODAC_EXAMPLES_CPP + 00_graphics/graphic_examples + 00_graphics/graphic_animation + 01_batman/main + 02_centered_form/main + 03_sivia/main + 04_explored_area/main + 06_graphics_3D/main + 07_centered_2D/main + 08_centered_3D/main + 09_robot_simu/main + 11_peibos/main + 13_qinter/main + 14_lohner/main + 16_visibility/main + ellipsoid_example/main + ) + + set(CODAC_EXAMPLES_PY + 00_graphics/graphic_examples + 00_graphics/graphic_animation + 00_graphics/graphic_colors + 02_centered_form/main + 02_centered_form/main_parabolas + 03_sivia/main + 04_explored_area/main + 06_graphics_3D/main + 09_robot_simu/main + 11_peibos/main + 13_qinter/main + 14_lohner/main + 16_visibility/main + custom_sep/coloration + custom_sep/custom_ctc + custom_sep/custom_sep + ellipsoid_example/main + ) + + set(CODAC_EXAMPLE_LIBRARIES ${PROJECT_NAME}-core ${PROJECT_NAME}-graphics) + + if(WITH_CAPD) + # 10_lie_groups is in this list rather than in the one above because it + # includes , which its own CMakeLists.txt does not say: the + # standalone project builds it only where a codac with CAPD is installed. + list(APPEND CODAC_EXAMPLES_CPP + 05_capd_solver/main + 10_lie_groups/lie_01 + 12_peibos_capd/main + ) + list(APPEND CODAC_EXAMPLE_LIBRARIES ${PROJECT_NAME}-capd capd::capd) + endif() + + # BUILD_SYMPY_EMBED_TESTS is decided in tests/CMakeLists.txt, which the + # top-level CMakeLists.txt adds before this directory. It is turned off + # wherever PYBIND11_FINDPYTHON=OFF, which is every configuration this + # project's CI builds, so the sympy example follows the sympy unit test + # rather than being built on its own terms. + if(WITH_PYTHON AND BUILD_SYMPY_EMBED_TESTS) + list(APPEND CODAC_EXAMPLES_CPP 15_sympy/main) + list(APPEND CODAC_EXAMPLES_PY 15_sympy/main) + list(APPEND CODAC_EXAMPLE_LIBRARIES ${PROJECT_NAME}-sympy) + endif() + + # The examples write their figures (JSON for VIBes, .ipe, .png, .obj) into the + # current directory, so they are run from the build tree rather than from the + # sources they were copied from. Assets they read are found through __FILE__, + # which stays an absolute path, so nothing has to be copied over. + set(CODAC_EXAMPLES_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/output) + file(MAKE_DIRECTORY ${CODAC_EXAMPLES_OUTPUT_DIR}) + + foreach(SRC_EXAMPLE ${CODAC_EXAMPLES_CPP}) + + string(REPLACE "/" "_" EXAMPLE_NAME ${SRC_EXAMPLE}) + set(EXAMPLE_NAME codac2_examples_${EXAMPLE_NAME}) + + add_executable( + ${EXAMPLE_NAME} + ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_EXAMPLE}.cpp + ) + + target_include_directories( + ${EXAMPLE_NAME} + SYSTEM PUBLIC + + # First, so that a header is opened at src/... rather than through + # build/include: the path a translation unit used is the path gcov records, + # and the two spellings would split every header into two entries in the + # coverage report. See codac_publish_include_dirs() in the top-level CMakeLists.txt. + ${CODAC_SOURCE_INCLUDE_DIRS} + + ${CMAKE_BINARY_DIR}/src # the generated "codac" umbrella header + ${CODAC_GENERATED_INCLUDE_DIRS} # and the per-module ones (codac-core.h, codac-graphics.h, etc.), each in its own module's binary directory + ) + + target_link_libraries( + ${EXAMPLE_NAME} + PUBLIC + + ${CODAC_EXAMPLE_LIBRARIES} + ) + + # The sympy example runs an embedded Python interpreter, which codac-sympy + # calls into: it links pybind11::embed, as its own CMakeLists.txt does and as + # the sympy unit test does in tests/CMakeLists.txt. + if(SRC_EXAMPLE MATCHES "^15_sympy/") + target_link_libraries(${EXAMPLE_NAME} PRIVATE pybind11::embed) + endif() + + add_test( + NAME ${EXAMPLE_NAME}_cpp + COMMAND ${EXAMPLE_NAME} + WORKING_DIRECTORY ${CODAC_EXAMPLES_OUTPUT_DIR} + ) + + if(CODAC_CPP_TEST_ENV) + set_tests_properties(${EXAMPLE_NAME}_cpp PROPERTIES + ENVIRONMENT "${CODAC_CPP_TEST_ENV}" + ) + endif() + + # The examples are part of the ctest suite here, so `make check` -- which + # rebuilds the suite before running it -- has to build them too. Without + # this it would run ctest against executables that may not exist yet, the + # way it does for the unit tests through the same call in + # tests/CMakeLists.txt. + if(TARGET check) + add_dependencies(check ${EXAMPLE_NAME}) + endif() + + endforeach() + + if(WITH_PYTHON) + + foreach(SRC_EXAMPLE ${CODAC_EXAMPLES_PY}) + + string(REPLACE "/" "_" EXAMPLE_NAME ${SRC_EXAMPLE}) + set(EXAMPLE_NAME codac2_examples_${EXAMPLE_NAME}) + + # PYTHON_TEST_ENV_ARGS carries whatever the Python interpreter needs to + # load a sanitized or a Windows build (LD_PRELOAD, PATH...); it is built + # in tests/CMakeLists.txt and exported from there. + add_test( + NAME ${EXAMPLE_NAME}_py + COMMAND + ${CMAKE_COMMAND} -E env + ${PYTHON_TEST_ENV_ARGS} + ${PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_EXAMPLE}.py + WORKING_DIRECTORY ${CODAC_EXAMPLES_OUTPUT_DIR} + ) + + endforeach() + + endif() + +endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 767f9781f..a7ddb8d6a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -135,6 +135,11 @@ if(WITH_PYTHON AND DEFINED PYBIND11_FINDPYTHON AND NOT PYBIND11_FINDPYTHON) set(BUILD_SYMPY_EMBED_TESTS OFF) endif() +# BUILD_SYMPY_EMBED_TESTS is an option(), hence a cache entry: the set() above +# only overrides it for this directory, and examples/ would otherwise read the +# cached ON again and build the sympy example where its own test is disabled. +set(BUILD_SYMPY_EMBED_TESTS "${BUILD_SYMPY_EMBED_TESTS}" PARENT_SCOPE) + # Sympy test if (WITH_PYTHON AND BUILD_SYMPY_EMBED_TESTS) list(APPEND SRC_TESTS @@ -434,6 +439,10 @@ if(CODAC_MSVC_ASAN_PATH_ENV) list(APPEND CODAC_CPP_TEST_ENV "${CODAC_MSVC_ASAN_PATH_ENV}") endif() +# examples/ is added by the top-level CMakeLists.txt right after this directory +# and runs the same kind of binaries, so both environments are handed over to it. +set(CODAC_CPP_TEST_ENV "${CODAC_CPP_TEST_ENV}" PARENT_SCOPE) +set(PYTHON_TEST_ENV_ARGS "${PYTHON_TEST_ENV_ARGS}" PARENT_SCOPE) # Build every test # ================================================================== From e81bed1aeae9d3c3a8ced2611347e13c39b9c0e9 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 10/19] Add the WITH_COVERAGE option and the coverage targets --- CMakeLists.txt | 151 ++++++++++++++++++++++++++ scripts/CMakeModules/codac_gaol.cmake | 11 +- 2 files changed, 157 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c12977dc..2f9796472 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -479,6 +479,157 @@ find_package(CAPD REQUIRED) endif() +################################################################################ +# Code coverage +################################################################################ + + # WITH_COVERAGE instruments the build and adds two targets: `coverage`, which + # runs the whole ctest suite and turns the counters gcov leaves behind into a + # report, and `coverage-report`, which only does the second half, so that a + # developer measuring coverage by hand and a continuous integration job + # reporting it use the same flags, the same gcov and the same filters, and + # cannot drift apart. Deciding all of that here + # rather than leaving it to the caller is also what spares everyone the traps + # below. + # + # Coverage answers "what do the tests reach", so it is worth turning + # BUILD_TESTS and TEST_EXAMPLES on with it -- the examples are integration + # tests and reach code the unit tests do not. + # + # This block has to come before the add_subdirectory() calls below: + # add_compile_options() is a directory property, and only reaches the targets + # created after it. Declared any later, it configures cleanly, builds + # cleanly, and leaves no .gcno file behind at all -- an empty report rather + # than an error. + option(WITH_COVERAGE "Instrument the build for code coverage (GCC/Clang)" OFF) + + if(WITH_COVERAGE) + + if(MSVC) + message(FATAL_ERROR "WITH_COVERAGE relies on gcov, which MSVC does not provide.") + endif() + + add_compile_options(--coverage) + add_link_options(--coverage) + + # pybind11 adds -flto to the modules it builds, and GCC cannot combine that + # with gcov instrumentation: the link then fails on an undefined vtable, + # the vtable being emitted nowhere once both are on. pybind11 leaves LTO + # alone as soon as CMAKE_INTERPROCEDURAL_OPTIMIZATION is set, which is what + # this does -- silently, because a coverage build that does not link is of + # no use to anyone. + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF) + + # gcov itself is toolchain-specific: the notes and counters Clang writes are + # read by `llvm-cov gcov`, not by GCC's gcov, and pointing gcovr at the + # wrong one yields an empty report rather than an error. The one shipped + # next to the compiler in use is therefore named explicitly. + set(CODAC_GCOV_ARG "") + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Debian and its derivatives ship the tools of each LLVM release under a + # versioned name in /usr/bin and unversioned in the release's own prefix, + # so both spellings and both places are tried, the version being taken + # from the compiler that will produce the notes. + get_filename_component(_codac_cxx_dir "${CMAKE_CXX_COMPILER}" DIRECTORY) + string(REGEX MATCH "^[0-9]+" _codac_llvm_major "${CMAKE_CXX_COMPILER_VERSION}") + find_program(LLVM_COV_EXECUTABLE + NAMES llvm-cov llvm-cov-${_codac_llvm_major} + HINTS ${_codac_cxx_dir} + /usr/lib/llvm-${_codac_llvm_major}/bin) + if(LLVM_COV_EXECUTABLE) + set(CODAC_GCOV_ARG --gcov-executable "${LLVM_COV_EXECUTABLE} gcov") + else() + message(STATUS "${COLOR_RED}llvm-cov was not found next to ${CMAKE_CXX_COMPILER}: the coverage report would come out empty.${COLOR_RESET}") + endif() + endif() + + find_program(GCOVR_EXECUTABLE gcovr) + + if(NOT GCOVR_EXECUTABLE) + + message(STATUS "${COLOR_RED}gcovr was not found: the build is instrumented, but the `coverage` target is unavailable (pip install gcovr).${COLOR_RESET}") + + else() + + # A note on paths, since it decides what the report is worth: gcov + # records the path each translation unit actually opened a header + # through, so a header reached both directly and through a copy in + # the build tree comes out as two unrelated entries, each showing + # only what its own callers exercised. Every consumer (tests, + # examples, Python bindings) therefore lists CODAC_SOURCE_INCLUDE_DIRS + # first -- see codac_publish_include_dirs() above. Without that, a + # header was reported well below its real coverage, and every header + # of graphics/, unsupported/ and extensions/ was measured on a + # build-tree copy that is not the file anyone edits. + # + # --gcov-exclude-directories keeps gcovr from even descending into the + # dependencies FetchContent puts in the build tree, which --exclude alone + # would only drop from the finished report. It matters beyond noise: + # Catch2 compiles with -ffile-prefix-map, so the paths recorded in its + # notes do not resolve from anywhere and gcov fails outright on them; and + # gcov reports negative hit counts on heavily inlined code, Eigen being + # where that first shows, which gcovr treats as fatal unless told + # otherwise. The pattern has no leading ".*/" on purpose: gcovr matches it + # against a path relative to the working directory, which here starts with + # "_deps/" and so has nothing for that slash to match. + set(CODAC_GCOVR_COMMAND + ${GCOVR_EXECUTABLE} + --root ${CMAKE_SOURCE_DIR} + ${CODAC_GCOV_ARG} + --gcov-exclude-directories ".*_deps.*" + --gcov-ignore-parse-errors=negative_hits.warn_once_per_file + --exclude ".*/tests/.*" + --exclude ".*/examples/.*" + --exclude ".*/python/.*" + --print-summary + --html-details ${CMAKE_BINARY_DIR}/coverage.html + --xml ${CMAKE_BINARY_DIR}/coverage.xml + # Where to look for the counters. Without it gcovr falls back on + # --root, i.e. the source tree, and walks every build directory + # sitting under it: a second configuration kept next to this one + # (a Debug tree, an older coverage tree) then has its own .gcda + # read into this report, mixing counters produced by a different + # build of different sources. "." rather than an absolute path + # because the working directory below is already the build tree, + # and because --gcov-exclude-directories above matches against + # paths relative to it. + .) + + # Two targets rather than one, because the counters gcov leaves behind + # accumulate across runs and are read independently of what produced + # them: `coverage` is the whole thing, `coverage-report` re-reads whatever + # the last ctest invocation happened to touch. The second is what to use + # after running part of the suite by hand -- ctest -R something -- which + # is the usual way of asking what one test reaches. + # + # VERBATIM on both, because the arguments above are regular expressions: + # without it CMake hands them to the shell unquoted, which expands + # ".*/tests/.*" as a file pattern -- gcovr then receives "./tests/.." and + # refuses it. + add_custom_target(coverage + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + COMMAND ${CODAC_GCOVR_COMMAND} + COMMAND ${CMAKE_COMMAND} -E echo + "Coverage report: ${CMAKE_BINARY_DIR}/coverage.html (and coverage.xml, Cobertura)" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running the tests and measuring what they reach" + VERBATIM) + + add_custom_target(coverage-report + COMMAND ${CODAC_GCOVR_COMMAND} + COMMAND ${CMAKE_COMMAND} -E echo + "Coverage report: ${CMAKE_BINARY_DIR}/coverage.html (and coverage.xml, Cobertura)" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Turning the counters left by the last test run into a report" + VERBATIM) + + message(STATUS "${COLOR_BLUE}Coverage instrumentation enabled; run `make coverage` to produce the report.${COLOR_RESET}") + + endif() + + endif() + + ################################################################################ # Compile sources ################################################################################ diff --git a/scripts/CMakeModules/codac_gaol.cmake b/scripts/CMakeModules/codac_gaol.cmake index 83b58f0a8..cca81b821 100644 --- a/scripts/CMakeModules/codac_gaol.cmake +++ b/scripts/CMakeModules/codac_gaol.cmake @@ -306,11 +306,12 @@ endfunction() # A project of its own, as in IBEX's build of GAOL with its autotools, rather # than the FetchContent that Eigen and Catch2 are brought in with, which would # build GAOL as a part of this project. Kept apart, GAOL is compiled with the -# flags it chooses, and not with Codac's warnings and sanitizers, and it is -# always built in Release, whatever the configuration of Codac, which is what -# the MSVC runtime choice of the top-level CMakeLists.txt counts on. -# CMAKE_CXX_FLAGS and CMAKE_C_FLAGS are handed over as they are when this is -# called, before Codac adds its own flags to them. +# flags it chooses, and not with Codac's warnings, sanitizers and coverage +# instrumentation, and it is always built in Release, whatever the +# configuration of Codac, which is what the MSVC runtime choice of the +# top-level CMakeLists.txt counts on. CMAKE_CXX_FLAGS and CMAKE_C_FLAGS are +# handed over as they are when this is called, before Codac adds its own flags +# to them. # # GAOL comes from the head of the master branch of the fork (version 4.3.1 of # GAOL), so that the fixes pushed to the fork reach Codac without a change here. From 80e702c82127560f071bc188a37f4bb1a3d7a808 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 11/19] Retry the Doxygen install on Windows, pin 1.17.0 on Intel macOS --- .github/workflows/macosmatrix.yml | 38 +++++++++++++++++++++++++- .github/workflows/vcmatrix.yml | 44 ++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 08231a92e..824e51fee 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -54,7 +54,43 @@ jobs: # if: runner.os=='macOS' - run: brew install catch2 # Issues with binary packages when cross-compiling... if: (runner.os=='macOS')&&(matrix.cfg.cross!=true) - - run: brew install graphviz ; brew install --formula doxygen ; python -m pip install --upgrade pip ; pip install --upgrade wheel setuptools sphinx breathe sphinx_rtd_theme sphinx-tabs sphinx-issues sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy + # Doxygen 1.18.0, the version Homebrew currently installs, segfaults on the + # Intel runners while parsing this project's headers, and does so before + # writing doc/api/xml/index.xml, so cmake cannot even configure -- + # doc/CMakeLists.txt tolerates a crash that still produced usable XML, but + # there is nothing to salvage here. Those jobs therefore take doxygen from + # the project's own release, which is the only place an older version is + # available in binary form: Homebrew never offers anything but the newest. + # + # The arm64 runners keep Homebrew's doxygen. They survive 1.18.0, and the + # arm build of the official release cannot run there anyway: it is linked + # against macOS 15 while these runners are macOS 14, so it aborts at load + # time on a missing libc++ symbol. Two doxygen versions across the matrix is + # not ideal -- the names of the docstring macros are derived from the text + # doxygen produces, so a wording change between versions renames them and + # the bindings that spell them out stop compiling, which is exactly what the + # manylinux images hit (see scripts/doxygen/doxygen2docstring.py). Keeping + # the two versions close, and normalizing that text rather than trusting it, + # is what guards against it. + - run: | + brew install graphviz + if [ "${{ matrix.cfg.arch }}" = "arm64" ]; then + brew install --formula doxygen + doxygen --version + else + DOXYGEN_VERSION=1.17.0 + curl -fsSL -o doxygen.zip "https://github.com/doxygen/doxygen/releases/download/Release_${DOXYGEN_VERSION//./_}/doxygen-${DOXYGEN_VERSION}-mac-intel.zip" + unzip -q doxygen.zip -d "$HOME/doxygen" + rm -f doxygen.zip + chmod +x "$HOME/doxygen/doxygen-${DOXYGEN_VERSION}/doxygen" + # $GITHUB_PATH only reaches the later steps, so the check below has to + # name the binary; it runs here rather than there so that a download + # that cannot execute fails at once, next to what produced it. + "$HOME/doxygen/doxygen-${DOXYGEN_VERSION}/doxygen" --version + echo "$HOME/doxygen/doxygen-${DOXYGEN_VERSION}" >> "$GITHUB_PATH" + fi + python -m pip install --upgrade pip + pip install --upgrade wheel setuptools build sphinx breathe sphinx_rtd_theme sphinx-tabs sphinx-issues sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy if: runner.os=='macOS' - run: | mkdir build ; cd build diff --git a/.github/workflows/vcmatrix.yml b/.github/workflows/vcmatrix.yml index e00aefb57..50484b6ec 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -60,13 +60,45 @@ jobs: if: runner.os=='Windows' #- run: choco install -y -r --no-progress eigen --version=3.4.0.20240224 ${{ matrix.cfg.choco_flags }} # if: runner.os=='Windows' - - run: choco install -y -r --no-progress graphviz doxygen.install & python -m pip install --upgrade pip & pip install --upgrade wheel setuptools sphinx breathe sphinx-issues sphinx-tabs sphinx_rtd_theme sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy + - run: choco install -y -r --no-progress graphviz + if: runner.os=='Windows' + - name: Install Doxygen (up to 5 attempts, non fatal) + shell: bash + run: | + export PATH="/c/ProgramData/chocolatey/bin:$PATH" + ok=0 + for i in 1 2 3 4 5; do + echo "=== Doxygen install attempt $i/5 ===" + choco install -y -r --no-progress doxygen.install + rc=$? + if [ $rc -eq 0 ] || [ $rc -eq 3010 ]; then + if doxygen --version; then ok=1; break; fi + echo "choco reported success but doxygen is not callable." + fi + echo "Attempt $i failed (exit code $rc), retrying in 20 s..." + sleep 20 + done + if [ $ok -eq 1 ]; then + echo "WITH_PYTHON=ON" >> $GITHUB_ENV + else + echo "::warning::Doxygen could not be installed after 5 attempts - building without documentation and without Python bindings." + echo "WITH_PYTHON=OFF" >> $GITHUB_ENV + fi + if: runner.os=='Windows' + - run: python -m pip install --upgrade pip && pip install --upgrade wheel setuptools sphinx breathe sphinx-issues sphinx-tabs sphinx_rtd_theme sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy + shell: bash if: runner.os=='Windows' - run: | + WITH_PYTHON=${WITH_PYTHON:-ON} + echo "Configuring with WITH_PYTHON=$WITH_PYTHON" mkdir build ; cd build - cmake -E env CXXFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" CFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D WITH_CAPD=OFF -D WITH_PYTHON=ON -D PYBIND11_FINDPYTHON=OFF .. + cmake -E env CXXFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" CFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D WITH_CAPD=OFF -D WITH_PYTHON=$WITH_PYTHON -D PYBIND11_FINDPYTHON=OFF .. cmake --build . -j 4 --config Release --target install - cmake --build . --config Release --target pip_package ; cp `ls *.whl` ../`ls *.whl | sed "s/py3-none-any/cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}-cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}${{ matrix.cfg.cpcfg }}/"` + if [ "$WITH_PYTHON" = "ON" ]; then + cmake --build . --config Release --target pip_package ; cp `ls *.whl` ../`ls *.whl | sed "s/py3-none-any/cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}-cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}${{ matrix.cfg.cpcfg }}/"` + else + echo "Skipping pip_package target (no Doxygen docstrings available)." + fi cd .. shell: bash - uses: xresloader/upload-to-github-release@v1 @@ -76,13 +108,17 @@ jobs: file: "*.whl" overwrite: true tag_name: autotagname-${{ github.sha }} - if: (github.event_name!='pull_request')&&((github.ref_name=='codac1')||(github.ref_name=='codac2')||(github.ref_name=='codac2_codac4matlab')) + if: (env.WITH_PYTHON!='OFF')&&(github.event_name!='pull_request')&&((github.ref_name=='codac1')||(github.ref_name=='codac2')||(github.ref_name=='codac2_codac4matlab')) - run: | pip install --no-deps --no-index *.whl python -c "import sys; print(sys.version)" ; python examples/02_centered_form/main.py pip install numpy sympy --prefer-binary python -m unittest discover codac.tests + shell: bash + if: (env.WITH_PYTHON!='OFF')&&(github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') + - run: | cd build && ctest -C Release -V --output-on-failure cd .. shell: bash if: (github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') + From c1ce598bf1f807a9f34961c4bf11fd380508a0e7 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 12/19] Build the wheels with python -m build and an SPDX license --- .github/workflows/vcmatrix.yml | 2 +- python/CMakeLists.txt | 13 ++++++++++++- python/setup.py.in | 6 ++++-- scripts/docker/build_pybinding.sh | 2 +- scripts/docker/build_pybinding_codac4matlab.sh | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/vcmatrix.yml b/.github/workflows/vcmatrix.yml index 50484b6ec..c8a87054f 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -85,7 +85,7 @@ jobs: echo "WITH_PYTHON=OFF" >> $GITHUB_ENV fi if: runner.os=='Windows' - - run: python -m pip install --upgrade pip && pip install --upgrade wheel setuptools sphinx breathe sphinx-issues sphinx-tabs sphinx_rtd_theme sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy + - run: python -m pip install --upgrade pip && pip install --upgrade wheel setuptools build sphinx breathe sphinx-issues sphinx-tabs sphinx_rtd_theme sphinx-reredirects furo sphinx-math-dollar sphinx_togglebutton sympy shell: bash if: runner.os=='Windows' - run: | diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index bab5e84ba..dd59ddd22 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -79,8 +79,19 @@ add_custom_target(pip_package) + # Built through "python -m build" rather than by invoking setup.py directly: + # setuptools deprecated the latter, and bdist_wheel reaches the wheel by way + # of the install command, so each build printed "setup.py install is + # deprecated". Both are scheduled for removal. + # + # --no-isolation reuses the setuptools and wheel of the environment instead of + # creating a virtual environment and downloading them again for every wheel. + # Every place that reaches this target installs them first (the two + # scripts/docker/build_pybinding*.sh, and the macosmatrix/vcmatrix + # workflows), so the isolated environment would only cost a download and + # would silently build against a different setuptools than the one tested. add_custom_command(TARGET pip_package PRE_BUILD - COMMAND ${PYTHON_EXECUTABLE} ARGS setup.py bdist_wheel -d ${CMAKE_BINARY_DIR} + COMMAND ${PYTHON_EXECUTABLE} ARGS -m build --wheel --no-isolation --outdir ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${PYTHON_PACKAGE_DIR} ) diff --git a/python/setup.py.in b/python/setup.py.in index 633f927e3..117df3d56 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -40,11 +40,13 @@ setup( 'pip>=19.0.0', 'vibes' ], - license="LGPLv3+", + # PEP 639: the licence is carried by an SPDX expression, and the matching + # "License :: ..." classifier is deprecated -- setuptools warns about it on + # every wheel build. The two must not be given together. + license="LGPL-3.0-or-later", classifiers=[ "Development Status :: 3 - Alpha", "Topic :: Scientific/Engineering :: Mathematics", - "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", ], include_package_data=True, zip_safe=False diff --git a/scripts/docker/build_pybinding.sh b/scripts/docker/build_pybinding.sh index b89c0b428..dbea1b2da 100755 --- a/scripts/docker/build_pybinding.sh +++ b/scripts/docker/build_pybinding.sh @@ -84,7 +84,7 @@ for PYBIN in /opt/python/cp3*/bin; do fi "${PYBIN}/python" -m pip install --upgrade pip - "${PYBIN}/python" -m pip install --upgrade wheel setuptools + "${PYBIN}/python" -m pip install --upgrade wheel setuptools build mkdir -p build_dir && cd build_dir cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. make -j4 diff --git a/scripts/docker/build_pybinding_codac4matlab.sh b/scripts/docker/build_pybinding_codac4matlab.sh index 1c18b1841..83578aeed 100644 --- a/scripts/docker/build_pybinding_codac4matlab.sh +++ b/scripts/docker/build_pybinding_codac4matlab.sh @@ -84,7 +84,7 @@ for PYBIN in /opt/python/cp3*/bin; do fi "${PYBIN}/python" -m pip install --upgrade pip - "${PYBIN}/python" -m pip install --upgrade wheel setuptools + "${PYBIN}/python" -m pip install --upgrade wheel setuptools build mkdir -p build_dir && cd build_dir cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. make -j4 From 96b172ebc88977dafc15eba710dbc019a6e5f907 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 13/19] Run only the Python tests in the wheel-building matrices --- .github/workflows/macosmatrix.yml | 10 +++++++++- .github/workflows/vcmatrix.yml | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 824e51fee..81340e020 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -112,7 +112,15 @@ jobs: python -c "import sys; print(sys.version)" ; python examples/02_centered_form/main.py pip install numpy sympy --prefer-binary python -m unittest discover codac.tests - cd build && ctest -C Release -V --output-on-failure + # Only the Python half of the suite is run here. These jobs exist to + # build and check a wheel per Python version, and the C++ tests do not + # depend on that version: running them again on every entry repeated + # the same 87 tests 18 times on Windows and 11 times on macOS. They are + # still compiled, so a C++ regression still breaks this workflow, and + # they are still executed in Release on the very same systems by + # unixmatrix.yml -- Visual Studio 2022 on x86/x64/arm64, macOS Sonoma + # arm64 and Sequoia x86_64. + cd build && ctest -C Release -R "_py" -V --output-on-failure cd .. shell: bash if: (matrix.cfg.cross!=true)&&(github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') diff --git a/.github/workflows/vcmatrix.yml b/.github/workflows/vcmatrix.yml index c8a87054f..e301e24a8 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -117,8 +117,16 @@ jobs: shell: bash if: (env.WITH_PYTHON!='OFF')&&(github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') - run: | - cd build && ctest -C Release -V --output-on-failure + # Only the Python half of the suite is run here. These jobs exist to + # build and check a wheel per Python version, and the C++ tests do not + # depend on that version: running them again on every entry repeated + # the same 87 tests 18 times on Windows and 11 times on macOS. They are + # still compiled, so a C++ regression still breaks this workflow, and + # they are still executed in Release on the very same systems by + # unixmatrix.yml -- Visual Studio 2022 on x86/x64/arm64, macOS Sonoma + # arm64 and Sequoia x86_64. + cd build && ctest -C Release -R "_py" -V --output-on-failure cd .. shell: bash - if: (github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') + if: (env.WITH_PYTHON!='OFF')&&(github.ref_name!='codac2_codac4matlab')&&(github.event.pull_request.base.ref!='codac2_codac4matlab') From ab5bf69b5b4901f09547c170cecb8343228216f5 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:35 +0200 Subject: [PATCH 14/19] Add Debug workflows with sanitizers and coverage --- .github/workflows/macdebug.yml | 189 ++++++++++++++++++++++ .github/workflows/unixdebug.yml | 226 +++++++++++++++++++++++++++ .github/workflows/windebugmatrix.yml | 165 +++++++++++++++++++ CMakeLists.txt | 9 +- 4 files changed, 585 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/macdebug.yml create mode 100644 .github/workflows/unixdebug.yml create mode 100644 .github/workflows/windebugmatrix.yml diff --git a/.github/workflows/macdebug.yml b/.github/workflows/macdebug.yml new file mode 100644 index 000000000..55aa543f9 --- /dev/null +++ b/.github/workflows/macdebug.yml @@ -0,0 +1,189 @@ +# Debug builds on macOS, under AddressSanitizer and UndefinedBehaviorSanitizer. +# +# The macOS counterpart of unixdebug.yml, and the same reasoning: every other +# workflow builds macOS in Release, so nothing was ever run there under a +# sanitizer. It also runs the examples, which -D TEST_EXAMPLES=ON registers as +# ctest integration tests (see examples/CMakeLists.txt). +# +# As on Linux, GitHub offers only two instruction sets here, so the third +# configuration is a compiler rather than a third architecture: AppleClang next +# to GCC, on arm64. +# +# Two things make macOS harder than Linux, and shape the matrix below. +# +# GCC on macOS links against libstdc++ while AppleClang links against libc++, +# so a library built with one of them cannot be linked by the other -- the two +# standard libraries are not ABI-compatible. GAOL is no obstacle: codac builds +# it with the compiler of the job (see scripts/CMakeModules/codac_gaol.cmake). +# Catch2 has to be treated the same way, which the Toolchain step below does. +# +# The Python bindings are left out of every job here, sanitized macOS being a +# configuration the test suite cannot currently run them in: the block of +# tests/CMakeLists.txt that hands the sanitizer runtime to the interpreter is +# Linux-only by construction, and says so -- LD_PRELOAD has no equivalent there, +# macOS needing DYLD_INSERT_LIBRARIES and a differently named runtime. Without +# that preload the extension module fails to load on the first unresolved +# __asan_* symbol. The Python half of the suite therefore runs under a sanitizer +# in unixdebug.yml, where the mechanism exists, and macOS covers the C++ half: +# the library, its unit tests and the C++ examples. Turning WITH_PYTHON off also +# removes the need for doxygen here, which doc/CMakeLists.txt only requires for +# the bindings. +on: + push: + branches: ['**'] + tags-ignore: ['**'] # Ignore all tag pushes + pull_request: + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + +jobs: + macdebug: + runs-on: ${{ matrix.cfg.os }} + # A sanitized Debug build is roughly an order of magnitude slower to run + # than the Release builds of the other workflows, and the examples are run + # on top of the test suite. Three hours leaves room for that while still + # turning a hang into a red job rather than into six hours of runner time. + timeout-minutes: 180 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + cfg: + - { os: macos-26 , arch: arm64 , runtime: tahoe , compiler: gcc , with_python: 'OFF', desc: 'macOS Tahoe GCC arm64 Debug ASan+UBSan' } + - { os: macos-15-intel, arch: x86_64, runtime: sequoia, compiler: gcc , with_python: 'OFF', desc: 'macOS Sequoia GCC x86_64 Debug ASan+UBSan' } + - { os: macos-26 , arch: arm64 , runtime: tahoe , compiler: appleclang, with_python: 'OFF', desc: 'macOS Tahoe AppleClang arm64 Debug ASan+UBSan' } + - { os: macos-26 , arch: arm64 , runtime: tahoe , compiler: llvm , with_python: 'OFF', desc: 'macOS Tahoe LLVM Clang arm64 Debug ASan+UBSan' } + name: ${{ matrix.cfg.desc }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + clean: false + + - run: echo "VERBOSE=1" >> $GITHUB_ENV + + # Homebrew's "gcc" formula is the newest GCC it packages, and the binaries + # it installs carry the major version in their name (g++-15 and so on), the + # plain g++ of macOS being a symlink to AppleClang. The version is therefore + # resolved here rather than pinned, and written to the environment for the + # later steps. + # Catch2 is deliberately not installed from Homebrew: the bottle is built + # with AppleClang against libc++, so the GCC jobs cannot link it -- the + # standard-library split described at the top of this file, showing up as + # undefined std::__1:: symbols at link time. Leaving it out lets + # tests/CMakeLists.txt fetch Catch2 and build it with the compiler of the + # job, which is correct for every entry of this matrix. + - name: Toolchain + run: | + case "${{ matrix.cfg.compiler }}" in + gcc) + brew install gcc + GCC_MAJOR=$(brew list --versions gcc | awk '{print $2}' | cut -d. -f1) + echo "CC=gcc-${GCC_MAJOR}" >> "$GITHUB_ENV" + echo "CXX=g++-${GCC_MAJOR}" >> "$GITHUB_ENV" + ;; + llvm) + # Upstream Clang, which is a different compiler from the AppleClang + # of the entry above: its own release cycle, its own diagnostics and + # its own sanitizer runtimes. Homebrew keeps it out of the way of the + # system toolchain, so it has to be named by its prefix, and it needs + # to be pointed at its own libc++ -- the formula says as much -- or + # it compiles against headers newer than the library it links. + brew install llvm + LLVM_PREFIX=$(brew --prefix llvm) + echo "CC=$LLVM_PREFIX/bin/clang" >> "$GITHUB_ENV" + echo "CXX=$LLVM_PREFIX/bin/clang++" >> "$GITHUB_ENV" + echo "LDFLAGS=-L$LLVM_PREFIX/lib/c++ -Wl,-rpath,$LLVM_PREFIX/lib/c++" >> "$GITHUB_ENV" + ;; + *) + echo "CC=clang" >> "$GITHUB_ENV" + echo "CXX=clang++" >> "$GITHUB_ENV" + ;; + esac + + - name: Compiler version + run: $CXX --version + + - name: Configure and build + run: | + mkdir build ; cd build + cmake \ + -D CMAKE_BUILD_TYPE=Debug \ + -D CMAKE_CXX_FLAGS="-fPIC" \ + -D CMAKE_C_FLAGS="-fPIC" \ + -D CMAKE_INSTALL_PREFIX="../codac" \ + -D BUILD_TESTS=ON \ + -D TEST_EXAMPLES=ON \ + -D WITH_PYTHON=${{ matrix.cfg.with_python }} \ + -D PYBIND11_FINDPYTHON=OFF \ + -D WITH_CAPD=OFF \ + .. 2>&1 | tee configure.log + cmake --build . -j 4 + + # The point of this workflow is the sanitizers, and the top-level + # CMakeLists.txt deliberately falls back to an unsanitized Debug build when + # it cannot find their runtime rather than failing to link. That fallback is + # the right default for someone building codac by hand, and exactly the + # wrong outcome here: the job would come out green having checked nothing it + # was written for. GCC's Darwin sanitizer support is the reason this is not + # theoretical. + - name: Check that the sanitizers really are enabled + run: | + if grep -q "will not be sanitized" build/configure.log ; then + echo "This job exists to run the suite under ASan and UBSan, and cmake reported:" + grep "will not be sanitized" build/configure.log + exit 1 + fi + echo "Sanitizers enabled." + + # The unit tests and the examples are the same ctest suite: examples are + # registered as tests by examples/CMakeLists.txt, so this one command runs + # both, and a sanitizer report in either fails the job. + - name: Unit tests and examples + run: | + cd build + ctest -V --output-on-failure 2>&1 | tee ctest.log + + # ctest is run verbose so that everything the sanitizers print reaches the + # log. ASan and UBSan do not agree on what a diagnostic costs: a leak or a + # buffer overflow aborts the process and fails the test, but a UBSan runtime + # error only prints and lets the run continue, so a test can pass having + # reported dozens of undefined behaviours. Without -V that output is thrown + # away for every test that passes -- which is precisely the output worth + # reading here. + - name: Sanitizer diagnostics + if: always() + run: | + log=build/ctest.log + [ -f "$log" ] || { echo "No ctest output to scan." ; exit 0 ; } + # A digest, because the verbose log of the whole suite is far too long + # to scan by eye. The step reports rather than judges: it never fails + # the job, the tests themselves decide that. + n=$(grep -cE "runtime error:|ERROR: AddressSanitizer|ERROR: LeakSanitizer|SUMMARY: (Address|Undefined|Leak)Sanitizer" "$log" || true) + echo "Sanitizer diagnostics found: $n" + if [ "$n" -gt 0 ]; then + echo "--- distinct messages, most frequent first ---" + # awk rather than head: this shell runs with pipefail, and head + # closing the pipe early makes sort die on SIGPIPE, which failed the + # step -- the one thing it was written never to do. + grep -hoE "runtime error: .*|ERROR: (Address|Leak)Sanitizer: [a-z-]+" "$log" \ + | sed -E "s/0x[0-9a-f]+/0xADDR/g" | sort | uniq -c | sort -rn | awk 'NR<=40' + echo "--- first occurrences in context ---" + grep -nE "runtime error:|ERROR: (Address|Leak)Sanitizer" "$log" | awk 'NR<=20' + fi diff --git a/.github/workflows/unixdebug.yml b/.github/workflows/unixdebug.yml new file mode 100644 index 000000000..7e0c058d8 --- /dev/null +++ b/.github/workflows/unixdebug.yml @@ -0,0 +1,226 @@ +# Debug builds on Linux, under AddressSanitizer and UndefinedBehaviorSanitizer, +# plus one coverage run. +# +# The rest of this directory answers "does it build and do the tests pass?" on a +# wide spread of systems, all of it in Release. Nothing there ever runs codac +# under a sanitizer on Linux or macOS: windebugmatrix.yml is the only Debug +# workflow and its only sanitized entries are the MSVC ones (the MinGW ones are +# unsanitized for want of a runtime in that toolchain). The whole GCC/Clang +# sanitizer path of the top-level CMakeLists.txt -- the configuration this +# project is developed in -- was therefore never exercised by CI, so a +# use-after-free or a signed overflow could only ever be caught on a developer's +# machine. This workflow closes that gap, and macdebug.yml does the same for +# macOS. +# +# It also runs the examples, which no other workflow does beyond the two that +# tests.yml builds by hand: -D TEST_EXAMPLES=ON now builds every example against +# the in-tree library and runs it as an integration test (see +# examples/CMakeLists.txt). Under a sanitizer they are worth as much as the unit +# tests: they exercise long chains of the library the way a user writes them. +# +# GitHub only offers x86_64 and arm64 Linux runners, so the third distinct +# configuration below is a compiler rather than a third instruction set: Clang +# and GCC disagree often enough about undefined behaviour, and their sanitizer +# runtimes are different implementations, that running both is worth more than a +# third architecture reached through emulation -- where ASan does not work +# anyway, its shadow memory needing an address space QEMU cannot provide. +on: + push: + branches: ['**'] + tags-ignore: ['**'] # Ignore all tag pushes + pull_request: + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + +jobs: + unixdebug: + runs-on: ${{ matrix.cfg.os }} + # A sanitized Debug build is roughly an order of magnitude slower to run + # than the Release builds of the other workflows, and the examples are run + # on top of the test suite. Measured on this matrix: 37 min on GCC arm64, + # 58 min on GCC x86_64, and more than three hours under Clang, whose + # instrumentation is markedly more expensive here -- the first run of this + # workflow spent its whole budget inside the test step and was cut there. + # Six hours is GitHub's own ceiling; it is set to it because a shorter one + # kills a job that is progressing, while ctest already bounds each + # individual test to 1500 s, so a genuinely hung test cannot reach this. + timeout-minutes: 360 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + cfg: + # The distribution's own compilers are used rather than a pinned + # version from a PPA: on the newest Ubuntu they are already the + # newest GCC and Clang, and every job prints what it actually got, + # so an upgrade of the image shows up in the log instead of + # silently doing nothing. Adding ubuntu-toolchain-r would also put + # a Launchpad round-trip on the critical path of every job, which + # has already taken this repository's CI down with a 504. + - { os: ubuntu-26.04 , arch: x86_64, runtime: resolute, cc: gcc , cxx: g++ , packages: 'g++' , build_type: Debug , with_python: 'ON' , coverage: false, desc: 'Ubuntu 26.04 GCC x86_64 Debug ASan+UBSan' } + - { os: ubuntu-26.04-arm, arch: arm64 , runtime: resolute, cc: gcc , cxx: g++ , packages: 'g++' , build_type: Debug , with_python: 'ON' , coverage: false, desc: 'Ubuntu 26.04 GCC arm64 Debug ASan+UBSan' } + - { os: ubuntu-26.04 , arch: x86_64, runtime: resolute, cc: clang , cxx: clang++ , packages: 'clang', build_type: Debug , with_python: 'ON' , coverage: false, test_timeout: 300, desc: 'Ubuntu 26.04 Clang x86_64 Debug ASan+UBSan' } + - { os: ubuntu-26.04-arm, arch: arm64 , runtime: resolute, cc: clang , cxx: clang++ , packages: 'clang', build_type: Debug , with_python: 'ON' , coverage: false, test_timeout: 300, desc: 'Ubuntu 26.04 Clang arm64 Debug ASan+UBSan' } + - { os: ubuntu-26.04 , arch: x86_64, runtime: resolute, cc: gcc , cxx: g++ , packages: 'g++' , build_type: Release, with_python: 'ON' , coverage: true , desc: 'Ubuntu 26.04 GCC x86_64 Release coverage' } + name: ${{ matrix.cfg.desc }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + clean: false + + - run: echo "VERBOSE=1" >> $GITHUB_ENV + + # setup-python rather than the interpreter of the image: the bindings need + # the development headers, and this is how tests.yml already provides them. + - uses: actions/setup-python@v7 + with: + python-version: '3.13' + + # doxygen and graphviz are what the docstrings of the bindings are generated + # from. pybind11 is deliberately not installed from the distribution: codac + # asks for 3.0.1 and fetches that version itself when it is not found, which + # is what every other workflow of this directory ends up using, whereas the + # package of the newest Ubuntu got picked up first and left codac-sympy + # linking a pybind11::pybind11 target that its CMake config does not define. + - name: Toolchain and dependencies + run: | + sudo apt-get -q update + sudo apt-get -y install ${{ matrix.cfg.packages }} cmake catch2 doxygen graphviz dpkg-dev || true + ${{ matrix.cfg.cxx }} --version + + - name: Python tooling + run: | + python -m pip install --upgrade pip wheel setuptools numpy sympy + if [ "${{ matrix.cfg.coverage }}" = "true" ]; then + python -m pip install --upgrade gcovr + fi + + # The instrumentation is requested through -D WITH_COVERAGE=ON rather than + # by passing --coverage by hand, so that this job and a developer running + # `make coverage` measure the same build with the same tool: the top-level + # CMakeLists.txt is the single place where the flags, the gcov executable + # matching the compiler and the gcovr filters are decided. The build type + # stays Release, so the coverage job compiles exactly what the Release jobs + # of the other workflows compile, instrumentation aside. + - name: Configure and build + env: + CC: ${{ matrix.cfg.cc }} + CXX: ${{ matrix.cfg.cxx }} + run: | + mkdir build ; cd build + cmake \ + -D WITH_COVERAGE=${{ matrix.cfg.coverage && 'ON' || 'OFF' }} \ + -D CMAKE_BUILD_TYPE=${{ matrix.cfg.build_type }} \ + -D CMAKE_CXX_FLAGS="-fPIC" \ + -D CMAKE_C_FLAGS="-fPIC" \ + -D CMAKE_INSTALL_PREFIX="../codac" \ + -D BUILD_TESTS=ON \ + -D TEST_EXAMPLES=ON \ + -D WITH_PYTHON=${{ matrix.cfg.with_python }} \ + -D PYBIND11_FINDPYTHON=OFF \ + -D WITH_CAPD=OFF \ + .. 2>&1 | tee configure.log + cmake --build . -j 4 + + # The point of the Debug jobs is the sanitizers, and the top-level + # CMakeLists.txt deliberately falls back to an unsanitized Debug build when + # it cannot find their runtime rather than failing to link. That fallback is + # the right default for someone building codac by hand, and exactly the + # wrong outcome here: the job would come out green having checked nothing it + # was written for. + - name: Check that the sanitizers really are enabled + if: matrix.cfg.build_type == 'Debug' + run: | + if grep -q "will not be sanitized" build/configure.log ; then + echo "This job exists to run the suite under ASan and UBSan, and cmake reported:" + grep "will not be sanitized" build/configure.log + exit 1 + fi + echo "Sanitizers enabled." + + # The unit tests and the examples are the same ctest suite: examples are + # registered as tests by examples/CMakeLists.txt, so this one command runs + # both, and a sanitizer report in either fails the job. + - name: Unit tests and examples + run: | + cd build + # ctest's own default here is 1500 s per test, which is far above what + # any test of this suite needs even instrumented -- the slowest of them + # takes under three minutes on a developer machine under ASan. Where a + # job sets test_timeout, that bound is tightened so that a test which + # stops progressing is reported as the one at fault, with its output, + # instead of the job being killed hours later with nothing to read. + TIMEOUT_FLAG="" + if [ -n "${{ matrix.cfg.test_timeout }}" ]; then + TIMEOUT_FLAG="--timeout ${{ matrix.cfg.test_timeout }}" + fi + # -V on the Debug jobs only: the coverage job runs the same suite + # without instrumentation, and its verbose output would carry nothing + # the others do not already show. + VERBOSE_FLAG="" + if [ "${{ matrix.cfg.build_type }}" = "Debug" ]; then VERBOSE_FLAG="-V" ; fi + ctest $VERBOSE_FLAG --output-on-failure $TIMEOUT_FLAG 2>&1 | tee ctest.log + + # ctest is run verbose so that everything the sanitizers print reaches the + # log. ASan and UBSan do not agree on what a diagnostic costs: a leak or a + # buffer overflow aborts the process and fails the test, but a UBSan runtime + # error only prints and lets the run continue, so a test can pass having + # reported dozens of undefined behaviours. Without -V that output is thrown + # away for every test that passes -- which is precisely the output worth + # reading here. + - name: Sanitizer diagnostics + if: always() + run: | + log=build/ctest.log + [ -f "$log" ] || { echo "No ctest output to scan." ; exit 0 ; } + # A digest, because the verbose log of the whole suite is far too long + # to scan by eye. The step reports rather than judges: it never fails + # the job, the tests themselves decide that. + n=$(grep -cE "runtime error:|ERROR: AddressSanitizer|ERROR: LeakSanitizer|SUMMARY: (Address|Undefined|Leak)Sanitizer" "$log" || true) + echo "Sanitizer diagnostics found: $n" + if [ "$n" -gt 0 ]; then + echo "--- distinct messages, most frequent first ---" + # awk rather than head: this shell runs with pipefail, and head + # closing the pipe early makes sort die on SIGPIPE, which failed the + # step -- the one thing it was written never to do. + grep -hoE "runtime error: .*|ERROR: (Address|Leak)Sanitizer: [a-z-]+" "$log" \ + | sed -E "s/0x[0-9a-f]+/0xADDR/g" | sort | uniq -c | sort -rn | awk 'NR<=40' + echo "--- first occurrences in context ---" + grep -nE "runtime error:|ERROR: (Address|Leak)Sanitizer" "$log" | awk 'NR<=20' + fi + + # coverage-report, not coverage: the suite has already been run by the step + # above, and this target only turns the counters it left behind into a + # report. The gcovr invocation lives in the top-level CMakeLists.txt, where + # `make coverage` reaches the very same one. + - name: Coverage report + if: matrix.cfg.coverage + run: | + cd build + gcovr --version + cmake --build . --target coverage-report + + - uses: actions/upload-artifact@v7 + if: matrix.cfg.coverage + with: + name: coverage-report + path: | + build/coverage.* + retention-days: 14 diff --git a/.github/workflows/windebugmatrix.yml b/.github/workflows/windebugmatrix.yml new file mode 100644 index 000000000..0e0ec4b0a --- /dev/null +++ b/.github/workflows/windebugmatrix.yml @@ -0,0 +1,165 @@ +# Debug builds and unit tests on Windows, with the latest MinGW and MSVC +# toolchains, on x64, x86 and (MSVC only) arm64, on both Windows 2022 (MSVC +# v143 / Visual Studio 2022) and the very latest Windows images (MSVC v145 / +# Visual Studio 2026, on windows-2025-vs2026 and windows-11-vs2026-arm). +# Unlike vcmatrix.yml/unixmatrix.yml (which build in Release mode and +# package the result), this workflow only compiles and runs the test suite +# in Debug configuration, so that the sanitizers enabled for Debug builds +# (see the "if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR +# CMAKE_CONFIGURATION_TYPES)" block in the top-level CMakeLists.txt) +# actually get exercised on Windows. There is no MinGW entry beyond +# windows-2022/x64/x86, and no MinGW arm64 entry at all: no such +# configuration exists anywhere else in this repo's workflows (only +# vc17/vc18 have an arm64 variant, and only vc17 is built on windows-2022), +# so MinGW stays on windows-2022 here too. +# +# gaol, the interval library codac is built upon, is built by CMake along +# with codac (see scripts/CMakeModules/codac_gaol.cmake), and always in +# Release, whatever the configuration of codac. Under MSVC this means +# codac's own Debug build is forced onto the Release CRT (/MD instead of +# the default /MDd) so it stays link-compatible with that gaol.lib; see the +# CMAKE_MSVC_RUNTIME_LIBRARY override in the top-level CMakeLists.txt for +# the details of what that trades away (debug-CRT heap/iterator checking) +# versus what it keeps (unoptimized code, debug symbols, ASan). +# +# The MinGW entries simply pin the newest MinGW version (same as +# unixmatrix.yml's own mingw15 entries): unlike MSVC, the choco `mingw` +# package does not ship libasan.a/libubsan.a at all here, for either x86 or +# x64, in any of the versions it offers (checked via `g++ +# -print-file-name=libasan*`/`libubsan*` across mingw15 down to mingw11) -- +# there is no version/architecture combination worth hunting for. The +# top-level CMakeLists.txt detects this (the same capability probe used for +# the MSVC ASan runtime DLL below) and simply leaves Debug builds +# unsanitized on a toolchain that lacks the runtime, rather than failing at +# link time with "ld.exe: cannot find -lasan/-lubsan"; actual ASan coverage +# on Windows comes from the MSVC entries in this matrix instead. +on: + push: + branches-ignore: + - codac2_codac4matlab + tags-ignore: ['**'] # Ignore all tag pushes + pull_request: + branches-ignore: + - codac2_codac4matlab + +concurrency: + # Keep only the newest run of this workflow for a given branch or pull + # request: a push that supersedes another leaves the older run computing a + # result nobody will read, while its jobs hold runners the newest run is + # waiting for. + # + # The three release branches are excluded. They are where the jobs upload + # their packages to a GitHub release (see the "github.ref_name==" conditions + # further down), and cancelling such a run halfway would leave the release + # with only part of its assets. The same expression is used in every + # workflow of this directory, including the ones that publish nothing, so + # that the rule stays a single thing to know. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref_name != 'codac1' && github.ref_name != 'codac2' && github.ref_name != 'codac2_codac4matlab' }} + +jobs: + windebugmatrix: + runs-on: ${{ matrix.cfg.os }} + # A test binary that cannot load a DLL it needs does not always fail fast: + # on the Visual Studio 2026 images every test of the suite instead sat + # until ctest's own 1500 s timeout, so the job kept going for the six hours + # GitHub allows before killing it, twice per run. Two hours is well above + # the twenty minutes a healthy job takes here and turns such a hang into a + # quick red job rather than a day of runner time. + timeout-minutes: 120 + defaults: + run: + shell: ${{ matrix.cfg.shell }} + strategy: + fail-fast: false + matrix: + cfg: + - { os: windows-2022, shell: cmd, arch: x64 , bitness: 64, runtime: mingw15, cmake_params: '-G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Debug', cmake_flags: '-fPIC', desc: 'Windows MinGW x64 Debug (unsanitized)' } + - { os: windows-2022, shell: cmd, arch: x86 , bitness: 32, runtime: mingw15, cmake_params: '-G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Debug', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW x86 Debug (unsanitized)' } + - { os: windows-2022, shell: cmd, arch: x64 , bitness: 64, runtime: vc17 , cmake_params: '-G "Visual Studio 17" -T v143 -A x64' , cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', desc: 'Windows Visual Studio 2022 x64 Debug' } + - { os: windows-2022, shell: cmd, arch: x86 , bitness: 32, runtime: vc17 , cmake_params: '-G "Visual Studio 17" -T v143 -A Win32', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', choco_flags: '--x86', desc: 'Windows Visual Studio 2022 x86 Debug' } + - { os: windows-11-arm, shell: cmd, arch: arm64, bitness: 64, runtime: vc17 , cmake_params: '-G "Visual Studio 17" -T v143 -A arm64', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', desc: 'Windows Visual Studio 2022 arm64 Debug' } + - { os: windows-2025-vs2026 , shell: cmd, arch: x64 , bitness: 64, runtime: vc18, cmake_params: '-G "Visual Studio 18" -T v145 -A x64' , cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', desc: 'Windows Visual Studio 2026 x64 Debug' } + - { os: windows-2025-vs2026 , shell: cmd, arch: x86 , bitness: 32, runtime: vc18, cmake_params: '-G "Visual Studio 18" -T v145 -A Win32', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', choco_flags: '--x86', desc: 'Windows Visual Studio 2026 x86 Debug' } + - { os: windows-11-vs2026-arm, shell: cmd, arch: arm64, bitness: 64, runtime: vc18, cmake_params: '-G "Visual Studio 18" -T v145 -A arm64', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', desc: 'Windows Visual Studio 2026 arm64 Debug' } + name: ${{ matrix.cfg.desc }} + steps: + - uses: actions/checkout@v7 + with: + submodules: true + fetch-depth: 0 + clean: false + - run: echo "VERBOSE=1" >> $GITHUB_ENV + shell: bash + - run: | + choco install -y -r --no-progress wget + wget https://lebarsfa.github.io/cache/cmake_extra_tools.zip --no-check-certificate -nv + 7z x cmake_extra_tools.zip -o"%SystemDrive%" -y + del /f /q cmake_extra_tools.zip + wget https://gist.github.com/lebarsfa/237841f9e5dad55ef192713b3b1b2f16/raw/04d77ced3457346c55f183ca12a10dbcb850e6d5/refreshenv.bashrc --no-check-certificate -nv + move /y refreshenv.bashrc %USERPROFILE% + # MinGW is pinned to the single newest version choco offers (15.2.0): + # none of its versions ship libasan/libubsan for this package, for + # either x86 or x64 (see the header comment above), so there is no + # ASan/UBSan-capable candidate left to probe for -- the top-level + # CMakeLists.txt detects the missing runtime itself and builds + # unsanitized instead. BASHMINGWPATH is written to $GITHUB_ENV so every + # later step (regardless of shell) picks it up automatically, without + # needing choco's own refreshenv.bashrc dance. + - run: | + choco install -y -r --no-progress mingw --version=15.2.0 --force ${{ matrix.cfg.choco_flags }} + echo "BASHMINGWPATH=/c/ProgramData/mingw64/mingw${{ matrix.cfg.bitness }}/bin" >> "$GITHUB_ENV" + if: startsWith(matrix.cfg.runtime, 'mingw') + shell: bash + - run: | + if [ -n "${BASHMINGWPATH:-}" ]; then export PATH="$BASHMINGWPATH:$PATH" ; fi + mkdir build ; cd build + cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON .. + cmake --build . -j 4 --config Debug --target install + cd .. + shell: bash + - run: | + if [ -n "${BASHMINGWPATH:-}" ]; then export PATH="$BASHMINGWPATH:$PATH" ; fi + cd build && ctest -C Debug -V --output-on-failure + cd .. + shell: bash + # A test killed by a signal loses everything it had written to its + # block-buffered standard output, so ctest reports the signal and nothing + # else. That is all this job has ever shown for the tests that crash on it, + # every one of them a user of Eigen's matrix product (arithmetic_mul and + # arithmetic_div crash where arithmetic_add and arithmetic_sub pass), and a + # bare signal says nothing about where. Re-running the first few failures + # under gdb prints the faulting frame, which is the one thing missing to + # diagnose them. The step reports rather than judges: it only runs after a + # failure, and never turns a green job red on its own. + - name: Backtrace of the failed tests + if: failure() + continue-on-error: true + run: | + if [ -n "${BASHMINGWPATH:-}" ]; then export PATH="$BASHMINGWPATH:$PATH" ; fi + + if ! command -v gdb > /dev/null 2>&1 ; then + echo "No gdb in this toolchain, skipping the backtraces." + exit 0 + fi + + failed=build/Testing/Temporary/LastTestsFailed.log + if [ ! -f "$failed" ] ; then + echo "ctest recorded no failed test, nothing to trace." + exit 0 + fi + + cd build + # LastTestsFailed.log holds one ":" per line; the C++ + # tests are the "_cpp" ones and their executable is the target + # itself, next to the tests directory of this single-configuration + # build. Three backtraces are plenty to identify a common frame, and + # keep this step short. + sed 's/^[0-9]*://' "../$failed" | grep '_cpp$' | head -3 | while read -r name ; do + exe="tests/${name%_cpp}.exe" + [ -f "$exe" ] || continue + echo "===================== $exe =====================" + gdb -batch -ex "set confirm off" -ex run -ex "bt 40" --args "./$exe" 2>&1 | tail -60 + done + shell: bash + diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f9796472..042e2e36a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -485,10 +485,11 @@ # WITH_COVERAGE instruments the build and adds two targets: `coverage`, which # runs the whole ctest suite and turns the counters gcov leaves behind into a - # report, and `coverage-report`, which only does the second half, so that a - # developer measuring coverage by hand and a continuous integration job - # reporting it use the same flags, the same gcov and the same filters, and - # cannot drift apart. Deciding all of that here + # report, and `coverage-report`, which only does the second half. That is what + # the "Ubuntu 26.04 GCC x86_64 Release coverage" job of + # .github/workflows/unixdebug.yml now calls, so that a developer measuring + # coverage by hand and the job reporting it use the same flags, the same gcov + # and the same filters, and cannot drift apart. Deciding all of that here # rather than leaving it to the caller is also what spares everyone the traps # below. # From d364001e9f4dc2dd0bd31aaa15463291a35ac3bd Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:36 +0200 Subject: [PATCH 15/19] Run the examples as tests in the package and wheel matrices --- .github/workflows/dockermatrix.yml | 2 +- .github/workflows/macosmatrix.yml | 7 ++++++- .github/workflows/unixmatrix.yml | 2 +- .github/workflows/vcmatrix.yml | 7 ++++++- scripts/docker/build_pybinding.sh | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dockermatrix.yml b/.github/workflows/dockermatrix.yml index e7cde280d..1a606a2b3 100644 --- a/.github/workflows/dockermatrix.yml +++ b/.github/workflows/dockermatrix.yml @@ -75,7 +75,7 @@ jobs: sudo apt-get -q update ; sudo apt-get -y install catch2 dpkg-dev || true ; \ fi && \ mkdir build ; cd build && \ - cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D BUILD_TESTS=ON -D CMAKE_INSTALL_PREFIX="../codac" .. && \ + cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D CMAKE_INSTALL_PREFIX="../codac" .. && \ cmake --build . -j 4 --config Release --target install && \ cd .. && \ zip -q -r codac_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip codac && \ diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 81340e020..6bb0ac012 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -94,7 +94,7 @@ jobs: if: runner.os=='macOS' - run: | mkdir build ; cd build - cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_SYSTEM_NAME=Darwin -D CMAKE_OSX_ARCHITECTURES=${{ matrix.cfg.arch }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D WITH_CAPD=OFF -D WITH_PYTHON=ON -D PYBIND11_FINDPYTHON=OFF .. + cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_SYSTEM_NAME=Darwin -D CMAKE_OSX_ARCHITECTURES=${{ matrix.cfg.arch }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D WITH_CAPD=OFF -D WITH_PYTHON=ON -D PYBIND11_FINDPYTHON=OFF .. cmake --build . -j 4 --config Release --target install cmake --build . --config Release --target pip_package ; cp `ls *.whl` ../`ls *.whl | sed "s/py3-none-any/cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}-cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}${{ matrix.cfg.cpcfg }}/"` cd .. @@ -120,6 +120,11 @@ jobs: # they are still executed in Release on the very same systems by # unixmatrix.yml -- Visual Studio 2022 on x86/x64/arm64, macOS Sonoma # arm64 and Sequoia x86_64. + # The same "_py" filter picks up the Python examples, which + # -D TEST_EXAMPLES=ON registers as codac2_examples_*_py. Unlike the C++ + # ones they do run on the interpreter of each entry, so they belong + # here; the C++ examples are only compiled, and run by unixmatrix.yml + # along with the C++ tests. cd build && ctest -C Release -R "_py" -V --output-on-failure cd .. shell: bash diff --git a/.github/workflows/unixmatrix.yml b/.github/workflows/unixmatrix.yml index 6c0b801fc..eec870acb 100644 --- a/.github/workflows/unixmatrix.yml +++ b/.github/workflows/unixmatrix.yml @@ -179,7 +179,7 @@ jobs: - run: | if [ ${{ runner.os }} = Windows ]; then source ~/refreshenv.bashrc ; refreshenv ; export PATH=$BASHMINGWPATH:$BASHCMAKEPATH:$PATH ; fi mkdir build ; cd build - cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D BUILD_TESTS=ON -D CMAKE_INSTALL_PREFIX="../codac" .. + cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D CMAKE_INSTALL_PREFIX="../codac" .. cmake --build . -j 4 --config Release --target install cd .. sed_param=s/PATH_SUFFIXES\ /PATHS\ \$\{CMAKE_CURRENT_LIST_FILE\}\\/..\\/..\\/..\\/..\\/\ PATH_SUFFIXES\ / diff --git a/.github/workflows/vcmatrix.yml b/.github/workflows/vcmatrix.yml index e301e24a8..5788546b4 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -92,7 +92,7 @@ jobs: WITH_PYTHON=${WITH_PYTHON:-ON} echo "Configuring with WITH_PYTHON=$WITH_PYTHON" mkdir build ; cd build - cmake -E env CXXFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" CFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D WITH_CAPD=OFF -D WITH_PYTHON=$WITH_PYTHON -D PYBIND11_FINDPYTHON=OFF .. + cmake -E env CXXFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" CFLAGS=" /MP4 /wd4267 /wd4244 /wd4305 /wd4996" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D WITH_CAPD=OFF -D WITH_PYTHON=$WITH_PYTHON -D PYBIND11_FINDPYTHON=OFF .. cmake --build . -j 4 --config Release --target install if [ "$WITH_PYTHON" = "ON" ]; then cmake --build . --config Release --target pip_package ; cp `ls *.whl` ../`ls *.whl | sed "s/py3-none-any/cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}-cp${{ matrix.cfg.py_v_maj }}${{ matrix.cfg.py_v_min }}${{ matrix.cfg.cpcfg }}/"` @@ -125,6 +125,11 @@ jobs: # they are still executed in Release on the very same systems by # unixmatrix.yml -- Visual Studio 2022 on x86/x64/arm64, macOS Sonoma # arm64 and Sequoia x86_64. + # The same "_py" filter picks up the Python examples, which + # -D TEST_EXAMPLES=ON registers as codac2_examples_*_py. Unlike the C++ + # ones they do run on the interpreter of each entry, so they belong + # here; the C++ examples are only compiled, and run by unixmatrix.yml + # along with the C++ tests. cd build && ctest -C Release -R "_py" -V --output-on-failure cd .. shell: bash diff --git a/scripts/docker/build_pybinding.sh b/scripts/docker/build_pybinding.sh index dbea1b2da..8c33d405e 100755 --- a/scripts/docker/build_pybinding.sh +++ b/scripts/docker/build_pybinding.sh @@ -86,7 +86,7 @@ for PYBIN in /opt/python/cp3*/bin; do "${PYBIN}/python" -m pip install --upgrade pip "${PYBIN}/python" -m pip install --upgrade wheel setuptools build mkdir -p build_dir && cd build_dir - cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. + cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. make -j4 make pip_package From ec26dde18c902d707f0e75df328b7194031950c3 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:36 +0200 Subject: [PATCH 16/19] Run the examples as tests in the Windows Debug jobs too --- .github/workflows/unixdebug.yml | 6 +++--- .github/workflows/windebugmatrix.yml | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/unixdebug.yml b/.github/workflows/unixdebug.yml index 7e0c058d8..903ae7a71 100644 --- a/.github/workflows/unixdebug.yml +++ b/.github/workflows/unixdebug.yml @@ -12,9 +12,9 @@ # machine. This workflow closes that gap, and macdebug.yml does the same for # macOS. # -# It also runs the examples, which no other workflow does beyond the two that -# tests.yml builds by hand: -D TEST_EXAMPLES=ON now builds every example against -# the in-tree library and runs it as an integration test (see +# It also runs the examples, as every workflow of this directory that runs the +# test suite now does: -D TEST_EXAMPLES=ON builds every example against the +# in-tree library and runs it as an integration test (see # examples/CMakeLists.txt). Under a sanitizer they are worth as much as the unit # tests: they exercise long chains of the library the way a user writes them. # diff --git a/.github/workflows/windebugmatrix.yml b/.github/workflows/windebugmatrix.yml index 0e0ec4b0a..31f6e83dc 100644 --- a/.github/workflows/windebugmatrix.yml +++ b/.github/workflows/windebugmatrix.yml @@ -64,8 +64,10 @@ jobs: # on the Visual Studio 2026 images every test of the suite instead sat # until ctest's own 1500 s timeout, so the job kept going for the six hours # GitHub allows before killing it, twice per run. Two hours is well above - # the twenty minutes a healthy job takes here and turns such a hang into a - # quick red job rather than a day of runner time. + # the forty minutes or so a healthy job takes here -- twenty for the build + # and the unit tests, and about as much again for the examples under ASan + # on MSVC -- and turns such a hang into a quick red job rather than a day of + # runner time. timeout-minutes: 120 defaults: run: @@ -114,7 +116,7 @@ jobs: - run: | if [ -n "${BASHMINGWPATH:-}" ]; then export PATH="$BASHMINGWPATH:$PATH" ; fi mkdir build ; cd build - cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON .. + cmake -E env CXXFLAGS="${{ matrix.cfg.cmake_flags }}" CFLAGS="${{ matrix.cfg.cmake_flags }}" cmake ${{ matrix.cfg.cmake_params }} -D CMAKE_INSTALL_PREFIX="../codac" -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON .. cmake --build . -j 4 --config Debug --target install cd .. shell: bash @@ -153,10 +155,12 @@ jobs: # LastTestsFailed.log holds one ":" per line; the C++ # tests are the "_cpp" ones and their executable is the target # itself, next to the tests directory of this single-configuration - # build. Three backtraces are plenty to identify a common frame, and - # keep this step short. + # build -- or in the examples directory, for the examples that + # -D TEST_EXAMPLES=ON registers the same way. Three backtraces are + # plenty to identify a common frame, and keep this step short. sed 's/^[0-9]*://' "../$failed" | grep '_cpp$' | head -3 | while read -r name ; do exe="tests/${name%_cpp}.exe" + [ -f "$exe" ] || exe="examples/${name%_cpp}.exe" [ -f "$exe" ] || continue echo "===================== $exe =====================" gdb -batch -ex "set confirm off" -ex run -ex "bt 40" --args "./$exe" 2>&1 | tail -60 From 50e71e9be02fa2de9d301b0770969b0aa3b38cab Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:36 +0200 Subject: [PATCH 17/19] Set CMAKE_POSITION_INDEPENDENT_CODE before add_subdirectory(src) --- CMakeLists.txt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f65251466..ebb87e361 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,13 +164,23 @@ # Compile sources ################################################################################ + # Python binding: + option(WITH_PYTHON "Build Python binding" OFF) + if(WITH_PYTHON) + # The Python modules are shared libraries that the static libraries of src/ + # are linked into, so these have to be position independent code as well. + # CMAKE_POSITION_INDEPENDENT_CODE only initializes the + # POSITION_INDEPENDENT_CODE property of the targets created after it is set: + # it has to be set before add_subdirectory(src). Set after it, as it used to + # be, it reached the Python modules only, which are position independent + # anyway, and -fPIC had to be given by hand in CMAKE_CXX_FLAGS. + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + endif() + add_subdirectory(src) # C++ sources add_subdirectory(doc) # documentation (Doxygen + Sphinx manual) - # Python binding: - option(WITH_PYTHON "Build Python binding" OFF) if(WITH_PYTHON) - set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) add_subdirectory(python) endif() From dcf274ade366e7cde6840e964ffe82bdce5ba904 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:36 +0200 Subject: [PATCH 18/19] Build Catch2 for the macOS deployment target, not Homebrew's --- .github/workflows/macosmatrix.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 6bb0ac012..8ed0b4536 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -52,8 +52,12 @@ jobs: shell: bash #- run: brew install eigen # if: runner.os=='macOS' - - run: brew install catch2 # Issues with binary packages when cross-compiling... - if: (runner.os=='macOS')&&(matrix.cfg.cross!=true) + # Catch2 is not taken from Homebrew: its package is built for the macOS of + # the runner, newer than the MACOSX_DEPLOYMENT_TARGET of these jobs, and the + # linker warned "was built for newer 'macOS' version (14.0) than being + # linked (10.16)" for each of its objects, in every test (some 76000 lines + # per run). tests/CMakeLists.txt then builds Catch2 with the deployment + # target and the architecture of the job. # Doxygen 1.18.0, the version Homebrew currently installs, segfaults on the # Intel runners while parsing this project's headers, and does so before # writing doc/api/xml/index.xml, so cmake cannot even configure -- From 21235fe8b278ab06196b77621150957612aebb44 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 13:37:36 +0200 Subject: [PATCH 19/19] Drop the -fPIC given by hand in the workflows, scripts and manual --- .github/workflows/macosmatrix.yml | 22 +++++++++---------- .github/workflows/tests.yml | 2 +- .github/workflows/unixdebug.yml | 2 -- doc/manual/development/info_dev.rst | 4 ++-- packages/temporary/gennewcodacpi_armhf.sh | 2 +- scripts/docker/build_pybinding.sh | 2 +- .../docker/build_pybinding_codac4matlab.sh | 2 +- 7 files changed, 17 insertions(+), 19 deletions(-) diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 8ed0b4536..a147d1bb8 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -21,17 +21,17 @@ jobs: fail-fast: false matrix: cfg: - - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , cmake_flags: '-fPIC', trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 14, desc: 'macOS Sonoma Python 3.14 arm64' } - - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , cmake_flags: '-fPIC', trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 13, desc: 'macOS Sonoma Python 3.13 arm64' } - - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , cmake_flags: '-fPIC', trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 12, desc: 'macOS Sonoma Python 3.12 arm64' } - - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , cmake_flags: '-fPIC', trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 11, desc: 'macOS Sonoma Python 3.11 arm64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 14, desc: 'macOS Sequoia Python 3.14 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 13, desc: 'macOS Sequoia Python 3.13 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 12, desc: 'macOS Sequoia Python 3.12 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 11, desc: 'macOS Sequoia Python 3.11 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 10, desc: 'macOS Sequoia Python 3.10 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 9 , desc: 'macOS Sequoia Python 3.9 x86_64' } - - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , cmake_flags: '-fPIC', trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 8 , desc: 'macOS Sequoia Python 3.8 x86_64' } + - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 14, desc: 'macOS Sonoma Python 3.14 arm64' } + - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 13, desc: 'macOS Sonoma Python 3.13 arm64' } + - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 12, desc: 'macOS Sonoma Python 3.12 arm64' } + - { os: macos-14 , shell: bash, arch: arm64 , runtime: sonoma , trgt: '11.0' , cpcfg: '-macosx_11_0_arm64' , py_v_maj: 3, py_v_min: 11, desc: 'macOS Sonoma Python 3.11 arm64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 14, desc: 'macOS Sequoia Python 3.14 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 13, desc: 'macOS Sequoia Python 3.13 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 12, desc: 'macOS Sequoia Python 3.12 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 11, desc: 'macOS Sequoia Python 3.11 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 10, desc: 'macOS Sequoia Python 3.10 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 9 , desc: 'macOS Sequoia Python 3.9 x86_64' } + - { os: macos-15-intel , shell: bash, arch: x86_64 , runtime: sequoia , trgt: '10.16', cpcfg: '-macosx_10_16_x86_64', py_v_maj: 3, py_v_min: 8 , desc: 'macOS Sequoia Python 3.8 x86_64' } name: ${{ matrix.cfg.desc }} steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5a274af83..078d27331 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -90,7 +90,7 @@ jobs: cd build # Building lib + tests - cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DWITH_CAPD=${{ matrix.cfg.with_capd }} -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON .. + cmake -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DWITH_CAPD=${{ matrix.cfg.with_capd }} -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON .. make -j 4 #make doc # todo make install diff --git a/.github/workflows/unixdebug.yml b/.github/workflows/unixdebug.yml index 903ae7a71..0d10240a9 100644 --- a/.github/workflows/unixdebug.yml +++ b/.github/workflows/unixdebug.yml @@ -128,8 +128,6 @@ jobs: cmake \ -D WITH_COVERAGE=${{ matrix.cfg.coverage && 'ON' || 'OFF' }} \ -D CMAKE_BUILD_TYPE=${{ matrix.cfg.build_type }} \ - -D CMAKE_CXX_FLAGS="-fPIC" \ - -D CMAKE_C_FLAGS="-fPIC" \ -D CMAKE_INSTALL_PREFIX="../codac" \ -D BUILD_TESTS=ON \ -D TEST_EXAMPLES=ON \ diff --git a/doc/manual/development/info_dev.rst b/doc/manual/development/info_dev.rst index 4f18a4548..4b597c5a7 100644 --- a/doc/manual/development/info_dev.rst +++ b/doc/manual/development/info_dev.rst @@ -79,12 +79,12 @@ If you simply want to use the latest Codac release in Python, you can download t Note that you will then have to ``import codac2`` instead of ``import codac`` in your Python scripts. - You will need to compile Codac using the ``-fPIC`` options (a GAOL built by CMake along with Codac is compiled that way on its own; a GAOL installed on your system has to have been as well), and to configure ``WITH_PYTHON=ON`` and ``PYBIND11_FINDPYTHON=OFF``. Note that CMake will automatically get the `pybind11 `_ files required for the binding. Also, you will have to configure ``BUILD_TESTS=ON`` if you want to run the unit tests. + You will need to configure ``WITH_PYTHON=ON`` and ``PYBIND11_FINDPYTHON=OFF``. Codac is then compiled as position independent code (``-fPIC``) on its own, as is a GAOL built by CMake along with Codac; a GAOL installed on your system has to have been compiled that way as well. Note that CMake will automatically get the `pybind11 `_ files required for the binding. Also, you will have to configure ``BUILD_TESTS=ON`` if you want to run the unit tests. .. code-block:: bash mkdir build ; cd build - cmake -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH="$HOME/doxygen/build_install" -DCMAKE_BUILD_TYPE=Release .. + cmake -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF -DBUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=$HOME/codac/build_install -DCMAKE_PREFIX_PATH="$HOME/doxygen/build_install" -DCMAKE_BUILD_TYPE=Release .. make ; make install 3. **Configure your Python environment**: diff --git a/packages/temporary/gennewcodacpi_armhf.sh b/packages/temporary/gennewcodacpi_armhf.sh index 87f72fae0..81a016801 100644 --- a/packages/temporary/gennewcodacpi_armhf.sh +++ b/packages/temporary/gennewcodacpi_armhf.sh @@ -37,7 +37,7 @@ python3 -m pip install \$PIP_OPTIONS --upgrade wheel --prefer-binary --extra-ind # With setuptools upgrade from pip, building the .whl might fail for bullseye... \\ #python3 -m pip install \$PIP_OPTIONS --upgrade setuptools --prefer-binary --extra-index-url https://www.piwheels.org/simple && \\ mkdir -p build_dir_\$(lsb_release -cs) && cd build_dir_\$(lsb_release -cs) && \ -cmake -E env CXXFLAGS=\"-fPIC\" CFLAGS=\"-fPIC\" cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. && \ +cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. && \ make -j4 && \ \ make pip_package && \ diff --git a/scripts/docker/build_pybinding.sh b/scripts/docker/build_pybinding.sh index 8c33d405e..73b759795 100755 --- a/scripts/docker/build_pybinding.sh +++ b/scripts/docker/build_pybinding.sh @@ -86,7 +86,7 @@ for PYBIN in /opt/python/cp3*/bin; do "${PYBIN}/python" -m pip install --upgrade pip "${PYBIN}/python" -m pip install --upgrade wheel setuptools build mkdir -p build_dir && cd build_dir - cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. + cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DTEST_EXAMPLES=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. make -j4 make pip_package diff --git a/scripts/docker/build_pybinding_codac4matlab.sh b/scripts/docker/build_pybinding_codac4matlab.sh index 83578aeed..17bfb158a 100644 --- a/scripts/docker/build_pybinding_codac4matlab.sh +++ b/scripts/docker/build_pybinding_codac4matlab.sh @@ -86,7 +86,7 @@ for PYBIN in /opt/python/cp3*/bin; do "${PYBIN}/python" -m pip install --upgrade pip "${PYBIN}/python" -m pip install --upgrade wheel setuptools build mkdir -p build_dir && cd build_dir - cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. + cmake -DPYTHON_EXECUTABLE=${PYBIN}/python -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DWITH_CAPD=OFF -DWITH_PYTHON=ON -DPYBIND11_FINDPYTHON=OFF .. make -j4 make pip_package