From fa2665b947c45e3c43e604bb985123690c9ad610 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 01/45] Ignore .pydevproject and CMakePresets.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 64523ced0..d4f0b655a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ .cproject .settings .project +.pydevproject # waf generated stuff .waf* @@ -56,6 +57,7 @@ doc/doxygen/xml/* build/ build_*/ build-*/ +CMakePresets.json # Latex files *.aux From dd38950accc69fa38977b1a3cb09f72afad32de4 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 02/45] Remove the IBEX conversions and the IBEX functions of vibes.h --- src/core/CMakeLists.txt | 2 - src/core/tools/ibex/codac2_ibex.h | 110 -------------------- src/core/tools/ibex/codac2_ibex_impl.h | 82 --------------- src/graphics/3rd/vibes/vibes.h | 34 ------ tests/CMakeLists.txt | 7 -- tests/core/tools/ibex/codac2_tests_ibex.cpp | 86 --------------- tests/core/tools/ibex/codac2_tests_ibex.py | 19 ---- 7 files changed, 340 deletions(-) delete mode 100644 src/core/tools/ibex/codac2_ibex.h delete mode 100644 src/core/tools/ibex/codac2_ibex_impl.h delete mode 100644 tests/core/tools/ibex/codac2_tests_ibex.cpp delete mode 100644 tests/core/tools/ibex/codac2_tests_ibex.py diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index db02e9526..8166deb2a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -276,8 +276,6 @@ ${CMAKE_CURRENT_SOURCE_DIR}/tools/codac2_trunc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tools/codac2_trunc.h ${CMAKE_CURRENT_SOURCE_DIR}/tools/codac2_TypeInfo.h - ${CMAKE_CURRENT_SOURCE_DIR}/tools/ibex/codac2_ibex.h - ${CMAKE_CURRENT_SOURCE_DIR}/tools/ibex/codac2_ibex_impl.h ${CMAKE_CURRENT_SOURCE_DIR}/trajectory/codac2_AnalyticTraj.h ${CMAKE_CURRENT_SOURCE_DIR}/trajectory/codac2_SampledTraj.h diff --git a/src/core/tools/ibex/codac2_ibex.h b/src/core/tools/ibex/codac2_ibex.h deleted file mode 100644 index 021b7156b..000000000 --- a/src/core/tools/ibex/codac2_ibex.h +++ /dev/null @@ -1,110 +0,0 @@ -/** - * \file codac2_ibex.h - * ---------------------------------------------------------------------------- - * \date 2024 - * \author Simon Rohou - * \copyright Copyright 2024 Codac Team - * \license GNU Lesser General Public License (LGPL) - */ - -#pragma once - -#ifdef _IBEX_INTERVAL_H_ // only available if IBEX has been included beforehand - -#include -#include -#include -#include -#include -#include "codac2_Interval.h" -#include "codac2_Vector.h" -#include "codac2_IntervalVector.h" -#include "codac2_Matrix.h" -#include "codac2_IntervalMatrix.h" - -namespace codac2 -{ - /** - * \brief Casts a Codac Interval object into an IBEX Interval object - * - * \param x const Codac type Interval - * \return const IBEX type Interval - */ - ibex::Interval to_ibex(const codac2::Interval& x); - - /** - * \brief Casts an IBEX Interval object into a Codac Interval object - * - * \param x const IBEX type Interval - * \return Codac type Interval - */ - codac2::Interval to_codac(const ibex::Interval& x); - - /** - * \brief Casts a Codac Vector object into an IBEX Vector object - * - * \param x const Codac type Vector - * \return IBEX type Vector - */ - ibex::Vector to_ibex(const codac2::Vector& x); - - /** - * \brief Casts an IBEX Vector object into a Codac Vector object - * - * \param x const IBEX type Vector - * \return Codac type Vector - */ - codac2::Vector to_codac(const ibex::Vector& x); - - /** - * \brief Casts a Codac IntervalVector object into an IBEX IntervalVector object - * - * \param x const Codac type IntervalVector - * \return IBEX type IntervalVector - */ - ibex::IntervalVector to_ibex(const codac2::IntervalVector& x); - - /** - * \brief Casts an IBEX IntervalVector object into a Codac IntervalVector object - * - * \param x const IBEX type IntervalVector - * \return Codac type IntervalVector - */ - codac2::IntervalVector to_codac(const ibex::IntervalVector& x); - - /** - * \brief Casts a Codac Matrix object into an IBEX Matrix object - * - * \param x const Codac type Matrix - * \return IBEX type Matrix - */ - ibex::Matrix to_ibex(const codac2::Matrix& x); - - /** - * \brief Casts an IBEX Matrix object into a Codac Matrix object - * - * \param x const IBEX type Matrix - * \return Codac type Matrix - */ - codac2::Matrix to_codac(const ibex::Matrix& x); - - /** - * \brief Casts a Codac IntervalMatrix object into an IBEX IntervalMatrix object - * - * \param x const Codac type IntervalMatrix - * \return IBEX type IntervalMatrix - */ - ibex::IntervalMatrix to_ibex(const codac2::IntervalMatrix& x); - - /** - * \brief Casts an IBEX IntervalMatrix object into a Codac IntervalMatrix object - * - * \param x const IBEX type IntervalMatrix - * \return Codac type IntervalMatrix - */ - codac2::IntervalMatrix to_codac(const ibex::IntervalMatrix& x); -} - -#include "codac2_ibex_impl.h" - -#endif \ No newline at end of file diff --git a/src/core/tools/ibex/codac2_ibex_impl.h b/src/core/tools/ibex/codac2_ibex_impl.h deleted file mode 100644 index 406472a46..000000000 --- a/src/core/tools/ibex/codac2_ibex_impl.h +++ /dev/null @@ -1,82 +0,0 @@ -/** - * codac2_ibex_impl.h - * ---------------------------------------------------------------------------- - * \date 2024 - * \author Gilles Chabert, (Simon Rohou) - * \copyright Copyright 2024 Codac Team - * \license GNU Lesser General Public License (LGPL) - */ - -#pragma once -#include - -// Inline functions - -namespace codac2 -{ - inline ibex::Interval to_ibex(const Interval& x) - { - return { x.lb(), x.ub() }; - } - - inline Interval to_codac(const ibex::Interval& x) - { - return { x.lb(), x.ub() }; - } - - #define cast_vector(OutputType,convert) \ - \ - OutputType x_(x.size()); \ - for(Index i = 0 ; i < (Index)x.size() ; i++) \ - x_[i] = convert(x[i]); \ - return x_; \ - - inline ibex::Vector to_ibex(const codac2::Vector& x) - { - cast_vector(ibex::Vector,double); - } - - inline codac2::Vector to_codac(const ibex::Vector& x) - { - cast_vector(codac2::Vector,double); - } - - inline ibex::IntervalVector to_ibex(const codac2::IntervalVector& x) - { - cast_vector(ibex::IntervalVector,to_ibex); - } - - inline codac2::IntervalVector to_codac(const ibex::IntervalVector& x) - { - cast_vector(codac2::IntervalVector,to_codac); - } - - #define cast_matrix(OutputType,convert_f,output_ij,input_ij,rows_,cols_) \ - \ - OutputType x_(x.rows_(), x.cols_()); \ - for(Index i = 0 ; i < (Index)x.rows_() ; i++) \ - for(Index j = 0 ; j < (Index)x.cols_() ; j++) \ - output_ij = convert_f(input_ij); \ - return x_; \ - - inline ibex::Matrix to_ibex(const codac2::Matrix& x) - { - cast_matrix(ibex::Matrix,double,x_[i][j],x(i,j),rows,cols); - } - - inline codac2::Matrix to_codac(const ibex::Matrix& x) - { - cast_matrix(codac2::Matrix,double,x_(i,j),x[i][j],nb_rows,nb_cols); - } - - inline ibex::IntervalMatrix to_ibex(const codac2::IntervalMatrix& x) - { - cast_matrix(ibex::IntervalMatrix,to_ibex,x_[i][j],x(i,j),rows,cols); - } - - inline codac2::IntervalMatrix to_codac(const ibex::IntervalMatrix& x) - { - cast_matrix(codac2::IntervalMatrix,to_codac,x_(i,j),x[i][j],nb_rows,nb_cols); - } - -} \ No newline at end of file diff --git a/src/graphics/3rd/vibes/vibes.h b/src/graphics/3rd/vibes/vibes.h index 4c46b0f35..cb928413f 100644 --- a/src/graphics/3rd/vibes/vibes.h +++ b/src/graphics/3rd/vibes/vibes.h @@ -462,15 +462,6 @@ namespace vibes { /** @} */ // end of group figure - // Ibex enabled functions - #ifdef _IBEX_INTERVAL_H_ - VIBES_FUNC_COLOR_PARAM_2(drawBox,const ibex::Interval &,x, const ibex::Interval &,y) - #endif //#ifdef _IBEX_INTERVAL_H_ - #ifdef __IBEX_INTERVAL_VECTOR_H__ - VIBES_FUNC_COLOR_PARAM_1(drawBox,const ibex::IntervalVector &,box); - VIBES_FUNC_COLOR_PARAM_1(drawBoxes, const std::vector &, boxes); - #endif //#ifdef __IBEX_INTERVAL_VECTOR_H__ - // // Inline Implementations @@ -521,31 +512,6 @@ namespace vibes { setObjectProperties(objectName, Params(key, value)); } - // Ibex enabled functions - #ifdef _IBEX_INTERVAL_H_ - inline void drawBox(const ibex::Interval &x, const ibex::Interval &y, Params params) { - drawBox(x.lb(),x.ub(),y.lb(),y.ub(),params); - } - #endif //#ifdef _IBEX_INTERVAL_H_ - #ifdef __IBEX_INTERVAL_VECTOR_H__ - /// \todo N-dimensionanl Ibex Inteval vector support - inline void drawBox(const ibex::IntervalVector &box, Params params) { - drawBox(box[0], box[1], params); - } - inline void drawBoxes(const std::vector &boxes, Params params){ - std::vector > bounds; - for(unsigned int i=0;i boundsI; - boundsI.push_back(boxes[i][0].lb()); - boundsI.push_back(boxes[i][0].ub()); - boundsI.push_back(boxes[i][1].lb()); - boundsI.push_back(boxes[i][1].ub()); - bounds.push_back(boundsI); - } - vibes::drawBoxes(bounds, params); - } - #endif //#ifdef __IBEX_INTERVAL_VECTOR_H__ } /// diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dff12a6f4..9a8bb6789 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -107,7 +107,6 @@ 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 ../doc/manual/manual/tools/src core/trajectory/codac2_tests_AnalyticTraj @@ -151,12 +150,6 @@ if (WITH_CAPD) set(CODAC_LIBRARIES ${CODAC_LIBRARIES} ${PROJECT_NAME}-capd capd::capd) endif() -# IBEX test -#if (WITH_IBEX) - #find_package(IBEX REQUIRED) - #ibex_init_common() # IBEX should have installed this function - #message(STATUS "Found IBEX version ${IBEX_VERSION}") -#endif() foreach(SRC_TEST ${SRC_TESTS}) string(REPLACE "/" "_" TEST_NAME ${SRC_TEST}) diff --git a/tests/core/tools/ibex/codac2_tests_ibex.cpp b/tests/core/tools/ibex/codac2_tests_ibex.cpp deleted file mode 100644 index be5c869d4..000000000 --- a/tests/core/tools/ibex/codac2_tests_ibex.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Codac tests - * ---------------------------------------------------------------------------- - * \date 2024 - * \author Simon Rohou - * \copyright Copyright 2024 Codac Team - * \license GNU Lesser General Public License (LGPL) - */ - -#include -#include -#include - -using namespace std; -using codac2::oo; - -TEST_CASE("IBEX") -{ - // [Interval] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::Interval(1,2)) == ibex::Interval(1,2)); - CHECK(codac2::to_ibex(codac2::Interval(-oo,oo)) == ibex::Interval()); - CHECK(codac2::to_ibex(codac2::Interval::empty()) == ibex::Interval::empty_set()); - } - - // [Interval] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::Interval(1,2)) == codac2::Interval(1,2)); - CHECK(codac2::to_codac(ibex::Interval()) == codac2::Interval(-oo,oo)); - CHECK(codac2::to_codac(ibex::Interval::empty_set()) == codac2::Interval::empty()); - } - - // [Vector] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::Vector::zero(2)) == ibex::Vector(2)); - CHECK(codac2::to_ibex(codac2::Vector({1,2,3})) == ibex::Vector({1,2,3})); - } - - // [Vector] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::Vector::zeros(2)) == codac2::Vector::zero(2)); - CHECK(codac2::to_codac(ibex::Vector({1,2,3})) == codac2::Vector({1,2,3})); - } - - // [IntervalVector] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::IntervalVector(2)) == ibex::IntervalVector(2)); - CHECK(codac2::to_ibex(codac2::IntervalVector({{1,2},{2,3},{-oo,oo}})) - == ibex::IntervalVector({{1,2},{2,3},ibex::Interval()})); - } - - // [IntervalVector] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::IntervalVector(2)) == codac2::IntervalVector(2)); - CHECK(codac2::to_codac(ibex::IntervalVector({{1,2},{2,3},ibex::Interval()})) - == codac2::IntervalVector({{1,2},{2,3},{-oo,oo}})); - } - - // [Matrix] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::Matrix::zero(2,3)) == ibex::Matrix(2,3)); - CHECK(codac2::to_ibex(codac2::Matrix({{1,2,3},{4,5,6}})) - == ibex::Matrix({{1,2,3},{4,5,6}})); - } - - // [Matrix] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::Matrix(2,3)) == codac2::Matrix::zero(2,3)); - CHECK(codac2::to_codac(ibex::Matrix({{1,2,3},{4,5,6}})) - == codac2::Matrix({{1,2,3},{4,5,6}})); - } - - // [IntervalMatrix] Codac -> IBEX - { - CHECK(codac2::to_ibex(codac2::IntervalMatrix(2,3)) == ibex::IntervalMatrix(2,3)); - CHECK(codac2::to_ibex(codac2::IntervalMatrix({{{-oo,1},{-oo,2},{-oo,3}},{{-oo,4},{-oo,5},{-oo,6}}})) - == ibex::IntervalMatrix({{{-oo,1},{-oo,2},{-oo,3}},{{-oo,4},{-oo,5},{-oo,6}}})); - } - - // [IntervalMatrix] IBEX -> Codac - { - CHECK(codac2::to_codac(ibex::IntervalMatrix(2,3)) == codac2::IntervalMatrix(2,3)); - CHECK(codac2::to_codac(ibex::IntervalMatrix({{{-oo,1},{-oo,2},{-oo,3}},{{-oo,4},{-oo,5},{-oo,6}}})) - == codac2::IntervalMatrix({{{-oo,1},{-oo,2},{-oo,3}},{{-oo,4},{-oo,5},{-oo,6}}})); - } -} \ No newline at end of file diff --git a/tests/core/tools/ibex/codac2_tests_ibex.py b/tests/core/tools/ibex/codac2_tests_ibex.py deleted file mode 100644 index 462f43bea..000000000 --- a/tests/core/tools/ibex/codac2_tests_ibex.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python - -# Codac tests -# ---------------------------------------------------------------------------- -# \date 2024 -# \author Simon Rohou -# \copyright Copyright 2024 Codac Team -# \license GNU Lesser General Public License (LGPL) - -import unittest -from codac import * - -class TestIbex(unittest.TestCase): - - def test_ibex(self): - self.assertTrue(True) # IBEX is not available in Python - -if __name__ == '__main__': - unittest.main() \ No newline at end of file From a331552d0a4111b8b9cc1191f5ccf4c7cf7f307f Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 03/45] 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 a1d79b9c1af783808336a5ba4150c631ea3ef963 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 04/45] 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 | 747 ++++++++++++++++++ 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 | 2 +- tests/test_codac/CMakeLists.txt | 13 +- 32 files changed, 1113 insertions(+), 130 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..e4cbb439a --- /dev/null +++ b/scripts/CMakeModules/codac_gaol.cmake @@ -0,0 +1,747 @@ +# ================================================================== +# 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. +# +# 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") + + +################################################################################ +# 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() + find_package(gaol CONFIG QUIET) + if(gaol_FOUND) + set(_from package) + set(_version "${gaol_VERSION}") + message(STATUS "Found GAOL ${gaol_VERSION}, CMake package in ${gaol_DIR}") + 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(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) + if(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) + find_package(gaol CONFIG REQUIRED NO_DEFAULT_PATH + PATHS "${CODAC_GAOL_INSTALL_TREE}/${CODAC_INSTALL_LIBDIR_3RD}/cmake/gaol") + 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. +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 CONFIG REQUIRED NO_DEFAULT_PATH + PATHS \"\${_codac_prefix}/${CODAC_INSTALL_LIBDIR_3RD}/cmake/gaol\")") + else() + set(_find "find_package(gaol 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 8166deb2a..93b8bf070 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -325,7 +325,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 9a8bb6789..7d8b8ca7a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -160,7 +160,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 b17a741816d73fc2f7465b508bbc386a2b2c88f4 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 05/45] 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/dependencies/install_ibex.sh | 17 ------------ scripts/docker/build_pybinding.sh | 5 ---- .../docker/build_pybinding_codac4matlab.sh | 5 ---- 11 files changed, 9 insertions(+), 90 deletions(-) delete mode 100644 scripts/dependencies/install_ibex.sh 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/dependencies/install_ibex.sh b/scripts/dependencies/install_ibex.sh deleted file mode 100644 index 375c96500..000000000 --- a/scripts/dependencies/install_ibex.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -cd $HOME -echo 'Installing IBEX in ' $HOME '...'; -if [ ! -e "ibex-lib/README.md" ]; then - #git clone -b master https://github.com/lebarsfa/ibex-lib.git ; - git clone -b ibex-2.8.9.20260819 https://github.com/lebarsfa/ibex-lib.git ; # To test a specific version of IBEX... - cd ibex-lib ; - mkdir build && cd build ; - cmake -E env CXXFLAGS="-fPIC" CFLAGS="-fPIC" cmake -DCMAKE_INSTALL_PREFIX=$HOME/ibex-lib/build_install -DCMAKE_BUILD_TYPE=Release .. ; - make -j 4 ; -else - echo 'Using cached directory.' ; -fi -cd $HOME/ibex-lib/build -make install -cd $HOME \ No newline at end of file diff --git a/scripts/docker/build_pybinding.sh b/scripts/docker/build_pybinding.sh index ab98904f3..7b6a34572 100755 --- a/scripts/docker/build_pybinding.sh +++ b/scripts/docker/build_pybinding.sh @@ -2,11 +2,6 @@ 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/ - 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..4c9177fb0 100644 --- a/scripts/docker/build_pybinding_codac4matlab.sh +++ b/scripts/docker/build_pybinding_codac4matlab.sh @@ -2,11 +2,6 @@ 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/ - git config --global --add safe.directory /io cd /io From 3c3ce5176a6e7147b3e80ea5b373c965bffd0ef2 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 06/45] Drop the MinGW jobs whose mingw-w64 the GAOL fork refuses --- .github/workflows/unixmatrix.yml | 30 +------------------ packages/choco/codac/codac.nuspec | 2 +- .../choco/codac/tools/chocolateyinstall.ps1 | 4 +-- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/.github/workflows/unixmatrix.yml b/.github/workflows/unixmatrix.yml index 6c0b801fc..b94dff014 100644 --- a/.github/workflows/unixmatrix.yml +++ b/.github/workflows/unixmatrix.yml @@ -43,14 +43,6 @@ jobs: #- { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: vc14 , cmake_params: '-G "Visual Studio 17" -T v140 -A Win32,version=10.0.19041.0 -D CMAKE_SYSTEM_VERSION=10.0.14393.0', cmake_flags: ' /MP4 /wd4267 /wd4244 /wd4305 /wd4996', test_config: 'Release/', choco_flags: '--x86', desc: 'Windows Visual Studio 2015 x86' } - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw15 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 15.2.0 x64' } - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw15 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 15.2.0 x86' } - - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw14 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 14.2.0 x64' } - - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw14 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 14.2.0 x86' } - - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw13 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 13.2.0 x64' } - - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw13 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 13.2.0 x86' } - - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw12 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 12.2.0 x64' } - - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw12 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 12.2.0 x86' } - - { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw11 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 11.2.0 x64' } - - { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw11 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 11.2.0 x86' } # Problems related to C++20 for MinGW 8 and earlier? #- { os: windows-2022 , shell: cmd , arch: x64 , bitness: 64, runtime: mingw8 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', desc: 'Windows MinGW 8.1.0 x64' } #- { os: windows-2022 , shell: cmd , arch: x86 , bitness: 32, runtime: mingw8 , cmake_params: '-G "MinGW Makefiles"', cmake_flags: '-fPIC', choco_flags: '--x86', desc: 'Windows MinGW 8.1.0 x86' } @@ -117,26 +109,6 @@ jobs: %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? echo export BASHMINGWPATH=/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc if: (matrix.cfg.runtime=='mingw8') - - run: | - choco install -y -r --no-progress mingw --version=11.2.0.07112021 --force ${{ matrix.cfg.choco_flags }} - %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? - echo export BASHMINGWPATH=/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc - if: (matrix.cfg.runtime=='mingw11') - - run: | - choco install -y -r --no-progress mingw --version=12.2.0.03042023 --force ${{ matrix.cfg.choco_flags }} - %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? - echo export BASHMINGWPATH=/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc - if: (matrix.cfg.runtime=='mingw12') - - run: | - choco install -y -r --no-progress mingw --version=13.2.0 --force ${{ matrix.cfg.choco_flags }} - %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\mingw64\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? - echo export BASHMINGWPATH=/c/ProgramData/mingw64/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc - if: (matrix.cfg.runtime=='mingw13') - - run: | - choco install -y -r --no-progress mingw --version=14.2.0 --force ${{ matrix.cfg.choco_flags }} - %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\mingw64\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? - echo export BASHMINGWPATH=/c/ProgramData/mingw64/mingw${{ matrix.cfg.bitness }}/bin>>%USERPROFILE%\.bashrc - if: (matrix.cfg.runtime=='mingw14') - run: | choco install -y -r --no-progress mingw --version=15.2.0 --force ${{ matrix.cfg.choco_flags }} %SystemDrive%\cmake_extra_tools\pathman /as C:\ProgramData\mingw64\mingw${{ matrix.cfg.bitness }}\bin & cd. & rem Non-zero exit code...? @@ -219,7 +191,7 @@ jobs: cd ../../.. checksum -f=codac.$PACKAGE_VERSION.nupkg -t=sha256 choco install -y -r --no-progress --ignore-dependencies -s . codac --pre --params "'/url:./codac_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip'" - if [ ${{ matrix.cfg.runtime }} != mingw13 ] || [ ${{ matrix.cfg.arch }} != x64 ]; then rm -Rf codac.$PACKAGE_VERSION.nupkg ; fi # To avoid upload conflicts of the same file... + if [ ${{ matrix.cfg.runtime }} != mingw15 ] || [ ${{ matrix.cfg.arch }} != x64 ]; then rm -Rf codac.$PACKAGE_VERSION.nupkg ; fi # To avoid upload conflicts of the same file... checksum -f=codac_${{ matrix.cfg.arch }}_${{ matrix.cfg.runtime }}.zip -t=sha256 shell: bash if: runner.os=='Windows' diff --git a/packages/choco/codac/codac.nuspec b/packages/choco/codac/codac.nuspec index 924073029..e0baeccf8 100644 --- a/packages/choco/codac/codac.nuspec +++ b/packages/choco/codac/codac.nuspec @@ -44,7 +44,7 @@ choco install -y --ignore-dependencies codac --params "'/url:https://github.com/ https://github.com/codac-team/codac/releases - + diff --git a/packages/choco/codac/tools/chocolateyinstall.ps1 b/packages/choco/codac/tools/chocolateyinstall.ps1 index d4155c393..f21d6cc14 100644 --- a/packages/choco/codac/tools/chocolateyinstall.ps1 +++ b/packages/choco/codac/tools/chocolateyinstall.ps1 @@ -32,9 +32,9 @@ New-Item -ItemType Directory -Force -Path $root | Out-Null $AllowedUrlPrefix = 'https://github.com/codac-team/codac' if (!$pp['url']) { - $url = 'https://github.com/codac-team/codac/releases/download/v1/codac_x86_mingw13.zip' + $url = 'https://github.com/codac-team/codac/releases/download/v1/codac_x86_mingw15.zip' $checksum = 'EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE' - $url64 = 'https://github.com/codac-team/codac/releases/download/v1/codac_x64_mingw13.zip' + $url64 = 'https://github.com/codac-team/codac/releases/download/v1/codac_x64_mingw15.zip' $checksum64 = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' $packageArgs = @{ packageName = $env:ChocolateyPackageName From cc551bb845af671160d72443930780f89ecdd82a Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 07/45] Document in the manual that Codac depends on GAOL instead of IBEX --- doc/manual/development/info_dev.rst | 31 +++--------- doc/manual/index.rst | 1 - doc/manual/manual/extensions/capd/capd.rst | 3 +- doc/manual/manual/extensions/sympy/index.rst | 1 - doc/manual/manual/installation/cpp.rst | 52 ++++++-------------- 5 files changed, 23 insertions(+), 65 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/index.rst b/doc/manual/index.rst index 29fe3a84a..f90d5cc4c 100644 --- a/doc/manual/index.rst +++ b/doc/manual/index.rst @@ -344,7 +344,6 @@ User manual * :ref:`sec-extensions` * :ref:`sec-extensions-capd` * :ref:`sec-extensions-sympy` - * Interface with the IBEX library * Frequently Asked Questions 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..04a4ec9b3 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, we recommend installing the version of `the fork of Jordan Ninin `_ rather than the original sources. 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. 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 d8aad5d195566e1d8923cd9434ae19cc7f71d976 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 08/45] Call std::abs in Interval::smag() --- src/core/domains/interval/codac2_Interval_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/domains/interval/codac2_Interval_impl.h b/src/core/domains/interval/codac2_Interval_impl.h index 8e2bd0cfc..e0b1e5e91 100644 --- a/src/core/domains/interval/codac2_Interval_impl.h +++ b/src/core/domains/interval/codac2_Interval_impl.h @@ -152,7 +152,7 @@ namespace codac2 inline double Interval::smag() const { - return (abs(lb()) > abs(ub())) ? lb() : ub(); + return (std::abs(lb()) > std::abs(ub())) ? lb() : ub(); } inline double Interval::smig() const From 2a2a393d1e05528bf57889fcb525493062fd95eb Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 09/45] Call GAOL directly, without the guards its fork makes unnecessary --- .../domains/interval/codac2_Interval_impl.h | 93 +++------------ .../codac2_Interval_operations_impl.h | 111 +++--------------- 2 files changed, 35 insertions(+), 169 deletions(-) diff --git a/src/core/domains/interval/codac2_Interval_impl.h b/src/core/domains/interval/codac2_Interval_impl.h index e0b1e5e91..96e0c641b 100644 --- a/src/core/domains/interval/codac2_Interval_impl.h +++ b/src/core/domains/interval/codac2_Interval_impl.h @@ -26,10 +26,7 @@ namespace codac2 inline Interval::Interval(double a) : gaol::interval(a) - { - if(a == -oo || a == oo) - set_empty(); - } + { } inline Interval::Interval(double a, double b) : gaol::interval(a,b) @@ -83,11 +80,7 @@ namespace codac2 inline Interval& Interval::operator=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator=(x); - + gaol::interval::operator=(x); return *this; } @@ -135,9 +128,7 @@ namespace codac2 inline double Interval::mid() const { - double m = gaol::interval::midpoint(); - gaol::round_upward(); - return m; + return gaol::interval::midpoint(); } inline double Interval::mag() const @@ -192,15 +183,7 @@ namespace codac2 inline double Interval::diam() const { - if(is_empty()) - return std::numeric_limits::quiet_NaN(); - - else - { - double d = gaol::interval::width(); - gaol::round_upward(); - return d; - } + return gaol::interval::width(); } inline double Interval::volume() const @@ -414,20 +397,12 @@ namespace codac2 inline Interval operator+(const Interval& x, double y) { - if(y == -oo || y == oo) - return Interval::empty(); - - else - return gaol::operator+(x,y); + return gaol::operator+(x,y); } inline Interval operator+(double x, const Interval& y) { - if(x == -oo || x == oo) - return Interval::empty(); - - else - return gaol::operator+(x,y); + return gaol::operator+(x,y); } inline Interval operator+(const Interval& x, const Interval& y) @@ -437,20 +412,12 @@ namespace codac2 inline Interval operator-(const Interval& x, double y) { - if(y == -oo || y == oo) - return Interval::empty(); - - else - return gaol::operator-(x, y); + return gaol::operator-(x, y); } inline Interval operator-(double x, const Interval& y) { - if(x == -oo || x == oo) - return Interval::empty(); - - else - return gaol::operator-(x, y); + return gaol::operator-(x, y); } inline Interval operator-(const Interval& x, const Interval& y) @@ -460,20 +427,12 @@ namespace codac2 inline Interval operator*(const Interval& x, double y) { - if(y == -oo || y == oo) - return Interval::empty(); - - else - return gaol::operator*(x,y); + return gaol::operator*(x,y); } inline Interval operator*(double x, const Interval& y) { - if(x == -oo || x == oo) - return Interval::empty(); - - else - return gaol::operator*(x,y); + return gaol::operator*(x,y); } inline Interval operator*(const Interval& x, const Interval& y) @@ -483,20 +442,12 @@ namespace codac2 inline Interval operator/(const Interval& x, double y) { - if(y == -oo || y == oo) - return Interval::empty(); - - else - return gaol::operator/(x,y); + return gaol::operator/(x,y); } inline Interval operator/(double x, const Interval& y) { - if(x == -oo || x == oo) - return Interval::empty(); - - else - return gaol::operator/(x,y); + return gaol::operator/(x,y); } inline Interval operator/(const Interval& x, const Interval& y) @@ -518,10 +469,7 @@ namespace codac2 inline Interval& Interval::operator+=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator+=(x); + gaol::interval::operator+=(x); return *this; } @@ -538,10 +486,7 @@ namespace codac2 inline Interval& Interval::operator-=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator-=(x); + gaol::interval::operator-=(x); return *this; } @@ -553,10 +498,7 @@ namespace codac2 inline Interval& Interval::operator*=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator*=(x); + gaol::interval::operator*=(x); return *this; } @@ -568,10 +510,7 @@ namespace codac2 inline Interval& Interval::operator/=(double x) { - if(x == -oo || x == oo) - set_empty(); - else - gaol::interval::operator/=(x); + gaol::interval::operator/=(x); return *this; } diff --git a/src/core/domains/interval/codac2_Interval_operations_impl.h b/src/core/domains/interval/codac2_Interval_operations_impl.h index 53e33a4ac..6a948e554 100644 --- a/src/core/domains/interval/codac2_Interval_operations_impl.h +++ b/src/core/domains/interval/codac2_Interval_operations_impl.h @@ -25,116 +25,72 @@ namespace codac2 inline Interval sqrt(const Interval& x) { - Interval y = gaol::sqrt(x); - gaol::round_upward(); - return y; + return gaol::sqrt(x); } inline Interval pow(const Interval& x, int p) { - Interval y = gaol::pow(x,p); - //gaol::round_upward(); // not necessary? - return y; + return gaol::pow(x,p); } inline Interval pow(const Interval& x, double p) { - if(p == -oo || p == oo) - return Interval::empty(); - - else - { - Interval y = gaol::pow(x,p); - gaol::round_upward(); - return y; - } + return gaol::pow(x,p); } inline Interval pow(const Interval& x, const Interval& p) { - Interval y = gaol::pow(x,p); - gaol::round_upward(); - return y; + return gaol::pow(x,p); } inline Interval root(const Interval& x, int p) { - // Get the root of the positive part (gaol does - // not consider negative values to be in the definition - // domain of the root function) - - gaol::interval y = gaol::nth_root(x, p>=0 ? p : -p); - - if(p%2 == 1 && x.lb() < 0) - y |= -gaol::nth_root(-x, p >= 0 ? p : -p); + Interval y = gaol::nth_root(x, p >= 0 ? p : -p); if(p < 0) y = 1.0/y; - gaol::round_upward(); return y; } inline Interval exp(const Interval& x) { - Interval y = gaol::exp(x); - gaol::round_upward(); - return y; + return gaol::exp(x); } inline Interval log(const Interval& x) { - if(x.ub() <= 0) // gaol returns (-oo,-DBL_MAX) if x.ub()==0, instead of empty set - return Interval::empty(); - - else - { - Interval y = gaol::log(x); - gaol::round_upward(); - return y; - } + return gaol::log(x); } inline Interval cos(const Interval& x) { - Interval y = gaol::cos(x); - gaol::round_upward(); - return y; + return gaol::cos(x); } inline Interval sin(const Interval& x) { - Interval y = gaol::sin(x); - gaol::round_upward(); - return y; + return gaol::sin(x); } inline Interval tan(const Interval& x) { - Interval y = gaol::tan(x); - gaol::round_upward(); - return y; + return gaol::tan(x); } inline Interval acos(const Interval& x) { - Interval y = gaol::acos(x); - gaol::round_upward(); - return y; + return gaol::acos(x); } inline Interval asin(const Interval& x) { - Interval y = gaol::asin(x); - gaol::round_upward(); - return y; + return gaol::asin(x); } inline Interval atan(const Interval& x) { - Interval y = gaol::atan(x); - gaol::round_upward(); - return y; + return gaol::atan(x); } inline Interval atan2(const Interval& y, const Interval& x) @@ -204,61 +160,32 @@ namespace codac2 inline Interval cosh(const Interval& x) { - Interval y; - if(x.is_unbounded()) - y = Interval(gaol::cosh(x).left(),oo); - else - y = gaol::cosh(x); - gaol::round_upward(); - return y; + return gaol::cosh(x); } inline Interval sinh(const Interval& x) { - Interval y = gaol::sinh(x); - gaol::round_upward(); - return y; + return gaol::sinh(x); } inline Interval tanh(const Interval& x) { - Interval y = gaol::tanh(x); - gaol::round_upward(); - return y; + return gaol::tanh(x); } inline Interval acosh(const Interval& x) { - Interval y = gaol::acosh(x); - gaol::round_upward(); - return y; + return gaol::acosh(x); } inline Interval asinh(const Interval& x) { - if(x.is_empty()) - return Interval::empty(); - - else if(x.lb() >= 0) - return gaol::asinh(x); - - else if(x.ub() <= 0) - return -gaol::asinh(-x); - - else - return { - -gaol::asinh(gaol::interval(0,-x.lb())).right(), - gaol::asinh(gaol::interval(0,x.ub())).right() - }; - - // no round_upward? + return gaol::asinh(x); } inline Interval atanh(const Interval& x) { - Interval y = gaol::atanh(x); - gaol::round_upward(); - return y; + return gaol::atanh(x); } inline Interval abs(const Interval& x) From c32c0c436cc20992f87f1da6e804dfe0dc661bcc Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 10/45] Take atan2 from GAOL, whose fork now implements it --- .../codac2_Interval_operations_impl.h | 62 +------------------ 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/src/core/domains/interval/codac2_Interval_operations_impl.h b/src/core/domains/interval/codac2_Interval_operations_impl.h index 6a948e554..1c5120df2 100644 --- a/src/core/domains/interval/codac2_Interval_operations_impl.h +++ b/src/core/domains/interval/codac2_Interval_operations_impl.h @@ -95,67 +95,7 @@ namespace codac2 inline Interval atan2(const Interval& y, const Interval& x) { - if(y.is_empty() || x.is_empty()) - return Interval::empty(); - - // We handle the special case x=[0,0] separately - else if(x == Interval::zero()) - { - if(y.lb() >= 0) - { - if(y.ub() == 0) - return Interval::empty(); // atan2(0,0) is undefined - else - return Interval::half_pi(); - } - - else if(y.ub() <= 0) - return -Interval::half_pi(); - - else - return Interval(-1,1)*Interval::half_pi(); - } - - else if(x.lb() >= 0) - return atan(y/x); // now, x.ub()>0 -> atan does not give an empty set - - else if(x.ub() <= 0) - { - if(y.lb() >= 0) - return atan(y/x) + Interval::pi(); // x.lb()<0 - else if(y.ub() < 0) - return atan(y/x) - Interval::pi(); - else - return Interval(-1,1)*Interval::pi(); - } - - else - { - if(y.lb() >= 0) - return atan(y/x.ub()) | (atan(y/x.lb()) + Interval::pi()); - - else if(y.ub() <= 0) - { - if(x.lb() != -oo) - { - if(x.ub() != oo) - return (atan(y/x.lb())-Interval::pi()) | atan(y/x.ub()); - else - return (atan(y/x.lb())-Interval::pi()) | Interval::zero(); - } - - else - { - if(x.ub() != oo) - return (-Interval::pi()) | atan(y/x.ub()); - else - return -Interval::pi() | Interval::zero(); - } - } - - else - return Interval(-1,1)*Interval::pi(); - } + return gaol::atan2(y,x); } inline Interval cosh(const Interval& x) From 6f031fc1ae0a1b77c462ef6c06820128e9e2b85b Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 11/45] Build GAOL with the FMA instructions and pass its flags on --- examples/ellipsoid_example/CMakeLists.txt | 21 ++++++++++++--------- scripts/CMakeModules/codac_gaol.cmake | 4 ---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/ellipsoid_example/CMakeLists.txt b/examples/ellipsoid_example/CMakeLists.txt index ad0a314c7..0947d9686 100644 --- a/examples/ellipsoid_example/CMakeLists.txt +++ b/examples/ellipsoid_example/CMakeLists.txt @@ -10,25 +10,28 @@ # Adding Eigen3 - # In case you installed Eigen3 in a local directory, you need - # to specify its path with the CMAKE_PREFIX_PATH option, e.g. - # set(CMAKE_PREFIX_PATH "~/eigen/build_install") - - find_package(Eigen3 3.4 REQUIRED NO_MODULE) - message(STATUS "Found Eigen3 version ${Eigen3_VERSION}") + # Codac builds and installs its own Eigen3 (see CODAC_INCLUDE_DIRS below), + # so no separate find_package(Eigen3) is needed -- and none would find it, + # since that build isn't registered as a system-discoverable package. + # Searching for an unrelated system Eigen3 here would risk pulling in a + # different version than the one codac-core/codac-graphics were compiled + # against, which is exactly the kind of ABI mismatch that broke examples + # like 01_batman, and that CODAC_CXX_FLAGS -- which carries -mfma when codac + # was built with it -- is there to prevent. # Adding Codac - # In case you installed Codac in a local directory, you need + # In case you installed Codac in a local directory, you need # to specify its path with the CMAKE_PREFIX_PATH option. - set(CMAKE_PREFIX_PATH "~/Documents/Code_these/codac/build_install") + # set(CMAKE_PREFIX_PATH "~/codac/build_install") find_package(CODAC REQUIRED) message(STATUS "Found Codac version ${CODAC_VERSION}") + # Compilation 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} Eigen3::Eigen) + target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES}) diff --git a/scripts/CMakeModules/codac_gaol.cmake b/scripts/CMakeModules/codac_gaol.cmake index e4cbb439a..09a1fb615 100644 --- a/scripts/CMakeModules/codac_gaol.cmake +++ b/scripts/CMakeModules/codac_gaol.cmake @@ -379,10 +379,6 @@ function(codac_gaol_build) # 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}") From ef041c3a4c7effeabc177b2fe1e6c0e6924b321a Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 12/45] 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 625e3c3206011e087609b8299646827c56894a55 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 13/45] Add assert_release_unreachable and fix Interval::init_from_list --- .../core/contractors/codac2_py_CtcInverse.h | 4 ++-- .../domains/interval/codac2_Interval_impl.h | 2 +- .../codac2_analytic_flat_input_layout.cpp | 2 +- src/core/geometry/codac2_Polygon.cpp | 3 +-- src/core/tools/codac2_assert.h | 18 ++++++++++++++++++ src/extensions/sympy/codac2_sympy_bridge.cpp | 12 +++++------- .../domains/interval/codac2_tests_Interval.cpp | 14 ++++++++++++++ .../domains/interval/codac2_tests_Interval.py | 15 +++++++++++++++ 8 files changed, 57 insertions(+), 13 deletions(-) diff --git a/python/src/core/contractors/codac2_py_CtcInverse.h b/python/src/core/contractors/codac2_py_CtcInverse.h index 46a5b5fa9..4bada2e13 100644 --- a/python/src/core/contractors/codac2_py_CtcInverse.h +++ b/python/src/core/contractors/codac2_py_CtcInverse.h @@ -81,7 +81,7 @@ void export_CtcInverse(py::module& m, }; if(xs.size() == 0) - assert_release(false && "contract() expects at least one argument"); + assert_release_unreachable("contract() expects at least one argument"); // Fast paths already consistent with the typed overloads @@ -114,7 +114,7 @@ void export_CtcInverse(py::module& m, has_tube_args = true; else { - assert_release(false && "contract(): variadic mode only supports " + assert_release_unreachable("contract(): variadic mode only supports " "Interval / IntervalVector / SlicedTube / SlicedTube"); } } diff --git a/src/core/domains/interval/codac2_Interval_impl.h b/src/core/domains/interval/codac2_Interval_impl.h index 96e0c641b..95d6f384a 100644 --- a/src/core/domains/interval/codac2_Interval_impl.h +++ b/src/core/domains/interval/codac2_Interval_impl.h @@ -72,7 +72,7 @@ namespace codac2 else { - assert_release("'Interval' can only be defined by one or two 'double' values."); + assert_release_unreachable("'Interval' can only be defined by one or two 'double' values."); } return *this; diff --git a/src/core/functions/analytic/codac2_analytic_flat_input_layout.cpp b/src/core/functions/analytic/codac2_analytic_flat_input_layout.cpp index 31365483d..57352603e 100644 --- a/src/core/functions/analytic/codac2_analytic_flat_input_layout.cpp +++ b/src/core/functions/analytic/codac2_analytic_flat_input_layout.cpp @@ -55,7 +55,7 @@ namespace codac2 } else - assert_release(false && "FlatInputLayout: unsupported variable type in function argument list"); + assert_release_unreachable("FlatInputLayout: unsupported variable type in function argument list"); } _size = flat; diff --git a/src/core/geometry/codac2_Polygon.cpp b/src/core/geometry/codac2_Polygon.cpp index efdc4ec8c..a7f909549 100644 --- a/src/core/geometry/codac2_Polygon.cpp +++ b/src/core/geometry/codac2_Polygon.cpp @@ -239,8 +239,7 @@ namespace codac2 return { {p[0],prev_float(oo)}, p }; default: { - assert_release(false && - "failed to test if the point is contained in polygon"); + assert_release_unreachable("failed to test if the point is contained in polygon"); return Segment(IntervalVector(2),IntervalVector(2)); } } diff --git a/src/core/tools/codac2_assert.h b/src/core/tools/codac2_assert.h index 18d5bc891..bccf40ade 100644 --- a/src/core/tools/codac2_assert.h +++ b/src/core/tools/codac2_assert.h @@ -20,6 +20,7 @@ namespace codac2 #define assert_release(ignore_test) ((void)0) #define assert_release_constexpr(ignore_test) ((void)0) + #define assert_release_unreachable(ignore_msg) ((void)0) #else @@ -52,6 +53,17 @@ namespace codac2 } \ } while (0) + // For a branch that must never be reached. Written as + // assert_release(false && "...") until now, whose condition is a + // compile-time constant: MSVC reported one C4127 ("conditional + // expression is constant") per site. Failing unconditionally states the + // intent directly and leaves no condition to warn about; the message is + // also reported on its own, rather than as the text of a false test. + #define assert_release_unreachable(msg) \ + do { \ + CODAC_ASSERT_MESSAGE(msg, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (0) + #else #define assert_release(test) \ @@ -68,6 +80,12 @@ namespace codac2 } \ } while (0) + // See the comment on the __PRETTY_FUNCTION__ variant above. + #define assert_release_unreachable(msg) \ + do { \ + CODAC_ASSERT_MESSAGE(msg, __FILE__, __LINE__, __func__); \ + } while (0) + #endif #endif diff --git a/src/extensions/sympy/codac2_sympy_bridge.cpp b/src/extensions/sympy/codac2_sympy_bridge.cpp index 38799f90e..1da249ac4 100644 --- a/src/extensions/sympy/codac2_sympy_bridge.cpp +++ b/src/extensions/sympy/codac2_sympy_bridge.cpp @@ -238,7 +238,7 @@ namespace codac2 continue; } - assert_release(false && "Unsupported variable type in FlatSymbolTable"); + assert_release_unreachable("Unsupported variable type in FlatSymbolTable"); } } @@ -492,7 +492,7 @@ namespace codac2 return export_matrix_component(child_at(ch,0), op->i(), op->j()); } - assert_release(false && "Unsupported Codac scalar node in SympyExporter"); + assert_release_unreachable("Unsupported Codac scalar node in SympyExporter"); return pybind11::none(); } @@ -509,8 +509,7 @@ namespace codac2 return export_node(child_at(children,i)); } - assert_release(false - && "Unsupported vector-component expression. Supported cases: direct VectorVar components, or vector expressions exposing scalar children via children_expr_base()."); + assert_release_unreachable("Unsupported vector-component expression. Supported cases: direct VectorVar components, or vector expressions exposing scalar children via children_expr_base()."); return pybind11::none(); } @@ -528,8 +527,7 @@ namespace codac2 return export_vector_component(child_at(children,j), i); } - assert_release(false - && "Unsupported matrix-component expression. Supported cases: direct MatrixVar components, or matrix expressions exposing column children via children_expr_base()."); + assert_release_unreachable("Unsupported matrix-component expression. Supported cases: direct MatrixVar components, or matrix expressions exposing column children via children_expr_base()."); return pybind11::none(); } @@ -681,7 +679,7 @@ namespace codac2 if(func.is(sympy.attr("atan2"))) return atan2(x1, x2); } - assert_release(false && "Unsupported SymPy node in importer"); + assert_release_unreachable("Unsupported SymPy node in importer"); return const_value(0.); } } diff --git a/tests/core/domains/interval/codac2_tests_Interval.cpp b/tests/core/domains/interval/codac2_tests_Interval.cpp index e98d6ca55..76c673f4f 100644 --- a/tests/core/domains/interval/codac2_tests_Interval.cpp +++ b/tests/core/domains/interval/codac2_tests_Interval.cpp @@ -16,6 +16,7 @@ #include #include #include +#include using namespace std; using namespace codac2; @@ -53,6 +54,19 @@ TEST_CASE("Interval - tests from IBEX") Interval x, y, z; + // An interval is a point or a pair of bounds; any other list length is a + // caller error and is reported through the assertion layer. A FAST_RELEASE + // build compiles that layer out, which is the one configuration where + // nothing is raised -- the guard mirrors the one in codac2_assert.h. +#if !(defined(FAST_RELEASE) && defined(NDEBUG)) + { + Interval from_list(0.,1.); + CHECK_THROWS_AS(from_list.init_from_list({1.,2.,3.}), std::invalid_argument); + CHECK_THROWS_AS(from_list.init_from_list({}), std::invalid_argument); + CHECK_THROWS_AS(Interval({1.,2.,3.}), std::invalid_argument); + } +#endif + x = Interval(0,1); x.set_empty(); CHECK(x == Interval::empty()); CHECK(x.is_empty()); diff --git a/tests/core/domains/interval/codac2_tests_Interval.py b/tests/core/domains/interval/codac2_tests_Interval.py index 9a505e469..01222b996 100644 --- a/tests/core/domains/interval/codac2_tests_Interval.py +++ b/tests/core/domains/interval/codac2_tests_Interval.py @@ -45,6 +45,21 @@ def test_interval(self): self.assertTrue(Interval([-oo,oo]) == Interval(-oo,oo)); self.assertTrue(Interval([oo,-oo]) == Interval.empty()); + # An interval is a point or a pair of bounds; any other list length is a + # caller error, reported by the C++ assertion layer as a + # std::invalid_argument, which pybind11 surfaces as a ValueError. A + # FAST_RELEASE build compiles that layer out and leaves the interval + # untouched instead; Python cannot see that switch, so both outcomes are + # accepted here and only the one that actually happened is checked. + for bad_list in ([1.,2.,3.], []): + x = Interval(0.,1.) + try: + x.init_from_list(bad_list) + except ValueError: + pass + else: + self.assertTrue(x == Interval(0.,1.)) + x = Interval() ; y = Interval() ; z = Interval() x = Interval(0,1); x.set_empty() From 3a7e48fd2db8dc8cd5e4e1a19817d5b569702926 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 14/45] Break the shared_ptr cycle between PavingNode parents and children --- src/core/domains/paving/codac2_Paving.cpp | 22 ++++--- src/core/domains/paving/codac2_Paving.h | 31 ++++++---- src/core/domains/paving/codac2_PavingNode.h | 10 ++-- .../contractors/codac2_tests_CtcInverse.cpp | 57 +++++++++++++++++++ 4 files changed, 96 insertions(+), 24 deletions(-) diff --git a/src/core/domains/paving/codac2_Paving.cpp b/src/core/domains/paving/codac2_Paving.cpp index c14c2142f..5d39a102f 100644 --- a/src/core/domains/paving/codac2_Paving.cpp +++ b/src/core/domains/paving/codac2_Paving.cpp @@ -18,14 +18,17 @@ namespace codac2 // PavingOut class PavingOut::PavingOut(Index n) - : Paving(n) + : Paving() { assert_release(n > 0); + this->init_tree(IntervalVector(n)); } PavingOut::PavingOut(const IntervalVector& x) - : Paving(x) - { } + : Paving() + { + this->init_tree(x); + } std::list PavingOut::connected_subsets(const PavingOut::NodeValue_& node_value) const { @@ -77,20 +80,23 @@ namespace codac2 IntervalVector operator&(const IntervalVector& x, const PavingOut& p) { return p & x; - }; + } // PavingInOut class PavingInOut::PavingInOut(Index n) - : Paving(n) + : Paving() { assert_release(n > 0); + this->init_tree(IntervalVector(n)); } PavingInOut::PavingInOut(const IntervalVector& x) - : Paving(x) - { } + : Paving() + { + this->init_tree(x); + } std::list PavingInOut::connected_subsets(const PavingInOut::NodeValue_& node_value) const { @@ -141,4 +147,4 @@ namespace codac2 l.push_back(n->unknown()); return l; }; -} \ No newline at end of file +} diff --git a/src/core/domains/paving/codac2_Paving.h b/src/core/domains/paving/codac2_Paving.h index 25f5c8f39..7f55aa161 100644 --- a/src/core/domains/paving/codac2_Paving.h +++ b/src/core/domains/paving/codac2_Paving.h @@ -32,16 +32,19 @@ namespace codac2 using NodeValue_ = std::function(Node_)>; using ConnectedSubset_ = Subpaving

; - Paving(Index n) - : Paving(IntervalVector(n)) - { - assert_release(n > 0); - } - - Paving(const IntervalVector& x) - : _tree(std::make_shared>(*static_cast(this), x)) - { } - + protected: + // Paving(Index n) + // : Paving(IntervalVector(n)) + // { + // assert_release(n > 0); + // } + // Paving(const IntervalVector& x) + // : _tree(std::make_shared>(*static_cast(this), x)) + // { } + + Paving () { } + + public: inline Index size() const { return std::get<0>(_tree->boxes()).size(); @@ -128,6 +131,12 @@ namespace codac2 friend class PavingNode

; + inline void init_tree(const IntervalVector& x) + { + _tree = std::make_shared>(*static_cast(this), x); + } + + inline static NodeTuple_ init_tuple(const IntervalVector& x) { return std::make_tuple(((X)x)...); @@ -191,4 +200,4 @@ namespace codac2 static const NodeValue_ outer, outer_complem, inner, bound, all; }; -} \ No newline at end of file +} diff --git a/src/core/domains/paving/codac2_PavingNode.h b/src/core/domains/paving/codac2_PavingNode.h index e294e1bd7..aa69a0108 100644 --- a/src/core/domains/paving/codac2_PavingNode.h +++ b/src/core/domains/paving/codac2_PavingNode.h @@ -61,7 +61,7 @@ namespace codac2 std::shared_ptr> top() const { - return _top; + return _top.lock(); } std::shared_ptr> top() @@ -102,7 +102,7 @@ namespace codac2 void visit(std::function>)> visitor) const { - if(!_top && !_right && _left && left()->boxes() == _x) + if(_top.expired() && !_right && _left && left()->boxes() == _x) left()->visit(visitor); else if(visitor(this->shared_from_this())) @@ -114,7 +114,7 @@ namespace codac2 void visit(std::function>)> visitor) { - if(!_top && !_right && _left && left()->boxes() == _x) + if(_top.expired() && !_right && _left && left()->boxes() == _x) _left->visit(visitor); else if(visitor(this->shared_from_this())) @@ -157,7 +157,7 @@ namespace codac2 const P& _paving; typename P::NodeTuple_ _x; - std::shared_ptr> _top = nullptr; + std::weak_ptr> _top; std::shared_ptr> _left = nullptr, _right = nullptr; }; -} \ No newline at end of file +} diff --git a/tests/core/contractors/codac2_tests_CtcInverse.cpp b/tests/core/contractors/codac2_tests_CtcInverse.cpp index 952b8a57c..627a4f934 100644 --- a/tests/core/contractors/codac2_tests_CtcInverse.cpp +++ b/tests/core/contractors/codac2_tests_CtcInverse.cpp @@ -19,6 +19,63 @@ using namespace std; using namespace codac2; + +// ============================================================================ +// 1. Paving: tree lifecycle and parent/child reference-counting regression +// ============================================================================ + +TEST_CASE("Paving tree structure after bisection", "[Paving]") +{ + PavingOut p(IntervalVector({{-1.,1.},{-1.,1.}})); + auto root = p.tree(); + + REQUIRE(root->is_leaf()); + CHECK(root->top() == nullptr); + + root->bisect(); + REQUIRE_FALSE(root->is_leaf()); + REQUIRE(root->left() != nullptr); + REQUIRE(root->right() != nullptr); + CHECK(root->left()->top() == root); + CHECK(root->right()->top() == root); + CHECK(root->left()->is_leaf()); + CHECK(root->right()->is_leaf()); + + root->left()->bisect(); + REQUIRE(root->left()->left() != nullptr); + CHECK(root->left()->left()->top() == root->left()); +} + +TEST_CASE("Paving nodes do not leak through a parent/child reference cycle", "[Paving][regression]") +{ + std::weak_ptr w_root, w_left, w_left_left; + + { + PavingOut p(IntervalVector({{-1.,1.},{-1.,1.}})); + auto root = p.tree(); + root->bisect(); + root->left()->bisect(); + + w_root = root; + w_left = root->left(); + w_left_left = root->left()->left(); + + // Sanity check: while 'p' (and so the whole tree) is alive, every + // node must still resolve through its weak_ptr. + REQUIRE_FALSE(w_root.expired()); + REQUIRE_FALSE(w_left.expired()); + REQUIRE_FALSE(w_left_left.expired()); + } + // 'p' is destroyed here. Before the fix, every node below the root + // would still be kept alive by its child's (owning) _top pointing back + // up through the cycle, so none of the weak_ptr's below would expire. + + CHECK(w_root.expired()); + CHECK(w_left.expired()); + CHECK(w_left_left.expired()); +} + + TEST_CASE("CtcInverse") { { From 3c884aca85b1caab07d47149176e216cfffe686f Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 15/45] Fix object ownership in the tube and contractor Python bindings --- .../src/core/contractors/codac2_py_CtcFixpoint.cpp | 2 +- python/src/core/contractors/codac2_py_CtcLazy.cpp | 2 +- python/src/core/contractors/codac2_py_CtcNot.cpp | 2 +- python/src/core/domains/tube/codac2_py_Slice.h | 2 ++ python/src/core/domains/tube/codac2_py_SlicedTube.h | 6 ++++-- python/src/core/domains/tube/codac2_py_TDomain.cpp | 12 +++++++----- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/python/src/core/contractors/codac2_py_CtcFixpoint.cpp b/python/src/core/contractors/codac2_py_CtcFixpoint.cpp index 989c4baa7..9743f82f5 100644 --- a/python/src/core/contractors/codac2_py_CtcFixpoint.cpp +++ b/python/src/core/contractors/codac2_py_CtcFixpoint.cpp @@ -26,7 +26,7 @@ void export_CtcFixpoint(py::module& m, py::class_,pyCtcI exported .def(py::init( - [](const pyCtcIntervalVector& c, double ratio) + [](const CtcBase& c, double ratio) { return std::make_unique(c.copy(),ratio); }), diff --git a/python/src/core/contractors/codac2_py_CtcLazy.cpp b/python/src/core/contractors/codac2_py_CtcLazy.cpp index 10f25c61e..8d995281a 100644 --- a/python/src/core/contractors/codac2_py_CtcLazy.cpp +++ b/python/src/core/contractors/codac2_py_CtcLazy.cpp @@ -26,7 +26,7 @@ void export_CtcLazy(py::module& m, py::class_,pyCtcInter exported .def(py::init( - [](const pyCtcIntervalVector& c) + [](const CtcBase& c) { return std::make_unique(c.copy()); }), diff --git a/python/src/core/contractors/codac2_py_CtcNot.cpp b/python/src/core/contractors/codac2_py_CtcNot.cpp index f7e3ac89e..0769e01e9 100644 --- a/python/src/core/contractors/codac2_py_CtcNot.cpp +++ b/python/src/core/contractors/codac2_py_CtcNot.cpp @@ -26,7 +26,7 @@ void export_CtcNot(py::module& m, py::class_,pyCtcInterv exported .def(py::init( - [](const pyCtcIntervalVector& c) + [](const CtcBase& c) { return std::make_unique(c.copy()); }), diff --git a/python/src/core/domains/tube/codac2_py_Slice.h b/python/src/core/domains/tube/codac2_py_Slice.h index 268ebe3f9..8f260e842 100644 --- a/python/src/core/domains/tube/codac2_py_Slice.h +++ b/python/src/core/domains/tube/codac2_py_Slice.h @@ -33,9 +33,11 @@ py::class_> export_Slice(py::module& m, const std::string& name) // Methods from class SliceBase .def("t0_tf", &Slice::t0_tf, + py::return_value_policy::reference_internal, CONST_INTERVAL_REF_SLICEBASE_T0_TF_CONST) .def("tslice", &Slice::tslice, + py::return_value_policy::reference_internal, CONST_TSLICE_REF_SLICEBASE_TSLICE_CONST) // Methods from class Slice diff --git a/python/src/core/domains/tube/codac2_py_SlicedTube.h b/python/src/core/domains/tube/codac2_py_SlicedTube.h index 85bfc96e4..584f19b34 100644 --- a/python/src/core/domains/tube/codac2_py_SlicedTube.h +++ b/python/src/core/domains/tube/codac2_py_SlicedTube.h @@ -84,9 +84,11 @@ py::class_,TubeBase> export_SlicedTube(py::module& m, const std::s py::return_value_policy::reference, SHARED_PTR_SLICE_T_SLICEDTUBE_T_LAST_SLICE) - .def("slice", [](SlicedTube& x, std::shared_ptr it) -> Slice& + .def("slice", [](SlicedTube& x, TSlice& it) -> Slice& { - return *x.slice(it); + return *x.slice( + std::shared_ptr(&it, [](TSlice*) {}) + ); }, py::return_value_policy::reference, SHARED_PTR_SLICE_T_SLICEDTUBE_T_SLICE_SHARED_PTR_TSLICE, diff --git a/python/src/core/domains/tube/codac2_py_TDomain.cpp b/python/src/core/domains/tube/codac2_py_TDomain.cpp index c061ad9c5..3eadf7dfe 100644 --- a/python/src/core/domains/tube/codac2_py_TDomain.cpp +++ b/python/src/core/domains/tube/codac2_py_TDomain.cpp @@ -40,19 +40,21 @@ void export_TDomain(py::module& m) .def("tslices_vector", &TDomain::tslices_vector, VECTOR_TSLICE_TDOMAIN_TSLICES_VECTOR_CONST) - .def("tslice", [](TDomain& tdomain, double t) -> std::shared_ptr + .def("tslice", [](TDomain& tdomain, double t) -> TSlice& { auto it = tdomain.tslice(t); - return std::shared_ptr(&(*it), [](TSlice*){}); + return *it; }, - LIST_TSLICE_ITERATOR_TDOMAIN_TSLICE_DOUBLE + py::return_value_policy::reference_internal, + LIST_TSLICE_ITERATOR_TDOMAIN_TSLICE_DOUBLE, "t"_a) - .def("sample", [](TDomain& tdomain, double t, bool with_gate) -> std::shared_ptr + .def("sample", [](TDomain& tdomain, double t, bool with_gate) -> TSlice& { auto it = tdomain.sample(t, with_gate); - return std::shared_ptr(&(*it), [](TSlice*){}); + return *it; }, + py::return_value_policy::reference_internal, LIST_TSLICE_ITERATOR_TDOMAIN_SAMPLE_DOUBLE_BOOL, "t"_a, "with_gate"_a=false) From bc3e43ff12a0759e0da605fb11a81d600eee60dd Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 16/45] Fix the acos/sinh bindings and the | in the cart_prod assertions --- .../core/domains/interval/codac2_py_Interval_operations.cpp | 4 ++-- python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/src/core/domains/interval/codac2_py_Interval_operations.cpp b/python/src/core/domains/interval/codac2_py_Interval_operations.cpp index 3d276d446..44e70a351 100644 --- a/python/src/core/domains/interval/codac2_py_Interval_operations.cpp +++ b/python/src/core/domains/interval/codac2_py_Interval_operations.cpp @@ -157,7 +157,7 @@ void export_Interval_operations(py::module& m, py::class_& py_Interval INTERVAL_TAN_CONST_INTERVAL_REF, "x"_a) - .def("atan", &codac2::atan, + .def("acos", &codac2::acos, INTERVAL_ACOS_CONST_INTERVAL_REF, "x"_a) @@ -177,7 +177,7 @@ void export_Interval_operations(py::module& m, py::class_& py_Interval INTERVAL_COSH_CONST_INTERVAL_REF, "x"_a) - .def("asinh", &codac2::asinh, + .def("sinh", &codac2::sinh, INTERVAL_SINH_CONST_INTERVAL_REF, "x"_a) diff --git a/python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp b/python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp index c7bf4e209..7ef0d2998 100644 --- a/python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp +++ b/python/src/core/domains/tube/codac2_py_tube_cart_prod.cpp @@ -27,11 +27,11 @@ void export_tube_cart_prod(py::module& m) Index n = 0; for(const auto& li : l) { - assert_release(is_instance>(li) | is_instance>(li)); + assert_release(is_instance>(li) || is_instance>(li)); n += is_instance>(li) ? 1 : cast>(li).size(); } - assert_release(is_instance>(*l.begin()) | is_instance>(*l.begin())); + assert_release(is_instance>(*l.begin()) || is_instance>(*l.begin())); std::shared_ptr tdomain = is_instance>(*l.begin()) ? cast>(*l.begin()).tdomain() : cast>(*l.begin()).tdomain(); @@ -42,7 +42,7 @@ void export_tube_cart_prod(py::module& m) Index i = 0; for(const auto& li : l) { - assert_release(is_instance>(li) | is_instance>(li)); + assert_release(is_instance>(li) || is_instance>(li)); IntervalVector si = cart_prod( is_instance>(li) ? cart_prod(cast>(li).slice(it)->codomain()) : cast>(li).slice(it)->codomain()); From df870343615d8b5350536ff8b8453e3f5b734aaa Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 17/45] Rename the CAPD PEIBOS result alias T to PEIBOS_CAPD_Result --- src/extensions/capd/codac2_peibos_capd.cpp | 10 +++++----- src/extensions/capd/codac2_peibos_capd.h | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/extensions/capd/codac2_peibos_capd.cpp b/src/extensions/capd/codac2_peibos_capd.cpp index 48eed323d..41e80c19b 100644 --- a/src/extensions/capd/codac2_peibos_capd.cpp +++ b/src/extensions/capd/codac2_peibos_capd.cpp @@ -19,12 +19,12 @@ using namespace std; namespace codac2 { - std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const vector& Sigma, double epsilon, bool verbose) + std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const vector& Sigma, double epsilon, bool verbose) { return PEIBOS(i_map, tf, dt, psi_0, Sigma, epsilon, Vector::zero(psi_0.output_size()), verbose); } - std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const vector& Sigma, double epsilon, const Vector& offset, bool verbose) + std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const vector& Sigma, double epsilon, const Vector& offset, bool verbose) { std::vector time_points; for (double t = 0.; t <= tf; t += dt) @@ -48,7 +48,7 @@ namespace codac2 double true_eps = split(Interval(-1.,1.)*IntervalVector::Ones(m), epsilon, boxes); int nthreads = nb_threads(); - std::vector>> thread_outputs(nthreads); + std::vector>> thread_outputs(nthreads); struct WorkItem { const OctaSym* sigma; const IntervalVector* box; }; std::vector work; @@ -109,7 +109,7 @@ namespace codac2 for (auto& th : threads) th.join(); - std::map> output; + std::map> output; for (auto& vec : thread_outputs) for (auto t : time_points) @@ -130,7 +130,7 @@ namespace codac2 return output; } - std::map> reach_set(const std::map>& peibos_output) + std::map> reach_set(const std::map>& peibos_output) { std::map> output; diff --git a/src/extensions/capd/codac2_peibos_capd.h b/src/extensions/capd/codac2_peibos_capd.h index 9aab7cfd0..3cfe4609d 100644 --- a/src/extensions/capd/codac2_peibos_capd.h +++ b/src/extensions/capd/codac2_peibos_capd.h @@ -36,7 +36,7 @@ namespace codac2 Vector offset; }; - using T = std::tuple; + using PEIBOS_CAPD_Result = std::tuple; /** * \brief PEIBOS algorithm using CAPD for guaranteed ODE propagation. @@ -54,7 +54,7 @@ namespace codac2 * \li The interval vector \f$\mathbf{z}\f$ containing the image \f$\bar{\mathbf{x}}(t))\f$ * \li The interval Jacobian matrix \f$\mathbf{J_f}\f$ containing \f$D\mathbf{\left[x\right]}(t)\f$ */ - std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const std::vector& Sigma, double epsilon, bool verbose = false); + std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const std::vector& Sigma, double epsilon, bool verbose = false); /** * \brief PEIBOS algorithm using CAPD for guaranteed ODE propagation. @@ -73,7 +73,7 @@ namespace codac2 * \li The interval vector \f$\mathbf{z}\f$ containing the image \f$\bar{\mathbf{x}}(t))\f$ * \li The interval Jacobian matrix \f$\mathbf{J_f}\f$ containing \f$D\mathbf{\left[x\right]}(t)\f$ */ - std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const std::vector& Sigma, double epsilon, const Vector& offset, bool verbose = false); + std::map> PEIBOS(const capd::IMap& i_map, double tf, double dt, const AnalyticFunction& psi_0, const std::vector& Sigma, double epsilon, const Vector& offset, bool verbose = false); /** @@ -84,5 +84,5 @@ namespace codac2 * \return A timed map of reach set parallelepipeds. At each time \f$t\f$, the value is a vector of Parallelepipeds enclosing the reach set at time \f$t\f$. * The function \ref parallelepiped_inclusion is used to compute each Parallelepiped from the PEIBOS CAPD output. */ - std::map> reach_set(const std::map>& peibos_output); + std::map> reach_set(const std::map>& peibos_output); } \ No newline at end of file From 3c0e3555940a1b0282dd74ceb045385fc15748aa Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 18/45] Avoid log10(0) when formatting integer values in IPE figures --- src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp index 3b5da24b3..585130497 100644 --- a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp +++ b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp @@ -108,7 +108,13 @@ const std::string format_number(double num, double step) result += "."; double remainder = num - ((double) int_part); // remainder to add int remainder_to_int = std::round(remainder * std::pow(10, -precision)); - int length_of_remainder = std::floor(std::log10(remainder_to_int)) + 1; // for example 12 has a length of 2 + // log10(0) is -inf, which cannot be converted to int: remainder_to_int + // is 0 whenever num is (numerically) an exact integer, e.g. num == 2. + // and step forces a negative precision anyway. Treating that case as + // a length of 0 matches what the "approximation of 0" guard below + // already expects, instead of formatting spurious digits for it. + int length_of_remainder = (remainder_to_int == 0) + ? 0 : std::floor(std::log10(remainder_to_int)) + 1; // for example 12 has a length of 2 // this part is need for the specific case where a number like 1. is represented as 0.999... (int part gives 0 instead of 1) if (length_of_remainder > -precision) From 38c5b2d22bcb15d5f4f237c8536bcf4178e427ee Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 19/45] Replace resize_save_values with Eigen's conservativeResize --- .../src/core/matrices/codac2_py_MatrixBase.h | 6 +-- .../src/core/matrices/codac2_py_VectorBase.h | 6 +-- .../codac2_Matrix_addons_MatrixBase.h | 44 +++++++++---------- .../codac2_Matrix_addons_VectorBase.h | 42 +++++++++--------- .../interval/codac2_tests_IntervalMatrix.cpp | 6 +-- .../interval/codac2_tests_IntervalMatrix.py | 6 +-- .../interval/codac2_tests_IntervalVector.cpp | 8 ++-- .../interval/codac2_tests_IntervalVector.py | 8 ++-- 8 files changed, 63 insertions(+), 63 deletions(-) diff --git a/python/src/core/matrices/codac2_py_MatrixBase.h b/python/src/core/matrices/codac2_py_MatrixBase.h index 457d03fbd..00f8ce98b 100644 --- a/python/src/core/matrices/codac2_py_MatrixBase.h +++ b/python/src/core/matrices/codac2_py_MatrixBase.h @@ -261,12 +261,12 @@ void export_MatrixBase(py::module& m, py::class_& pyclass) DOC_TO_BE_DEFINED, "nb_rows"_a, "nb_cols"_a) - .def("resize_save_values", [](S& x, Index_type nb_rows, Index_type nb_cols) + .def("conservativeResize", [](S& x, Index_type nb_rows, Index_type nb_cols) { matlab::test_integer(nb_rows, nb_cols); - x.resize_save_values(nb_rows, nb_cols); + x.conservativeResize(nb_rows, nb_cols); }, - MATRIX_ADDONS_MATRIXBASE_VOID_RESIZE_SAVE_VALUES_INDEX_INDEX, + "Resize the matrix while preserving the existing coefficients.", "nb_rows"_a, "nb_cols"_a) .def_static("zero", [](Index_type r, Index_type c) diff --git a/python/src/core/matrices/codac2_py_VectorBase.h b/python/src/core/matrices/codac2_py_VectorBase.h index 89b41e4cb..feea0fa83 100644 --- a/python/src/core/matrices/codac2_py_VectorBase.h +++ b/python/src/core/matrices/codac2_py_VectorBase.h @@ -105,12 +105,12 @@ void export_VectorBase([[maybe_unused]] py::module& m, py::class_& pyclass) DOC_TO_BE_DEFINED, "n"_a) - .def("resize_save_values", [](S& x, Index_type n) + .def("conservativeResize", [](S& x, Index_type n) { matlab::test_integer(n); - x.resize_save_values(n); + x.conservativeResize(n); }, - MATRIX_ADDONS_VECTORBASE_VOID_RESIZE_SAVE_VALUES_INDEX, + "Resize the vector while preserving the existing coefficients.", "n"_a) .def("put", [](S& x, Index_type start_id, const S& x1) diff --git a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_MatrixBase.h b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_MatrixBase.h index b56b95309..969c8cd9e 100644 --- a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_MatrixBase.h +++ b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_MatrixBase.h @@ -154,25 +154,25 @@ inline static Matrix random(Index r, Index c) return DenseBase>::Random(r,c); } -/** - * \brief Resizes the matrix to (``r``,``c``), preserving existing values where possible. - * - * \param r New number of rows. - * \param c New number of columns. - * - * \details - * This function resizes the matrix while preserving the data in the overlapping region - * of the old and new sizes. Unlike Eigen's ``resize()``, which discards old data, this - * function copies existing values into the resized matrix. - */ -template - requires (!IsVectorOrRow) -inline void resize_save_values(Index r, Index c) -{ - // With resize() of Eigen, the data is reallocated and all previous values are lost. - auto copy = *this; - this->resize(r,c); - for(Index i = 0 ; i < std::min((Index)copy.rows(),r) ; i++) - for(Index j = 0 ; j < std::min((Index)copy.cols(),c) ; j++) - (*this)(i,j) = copy(i,j); -} \ No newline at end of file +// /** +// * \brief Resizes the matrix to (``r``,``c``), preserving existing values where possible. +// * +// * \param r New number of rows. +// * \param c New number of columns. +// * +// * \details +// * This function resizes the matrix while preserving the data in the overlapping region +// * of the old and new sizes. Unlike Eigen's ``resize()``, which discards old data, this +// * function copies existing values into the resized matrix. +// */ +// template +// requires (!IsVectorOrRow) +// inline void resize_save_values(Index r, Index c) +// { +// // With resize() of Eigen, the data is reallocated and all previous values are lost. +// auto copy = *this; +// this->resize(r,c); +// for(Index i = 0 ; i < std::min((Index)copy.rows(),r) ; i++) +// for(Index j = 0 ; j < std::min((Index)copy.cols(),c) ; j++) +// (*this)(i,j) = copy(i,j); +// } \ No newline at end of file diff --git a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_VectorBase.h b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_VectorBase.h index f5021ebf5..63e706508 100644 --- a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_VectorBase.h +++ b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_VectorBase.h @@ -195,24 +195,24 @@ inline void put(Index start_id, const MatrixBase& x) this->segment(start_id,x.size()) << x; } -/** - * \brief Resizes the vector or row matrix to size \p n, preserving existing values. - * - * \pre The matrix is a vector or row vector. - * - * \param n The new size. - * - * \details - * Eigen's ``resize()`` discards existing data, so this function copies existing - * values before resizing and restores them afterward. - */ -template - requires IsVectorOrRow -inline void resize_save_values(Index n) -{ - // With resize() of Eigen, the data is reallocated and all previous values are lost. - auto copy = *this; - this->resize(n); - for(Index i = 0 ; i < std::min((Index)copy.size(),n) ; i++) - (*this)[i] = copy[i]; -} \ No newline at end of file +// /** +// * \brief Resizes the vector or row matrix to size \p n, preserving existing values. +// * +// * \pre The matrix is a vector or row vector. +// * +// * \param n The new size. +// * +// * \details +// * Eigen's ``resize()`` discards existing data, so this function copies existing +// * values before resizing and restores them afterward. +// */ +// template +// requires IsVectorOrRow +// inline void resize_save_values(Index n) +// { +// // With resize() of Eigen, the data is reallocated and all previous values are lost. +// auto copy = *this; +// this->resize(n); +// for(Index i = 0 ; i < std::min((Index)copy.size(),n) ; i++) +// (*this)[i] = copy[i]; +// } \ No newline at end of file diff --git a/tests/core/domains/interval/codac2_tests_IntervalMatrix.cpp b/tests/core/domains/interval/codac2_tests_IntervalMatrix.cpp index f54c0825f..491cb0354 100644 --- a/tests/core/domains/interval/codac2_tests_IntervalMatrix.cpp +++ b/tests/core/domains/interval/codac2_tests_IntervalMatrix.cpp @@ -304,7 +304,7 @@ TEST_CASE("IntervalMatrix") IntervalVector r2({{-1,0},{-2,0}}); m1.row(0) = r1.transpose().eval(); m1.row(1) = r2.transpose().eval(); - m1.resize_save_values(2,3); + m1.conservativeResize(2,3); m1(0,2) = Interval(0,3); m1(1,2) = Interval(-3,0); @@ -315,7 +315,7 @@ TEST_CASE("IntervalMatrix") IntervalMatrix m1(1,3); IntervalVector r1({{0,1},{0,2},{0,3}}); m1.row(0) = r1.transpose().eval(); - m1.resize_save_values(2,3); + m1.conservativeResize(2,3); m1(1,0) = Interval(-1,0); m1(1,1) = Interval(-2,0); m1(1,2) = Interval(-3,0); @@ -325,7 +325,7 @@ TEST_CASE("IntervalMatrix") { IntervalMatrix e(IntervalMatrix::empty(1,1)); - e.resize_save_values(2,3); + e.conservativeResize(2,3); CHECK(e.is_empty()); } diff --git a/tests/core/domains/interval/codac2_tests_IntervalMatrix.py b/tests/core/domains/interval/codac2_tests_IntervalMatrix.py index f68cc037b..6af49f47b 100644 --- a/tests/core/domains/interval/codac2_tests_IntervalMatrix.py +++ b/tests/core/domains/interval/codac2_tests_IntervalMatrix.py @@ -262,7 +262,7 @@ def test_intervalmatrix(self): r2 = IntervalVector([[-1,0],[-2,0]]) m1.set_row(0,r1.transpose()) m1.set_row(1,r2.transpose()) - m1.resize_save_values(2,3) + m1.conservativeResize(2,3) m1[0,2] = Interval(0,3) m1[1,2] = Interval(-3,0) @@ -271,7 +271,7 @@ def test_intervalmatrix(self): m1 = IntervalMatrix(1,3) r1 = IntervalVector([[0,1],[0,2],[0,3]]) m1.set_row(0,r1.transpose()) - m1.resize_save_values(2,3) + m1.conservativeResize(2,3) m1[1,0] = Interval(-1,0) m1[1,1] = Interval(-2,0) m1[1,2] = Interval(-3,0) @@ -279,7 +279,7 @@ def test_intervalmatrix(self): self.assertTrue(m1 == self.M1()) e = IntervalMatrix.empty(1,1) - e.resize_save_values(2,3) + e.conservativeResize(2,3) self.assertTrue(e.is_empty()) m1 = IntervalMatrix(self.M1()) diff --git a/tests/core/domains/interval/codac2_tests_IntervalVector.cpp b/tests/core/domains/interval/codac2_tests_IntervalVector.cpp index 04e4be366..738e5fa60 100644 --- a/tests/core/domains/interval/codac2_tests_IntervalVector.cpp +++ b/tests/core/domains/interval/codac2_tests_IntervalVector.cpp @@ -112,7 +112,7 @@ TEST_CASE("IntervalVector") { IntervalVector x(1); x[0] = Interval(1,2); - x.resize_save_values(3); + x.conservativeResize(3); CHECK(x.size() == 3); CHECK(x[0] == Interval(1,2)); CHECK(x[1] == Interval(-oo,oo)); @@ -122,7 +122,7 @@ TEST_CASE("IntervalVector") { IntervalVector x(1); x[0] = Interval(1,2); - x.resize_save_values(1); + x.conservativeResize(1); CHECK(x.size() == 1); CHECK(x[0] == Interval(1,2)); } @@ -131,7 +131,7 @@ TEST_CASE("IntervalVector") IntervalVector x(2); x[0] = Interval(1,2); x.set_empty(); - x.resize_save_values(3); + x.conservativeResize(3); CHECK(x.size() == 3); CHECK(x.is_empty()); CHECK(x[2] == Interval(-oo,oo)); @@ -141,7 +141,7 @@ TEST_CASE("IntervalVector") IntervalVector x(5); x[0] = Interval(1,2); x[1] = Interval(3,4); - x.resize_save_values(2); + x.conservativeResize(2); CHECK(x.size() == 2); CHECK(x[0] == Interval(1,2)); CHECK(x[1] == Interval(3,4)); diff --git a/tests/core/domains/interval/codac2_tests_IntervalVector.py b/tests/core/domains/interval/codac2_tests_IntervalVector.py index 168b4254a..05d22f692 100644 --- a/tests/core/domains/interval/codac2_tests_IntervalVector.py +++ b/tests/core/domains/interval/codac2_tests_IntervalVector.py @@ -92,7 +92,7 @@ def test_intervalvector(self): x = IntervalVector(1) x[0] = Interval(1,2) - x.resize_save_values(3) + x.conservativeResize(3) self.assertTrue(x.size() == 3) self.assertTrue(x[0] == Interval(1,2)) self.assertTrue(x[1] == Interval(-oo,oo)) @@ -100,14 +100,14 @@ def test_intervalvector(self): x = IntervalVector(1) x[0] = Interval(1,2) - x.resize_save_values(1) + x.conservativeResize(1) self.assertTrue(x.size() == 1) self.assertTrue(x[0] == Interval(1,2)) x = IntervalVector(2) x[0] = Interval(1,2) x.set_empty() - x.resize_save_values(3) + x.conservativeResize(3) self.assertTrue(x.size() == 3) self.assertTrue(x.is_empty()) self.assertTrue(x[2] == Interval(-oo,oo)) @@ -115,7 +115,7 @@ def test_intervalvector(self): x = IntervalVector(5) x[0] = Interval(1,2) x[1] = Interval(3,4) - x.resize_save_values(2) + x.conservativeResize(2) self.assertTrue(x.size() == 2) self.assertTrue(x[0] == Interval(1,2)) self.assertTrue(x[1] == Interval(3,4)) From 1bf05991ba274ddf2e5c3fa0c731cb26b7c7488d Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 20/45] Define Eigen's MADD and ASSUME_ALIGNED macros for MSVC arm64 and UBSan --- src/core/matrices/codac2_matrices.h | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/core/matrices/codac2_matrices.h b/src/core/matrices/codac2_matrices.h index a92cd6a1f..779bb6dd8 100644 --- a/src/core/matrices/codac2_matrices.h +++ b/src/core/matrices/codac2_matrices.h @@ -19,6 +19,61 @@ #pragma once +/* Eigen's blocked product kernel accumulates in two different ways. When + * EIGEN_HAS_SINGLE_INSTRUCTION_MADD is defined it writes c = pmadd(a,b,c), + * which is exactly the fused multiply-add the accumulator expects. Otherwise + * it multiplies into a temporary declared with the type of the right-hand + * operand, tmp = pmul(a,tmp), then adds it with padd(c,tmp). That second path + * is a register-allocation workaround for compilers without a fused + * multiply-add, and it silently assumes that the product has the type of the + * right-hand operand. Codac breaks that assumption everywhere it multiplies + * matrices of two different scalar types -- a Matrix by an IntervalMatrix, + * for instance, whose product is an IntervalMatrix. The kernel then either + * fails to compile (no padd overload accepts the two types) or, worse, + * narrows the product down to the type of the right-hand operand. + * + * Every architecture Eigen vectorizes defines the macro in its packet-math + * header, so this path is normally unreachable. MSVC on arm64 is the exception: + * Eigen only enables NEON on __ARM_NEON, which that compiler does not define, + * so the whole library falls back on scalar packets and the arm64 builds fail + * to compile those mixed-type products. Defining the macro here selects the + * pmadd path unconditionally, which is the one all the other targets already + * take, and keeps the same results everywhere. It must be defined before Eigen + * is included, hence its position at the very top of this file. + */ +#ifndef EIGEN_HAS_SINGLE_INSTRUCTION_MADD +#define EIGEN_HAS_SINGLE_INSTRUCTION_MADD +#endif + +/* Eigen's own EIGEN_ASSUME_ALIGNED(PTR, ALIGN_BYTES), defined in + * Eigen/src/Core/util/Memory.h, hints the compiler that a packet load/store + * is aligned by calling std::assume_aligned<8 * (ALIGN_BYTES)>(PTR) when the + * standard library provides it (as libc++ does). ALIGN_BYTES is always + * called with an already-byte-valued Alignment enumerator (Aligned16 = 16, + * Aligned32 = 32...), so that extra "8 *" claims eight times the alignment + * Eigen itself computed -- e.g. a NEON Packet2d, whose unpacket_traits + * report Aligned16, ends up asserted as 128-byte aligned. libc++'s + * std::assume_aligned() is, among major standard libraries, the one that + * actually verifies such a claim at run time under -fsanitize=alignment + * (GCC/libstdc++'s __builtin_assume_aligned() treats it as a silent + * optimizer hint instead), which is why every heap-allocated Eigen::Matrix + * op trips it under UBSan on macOS/Clang -- e.g. in codac2::gauss_jordan() + * or any matrix product -- while the same code is silent on + * Linux/GCC. The buffers themselves are never actually misaligned for the + * SIMD width Eigen emits; only this hint's arithmetic is wrong. Memory.h + * guards its own definition with #ifndef EIGEN_ASSUME_ALIGNED, so defining + * it here first -- before Eigen is included, like EIGEN_HAS_SINGLE_ + * INSTRUCTION_MADD above -- replaces the miscomputed hint with a no-op + * (Eigen's own fallback for compilers lacking std::assume_aligned/ + * __builtin_assume_aligned) rather than trying to patch the multiplier, + * so this keeps working even if a future Eigen release changes it again. + * It does not change which aligned/unaligned load or store instruction + * Eigen selects for a given expression, only this compiler-hint call. + */ +#ifndef EIGEN_ASSUME_ALIGNED +#define EIGEN_ASSUME_ALIGNED(PTR, ALIGN_BYTES) +#endif + #include #include "codac2_Interval.h" #include "codac2_Interval_operations.h" From ccd1173f5d9e9fb345bb2d0ca7e28f5a339c55c6 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 21/45] Generalize IsIntervalDomain to any type flagged is_interval_based --- src/core/matrices/codac2_matrices.h | 2 +- .../Matrix_addons/codac2_Matrix_addons_IntervalMatrixBase.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/matrices/codac2_matrices.h b/src/core/matrices/codac2_matrices.h index 779bb6dd8..4714ee234 100644 --- a/src/core/matrices/codac2_matrices.h +++ b/src/core/matrices/codac2_matrices.h @@ -85,7 +85,7 @@ namespace Eigen concept IsVectorOrRow = (C == 1 || R == 1); template - concept IsIntervalDomain = std::is_same_v; + concept IsIntervalDomain = codac2::is_interval_based::value; } #define EIGEN_MATRIXBASE_PLUGIN "codac2_MatrixBase_addons_include.h" diff --git a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_IntervalMatrixBase.h b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_IntervalMatrixBase.h index 64ef607a3..a53447794 100644 --- a/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_IntervalMatrixBase.h +++ b/src/core/matrices/eigen/Matrix_addons/codac2_Matrix_addons_IntervalMatrixBase.h @@ -141,7 +141,7 @@ inline bool operator==(const MatrixBase& x) const inline void set_empty() requires IsIntervalDomain { - this->init(codac2::Interval::empty()); + this->init(Scalar::empty()); } /** @@ -342,4 +342,4 @@ template inline auto bisect_largest(double ratio = 0.49, const std::vector& among_indices = {}) const { return bisect(this->max_diam_index(among_indices), ratio); -} \ No newline at end of file +} From 5a176bd2d7456200842a0e49d9a2efee14fa053f Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 22/45] Use the portable PI constant instead of the non-standard M_PI --- doc/manual/manual/extensions/capd/src.cpp | 4 ++-- examples/00_graphics/graphic_animation.cpp | 2 +- examples/05_capd_solver/main.cpp | 2 +- examples/12_peibos_capd/main.cpp | 2 +- src/core/domains/ellipsoid/codac2_Ellipsoid.cpp | 5 +++-- tests/extensions/capd/codac2_tests_peibos_capd.cpp | 3 ++- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/manual/manual/extensions/capd/src.cpp b/doc/manual/manual/extensions/capd/src.cpp index d1093334b..291b21f68 100644 --- a/doc/manual/manual/extensions/capd/src.cpp +++ b/doc/manual/manual/extensions/capd/src.cpp @@ -78,7 +78,7 @@ int main() // initial set // [codac-capd-5-beg] capd::IVector c(2); - c[0] = -M_PI/2.; + c[0] = -PI/2.; c[1] = 0.; // take some box around c c[0] += capd::Interval(-1,1)*1e-2; @@ -137,7 +137,7 @@ int main() OctaSym id_2d ({1,2}); OctaSym s ({-2,1}); - auto peibos_output_pend = PEIBOS(vectorField_pend, 20., 0.2, psi0_pend, {id_2d,s,s*s,s.invert()}, 0.02, {-M_PI/2.,0.}); + auto peibos_output_pend = PEIBOS(vectorField_pend, 20., 0.2, psi0_pend, {id_2d,s,s*s,s.invert()}, 0.02, {-PI/2.,0.}); auto m_v_par_2d_pend = reach_set(peibos_output_pend); // [peibos-capd-3-end] diff --git a/examples/00_graphics/graphic_animation.cpp b/examples/00_graphics/graphic_animation.cpp index 6f357e68c..309d74a4c 100644 --- a/examples/00_graphics/graphic_animation.cpp +++ b/examples/00_graphics/graphic_animation.cpp @@ -18,7 +18,7 @@ int main() { fig.clear(); fig.draw_point({5.0*cos(theta), 5.0*sin(theta)}, Color::red()); - theta += 2.0 * M_PI / (double) steps; + theta += 2.0 * PI / (double) steps; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } diff --git a/examples/05_capd_solver/main.cpp b/examples/05_capd_solver/main.cpp index 0fac5e591..aeb0a02be 100644 --- a/examples/05_capd_solver/main.cpp +++ b/examples/05_capd_solver/main.cpp @@ -30,7 +30,7 @@ int main() // initial set capd::IVector c(2); - c[0] = -M_PI/2.; + c[0] = -PI/2.; c[1] = 0.; // take some box around c c[0] += capd::interval(-1,1)*1e-2; diff --git a/examples/12_peibos_capd/main.cpp b/examples/12_peibos_capd/main.cpp index f4b3a2b80..d7b44b5f8 100644 --- a/examples/12_peibos_capd/main.cpp +++ b/examples/12_peibos_capd/main.cpp @@ -49,7 +49,7 @@ int main() AnalyticFunction psi0_pend ({X_2d},{0.1*X_2d[0],0.1}); - auto peibos_output_pend = PEIBOS(vectorField_pend, tf_pend, dt_pend, psi0_pend, {id_2d,s,s*s,s.invert()}, 0.02, {-M_PI/2.,0.}, true); + auto peibos_output_pend = PEIBOS(vectorField_pend, tf_pend, dt_pend, psi0_pend, {id_2d,s,s*s,s.invert()}, 0.02, {-PI/2.,0.}, true); Figure2D output_pend ("Pendulum",GraphicOutput::VIBES | GraphicOutput::IPE); output_pend.set_axes(axis(0,{-2,2}),axis(1,{-3,3})); diff --git a/src/core/domains/ellipsoid/codac2_Ellipsoid.cpp b/src/core/domains/ellipsoid/codac2_Ellipsoid.cpp index 2c9dd5e31..b2760d2ba 100644 --- a/src/core/domains/ellipsoid/codac2_Ellipsoid.cpp +++ b/src/core/domains/ellipsoid/codac2_Ellipsoid.cpp @@ -8,6 +8,7 @@ */ #include "codac2_Ellipsoid.h" +#include "codac2_math.h" #include using namespace std; @@ -114,9 +115,9 @@ namespace codac2 { vector vx, vy; double dr = 0.2; - for(double t = -M_PI ; t < M_PI+dr ; t+=dr) + for(double t = -PI ; t < PI+dr ; t+=dr) { - if(t > M_PI) t = M_PI; + if(t > PI) t = PI; Vector p = x.mu+V*Vector({std::cos(t),std::sin(t)}).cwiseProduct(D_); vx.push_back(p[0]); vy.push_back(p[1]); diff --git a/tests/extensions/capd/codac2_tests_peibos_capd.cpp b/tests/extensions/capd/codac2_tests_peibos_capd.cpp index ffa1eaaa8..fb4a21702 100644 --- a/tests/extensions/capd/codac2_tests_peibos_capd.cpp +++ b/tests/extensions/capd/codac2_tests_peibos_capd.cpp @@ -9,6 +9,7 @@ #include #include +#include #include using namespace std; @@ -20,7 +21,7 @@ TEST_CASE("PEIBOS_CAPD") capd::IMap vectorField_pend("var:t,w;fun:w,-sin(t) - 0.5*w;"); VectorVar X(1); AnalyticFunction psi0_pend ({X},{0.1*X[0],0.1}); - auto peibos_output_pend = PEIBOS(vectorField_pend, 1.0, 0.5, psi0_pend, {OctaSym({1,2})}, 0.2, {-M_PI/2.,0.}); + auto peibos_output_pend = PEIBOS(vectorField_pend, 1.0, 0.5, psi0_pend, {OctaSym({1,2})}, 0.2, {-PI/2.,0.}); auto m_v_par_2d_pend = reach_set(peibos_output_pend); } } \ No newline at end of file From b1ca8a06a6bd757f2ecbe5e875376f0d39b93392 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 23/45] 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 09a1fb615..db2b59939 100644 --- a/scripts/CMakeModules/codac_gaol.cmake +++ b/scripts/CMakeModules/codac_gaol.cmake @@ -287,11 +287,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 7d8b8ca7a..01c94f371 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -151,6 +151,249 @@ if (WITH_CAPD) 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}) @@ -168,9 +411,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 4970bdde498b328f8e009ed34939bd31d11553b5 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 24/45] 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 | 54 ++++++++++--- 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, 356 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 93b8bf070..505aac16c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -297,7 +297,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 @@ -324,7 +337,10 @@ ${CMAKE_CURRENT_SOURCE_DIR}/separators ${CMAKE_CURRENT_SOURCE_DIR}/tools ${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) @@ -335,36 +351,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 01c94f371..3be7e6773 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -151,6 +151,37 @@ if (WITH_CAPD) 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) # ------------------------------------------------------------------ @@ -394,22 +425,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 @@ -417,7 +510,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 e8b551b00d6e8e2c379d6b69c7ec0585abbd4507 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:39 +0200 Subject: [PATCH 25/45] 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 db2b59939..e0afb693d 100644 --- a/scripts/CMakeModules/codac_gaol.cmake +++ b/scripts/CMakeModules/codac_gaol.cmake @@ -287,11 +287,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 10eb1609f666f44a58e41003c63c1f5d5938aaf1 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 26/45] 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 23eb77689404626b7d3b66fd59798d9943533b7e Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 27/45] 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 3be7e6773..791566ae3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -132,6 +132,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 @@ -425,6 +430,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 0402ec4d83080b6c3ca68d9b4017488890f25b00 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 28/45] Fix make check stopping on a non-existent codac target --- CMakeLists.txt | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f9796472..6d6088be0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -658,9 +658,30 @@ endif() if(BUILD_TESTS) include(CTest) + # `check` is `test` plus two things: it prints the output of what failed, + # and it rebuilds the suite before running it. CMake's own `test` target + # does neither -- it runs ctest on whatever happens to be in the build tree. + # + # There is no DEPENDS here on purpose. In add_custom_target(), DEPENDS names + # *files*, not targets, and this target used to carry "DEPENDS + # ${PROJECT_NAME}", which is neither: no file and no target is called + # "codac", the libraries being codac-core, codac-graphics and so on. make + # therefore stopped on "No rule to make target 'codac'" after rebuilding + # everything and never reached ctest, so `make check` could not run the + # suite at all. What must exist before the suite runs is attached with + # add_dependencies() instead: every test executable, by + # tests/CMakeLists.txt; every example, by examples/CMakeLists.txt; and the + # Python extension modules just below, which the Python half of the suite + # imports. add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure $(ARGS) - DEPENDS ${PROJECT_NAME} COMMENT "Running the tests") + COMMENT "Running the tests") + + foreach(_codac_py_module _core _graphics _unsupported) + if(TARGET ${_codac_py_module}) + add_dependencies(check ${_codac_py_module}) + endif() + endforeach() add_subdirectory(tests) endif() From 8f844af38aec428eb2de6944abcd1b39153dc6ab Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 29/45] Run the workflows on every branch and cancel superseded runs --- .github/workflows/dockercentos.yml | 23 +++++++++++++++++++---- .github/workflows/dockermatrix.yml | 26 ++++++++++++++++++++------ .github/workflows/macosmatrix.yml | 23 +++++++++++++++++++---- .github/workflows/tests.yml | 15 +++++++++++++++ .github/workflows/unixmatrix.yml | 18 ++++++++++++++++++ .github/workflows/vcmatrix.yml | 23 +++++++++++++++++++---- 6 files changed, 110 insertions(+), 18 deletions(-) diff --git a/.github/workflows/dockercentos.yml b/.github/workflows/dockercentos.yml index 2494213d5..80eac9bd5 100644 --- a/.github/workflows/dockercentos.yml +++ b/.github/workflows/dockercentos.yml @@ -1,13 +1,28 @@ # This files generates Python wheels for Linux on: push: - branches: - - codac1 - - codac2 - - codac2_codac4matlab + branches: ['**'] tags-ignore: ['**'] # Ignore all tag pushes pull_request: +permissions: + contents: write + +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: dockercentos: runs-on: ${{ matrix.cfg.os }} diff --git a/.github/workflows/dockermatrix.yml b/.github/workflows/dockermatrix.yml index e7cde280d..4e5303519 100644 --- a/.github/workflows/dockermatrix.yml +++ b/.github/workflows/dockermatrix.yml @@ -1,17 +1,31 @@ # This file checks that the lib runs on ARM on: push: - branches: - - codac1 - - codac2 - # The following is implied by above selection... - #branches-ignore: - #- codac2_codac4matlab + branches-ignore: + - codac2_codac4matlab tags-ignore: ['**'] # Ignore all tag pushes pull_request: branches-ignore: - codac2_codac4matlab +permissions: + contents: write + +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: dockermatrix: runs-on: ${{ matrix.cfg.os }} diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 08231a92e..47c511574 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -1,13 +1,28 @@ # This file generates Python wheels for macOS on: push: - branches: - - codac1 - - codac2 - - codac2_codac4matlab + branches: ['**'] tags-ignore: ['**'] # Ignore all tag pushes pull_request: +permissions: + contents: write + +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: macosmatrix: runs-on: ${{ matrix.cfg.os }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5a274af83..07db456ca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,6 +8,21 @@ on: 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: tests: runs-on: ${{ matrix.cfg.os }} diff --git a/.github/workflows/unixmatrix.yml b/.github/workflows/unixmatrix.yml index b94dff014..ab7ea3e17 100644 --- a/.github/workflows/unixmatrix.yml +++ b/.github/workflows/unixmatrix.yml @@ -5,6 +5,24 @@ on: tags-ignore: ['**'] # Ignore all tag pushes pull_request: +permissions: + contents: write + +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: deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/vcmatrix.yml b/.github/workflows/vcmatrix.yml index e00aefb57..1abf0ab5d 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -1,13 +1,28 @@ # This file generates Python wheels for Windows on: push: - branches: - - codac1 - - codac2 - - codac2_codac4matlab + branches: ['**'] tags-ignore: ['**'] # Ignore all tag pushes pull_request: +permissions: + contents: write + +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: vcmatrix: runs-on: ${{ matrix.cfg.os }} From a87804b7483c9546c58dded7b5ff79ca2c386e61 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 30/45] 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 47c511574..b4cc7b793 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -69,7 +69,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 1abf0ab5d..5026754e3 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -75,13 +75,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 @@ -91,13 +123,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 743e7bb8714923bc08842470eec60388c431dd0e Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 31/45] 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 5026754e3..3f2d1bd00 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -100,7 +100,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 845097694..e57b28372 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -81,8 +81,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 7b6a34572..e7e98e4df 100755 --- a/scripts/docker/build_pybinding.sh +++ b/scripts/docker/build_pybinding.sh @@ -78,7 +78,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 4c9177fb0..df69d308d 100644 --- a/scripts/docker/build_pybinding_codac4matlab.sh +++ b/scripts/docker/build_pybinding_codac4matlab.sh @@ -78,7 +78,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 c7ad16e90920400a4a43fb5f69688a12b5bbc476 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 32/45] 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 b4cc7b793..3721b1bda 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -127,7 +127,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 3f2d1bd00..3b781febc 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -132,8 +132,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 1c09359403da3c34ca5e344f6adfe8bd0afe2491 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 33/45] 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 6d6088be0..184d9b314 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 f9869c785076cd8a575229fae8d8c2883fa885cf Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 34/45] Run the examples in every workflow job that runs the tests --- .github/workflows/dockermatrix.yml | 2 +- .github/workflows/macosmatrix.yml | 7 ++++++- .github/workflows/unixdebug.yml | 6 +++--- .github/workflows/unixmatrix.yml | 2 +- .github/workflows/vcmatrix.yml | 7 ++++++- .github/workflows/windebugmatrix.yml | 14 +++++++++----- scripts/docker/build_pybinding.sh | 2 +- 7 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.github/workflows/dockermatrix.yml b/.github/workflows/dockermatrix.yml index 4e5303519..1ab715bae 100644 --- a/.github/workflows/dockermatrix.yml +++ b/.github/workflows/dockermatrix.yml @@ -89,7 +89,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 3721b1bda..f40b5caa9 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -109,7 +109,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 .. @@ -135,6 +135,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/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/unixmatrix.yml b/.github/workflows/unixmatrix.yml index ab7ea3e17..f56d62a0f 100644 --- a/.github/workflows/unixmatrix.yml +++ b/.github/workflows/unixmatrix.yml @@ -169,7 +169,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 3b781febc..2a146e13e 100644 --- a/.github/workflows/vcmatrix.yml +++ b/.github/workflows/vcmatrix.yml @@ -107,7 +107,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 }}/"` @@ -140,6 +140,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/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 diff --git a/scripts/docker/build_pybinding.sh b/scripts/docker/build_pybinding.sh index e7e98e4df..8afdd9303 100755 --- a/scripts/docker/build_pybinding.sh +++ b/scripts/docker/build_pybinding.sh @@ -80,7 +80,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 213ad52521cef707cb0ec83a94612166528c723c Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 35/45] 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 f40b5caa9..98bd30181 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -67,8 +67,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 b09da883aabf2b11979dc71cd868ffe1fe587a9e Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 36/45] Request the arm64 Python interpreter on Apple Silicon runners --- .github/workflows/macosmatrix.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 98bd30181..058b1e69a 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -57,7 +57,10 @@ jobs: - uses: actions/setup-python@v7 with: python-version: ${{ matrix.cfg.py_v_maj }}.${{ matrix.cfg.py_v_min }} - architecture: x64 + # arm64 on the Apple Silicon entries: the interpreter has to run + # natively to load the arm64-only extension, which "x64" only achieved + # as long as the build it installed happened to be universal2. + architecture: ${{ matrix.cfg.arch == 'arm64' && 'arm64' || 'x64' }} if: matrix.cfg.py_v_maj!='' - run: | python -c "import sys; print(sys.version)" From bf80e7d24e12dbf61e39f7e061f5144fb97ebc34 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 37/45] Document the tests, sanitized Debug builds, examples and coverage --- doc/manual/development/info_dev.rst | 299 ++++++++++++++++++++++++++-- 1 file changed, 284 insertions(+), 15 deletions(-) diff --git a/doc/manual/development/info_dev.rst b/doc/manual/development/info_dev.rst index 4f18a4548..ad0ab52ca 100644 --- a/doc/manual/development/info_dev.rst +++ b/doc/manual/development/info_dev.rst @@ -12,8 +12,28 @@ To build this manual using Sphinx, follow these steps: .. code-block:: bash - sudo apt-get install sphinx-common graphviz - pip install sphinx sphinx_rtd_theme furo sphinx-math-dollar sphinx_tabs breathe sphinx_togglebutton + sudo apt-get install graphviz + pip install sphinx breathe furo sphinx-rtd-theme sphinx-math-dollar sphinx-tabs sphinx-togglebutton + + That list is where the ``extensions`` declared in ``doc/manual/conf.py.in`` + come from -- ``breathe``, ``sphinx_math_dollar``, ``sphinx_tabs``, + ``sphinx_togglebutton`` and ``sphinx_rtd_theme``, the sixth one, + ``sphinx.ext.mathjax``, shipping with Sphinx itself -- plus ``furo``, which is + the theme the manual is actually rendered with (``html_theme``). Adding an + extension to ``conf.py.in`` means adding its package here as well. Sphinx + itself comes from ``pip`` rather than from the distribution's + ``sphinx-common`` package, so that Sphinx and its extensions are installed by + the same tool and cannot end up in two incompatible versions. + + Building the manual asks for a more recent Python than building the binding + does. The current Sphinx requires **Python 3.12 or newer**, and the current + ``sphinx-math-dollar``, ``sphinx-tabs`` and ``breathe`` require 3.11, 3.10 and + 3.9 respectively. Nothing fails on an older interpreter, and that is precisely + what makes it worth knowing: ``pip`` quietly falls back to whatever release + still supports it, so on Python 3.8 the command line above installs Sphinx + 7.1.2 and ``furo`` 2024.8.6 rather than the current ones. When the manual + renders differently on two machines, comparing ``pip list`` is the first thing + to do. 2. **Building the manual**: After configuring the CMake project, the manual can be build with: @@ -43,14 +63,14 @@ If you simply want to use the latest Codac release in Python, you can download t 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: + - `Doxygen `_. **1.17.0 is the recommended version, and 1.16.1 the minimum.** Avoid 1.18.0: it segfaults while parsing Codac's headers on macOS with an Intel processor, which is why ``.github/workflows/macosmatrix.yml`` pins 1.17.0 on those runners. On Linux systems, recent releases are not available as Debian packages, so we advise to install Doxygen from the sources: .. code-block:: bash cd $HOME git clone https://github.com/doxygen/doxygen cd doxygen - git checkout Release_1_16_1 # 1.16.1, or any newer release + git checkout Release_1_17_0 # 1.16.1 is the oldest that works; 1.18.0 is known to break on MacOS intel mkdir build ; cd build cmake -DCMAKE_INSTALL_PREFIX=$HOME/doxygen/build_install .. make ; make install @@ -59,6 +79,8 @@ 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. + This is also why the Doxygen version is worth pinning rather than taking whatever is at hand. The names of the generated docstring macros are derived from the text Doxygen produces, so a wording change between two releases renames them, and the binding sources that spell those names out then stop compiling. ``scripts/doxygen/doxygen2docstring.py`` normalizes that text rather than trusting it, which absorbs small differences; staying within the recommended range is what covers the rest. + 2. **Compile Codac with Python binding**: @@ -69,15 +91,6 @@ If you simply want to use the latest Codac release in Python, you can download t git clone https://github.com/codac-team/codac $HOME/codac cd $HOME/codac - .. admonition:: Using Codac v2 simultaneously with Codac v1 - - In case you want to use the two versions of Codac in the same Python script, you will have to compile the binaries of Codac v2 under a different name in order to avoid ``import`` conflicts. Things are already prepared in the branch ``codac2_renamed``, you can therefore: - - .. code-block:: bash - - git checkout codac2_renamed - - 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. @@ -94,9 +107,13 @@ If you simply want to use the latest Codac release in Python, you can download t .. code-block:: bash cd $HOME/codac/build/python/python_package - python setup.py develop --user + pip install --user -e . + + (Earlier versions of this page used ``python setup.py develop --user``. That + command has been removed from setuptools; ``pip install -e`` is its + replacement and does the same thing.) - And update your configuration file (for instance, ``$HOME/.bashrc`` on Linux systems) with: + Alternatively, or in addition, update your configuration file (for instance, ``$HOME/.bashrc`` on Linux systems) with: .. code-block:: bash @@ -123,6 +140,258 @@ If you simply want to use the latest Codac release in Python, you can download t You should obtain a graphical output corresponding to a set inversion. +.. _sec-dev-info-tests: + +Running the tests +----------------- + +The test suite is built when the project is configured with ``-D BUILD_TESTS=ON`` +and run through `CTest `_ +from the build directory: + +.. code-block:: bash + + cd $HOME/codac/build + ctest --output-on-failure + +Two ``make`` targets wrap it, and they are not interchangeable: + +.. code-block:: bash + + make test # what CMake provides: runs ctest on the build tree as it stands + make check # rebuilds the suite first, and prints the output of what failed + +``make check`` is the one to use after editing a source file. ``make test`` runs +whatever executables are already there, so it happily reports a pass on the +previous build of a test you have just changed. Both accept ``ARGS`` to forward +options to ctest, with the Makefile generator: + +.. code-block:: bash + + make check ARGS="-R IntervalVector" + +Every entry of the suite exists in up to two flavours, one per language: a C++ +test, and -- when the build has ``WITH_PYTHON=ON`` and a Python file of the same +name sits next to the C++ one -- its Python counterpart. The CTest name of each +is built from the path of the source under ``tests/``, with the slashes turned +into underscores and ``_cpp`` or ``_py`` appended, so +``tests/core/domains/interval/codac2_tests_IntervalVector.cpp`` becomes +``codac2_tests_core_domains_interval_IntervalVector_cpp``. ``-R`` takes a +regular expression matched anywhere in that name, which is what makes the long +names painless: + +.. code-block:: bash + + ctest -N # list the tests without running them + ctest -R IntervalVector # both flavours of one test + ctest -R "_py" # the whole Python half of the suite + ctest -R "_cpp" -j 8 # the C++ half, eight at a time + ctest -V -R IntervalVector_cpp # verbose: everything the test prints + +``ctest -V`` matters more than it looks, for the reason given in the next +section: a sanitizer can report a problem without failing the test, and without +``-V`` CTest discards the output of every test that passes. + +.. _sec-dev-info-adding-a-test: + +Adding a test +------------- + +1. Write the test as ``tests//codac2_tests_.cpp``, using + `Catch2 `_ (version 3), next to the + existing tests of the same part of the library. + +2. **Register it**, by adding its path -- relative to ``tests/``, without the + ``.cpp`` extension -- to the ``SRC_TESTS`` list of ``tests/CMakeLists.txt``: + + .. code-block:: cmake + + list(APPEND SRC_TESTS + + # ---------------------------------------------------------------- + # Domains + # ---------------------------------------------------------------- + + core/domains/interval/codac2_tests_IntervalVector + core/domains/interval/codac2_tests_MyNewTest # <-- the new one + ... + + This step is not optional, and nothing will remind you of it: a test file that + is not listed there is never compiled and never run. The suite stays green + while the new test does nothing at all, which is the one failure mode worth + guarding against by hand. + +3. Optionally, write the Python counterpart as + ``tests//codac2_tests_.py`` -- same name, same directory. It needs + no registration: it is picked up automatically and added as + ``codac2_tests__py`` whenever the build has ``WITH_PYTHON=ON``. + +4. Re-run CMake, and check that what you expect is actually there: + + .. code-block:: bash + + cmake .. && ctest -N -R MyNewTest + +.. _sec-dev-info-debug: + +Debug builds: AddressSanitizer and UndefinedBehaviorSanitizer +------------------------------------------------------------- + +Configuring with ``-D CMAKE_BUILD_TYPE=Debug`` does more than turn the +optimizations off: the top-level ``CMakeLists.txt`` also compiles and links +everything with ``-fsanitize=address,undefined``. A Debug build therefore reports +out-of-bounds accesses, use-after-free, memory leaks and undefined behaviour as it +runs, and running the test suite in that configuration is the cheapest way to find +them. + +.. code-block:: bash + + mkdir build_debug ; cd build_debug + cmake -D CMAKE_BUILD_TYPE=Debug -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON .. + make + ctest -V --output-on-failure + +Two things are worth knowing about it. + +- The sanitizers are enabled only if the compiler in use actually ships their + runtime. CMake probes for it and says which way it went, at configure time: + either ``ASan/UBSan will be enabled for Debug-configuration builds.`` or ``No + ASan/UBSan runtime found ...: Debug-configuration builds will not be + sanitized.`` Falling back to an unsanitized Debug build is deliberate -- it + beats a configuration that fails to link on every target -- but it does mean + that line is worth reading before concluding that a build found nothing. + +- ASan and UBSan do not agree on what a diagnostic costs. A buffer overflow or a + leak aborts the process and fails the test; a UBSan ``runtime error:`` only + prints and lets the run continue. A test can therefore pass having reported + dozens of undefined behaviours, and ``ctest -V`` is what makes them visible. + +This is the configuration ``.github/workflows/unixdebug.yml`` and +``.github/workflows/macdebug.yml`` run on every push, across four Linux and four +macOS jobs (GCC and Clang, x86_64 and arm64). + +.. _sec-dev-info-examples: + +Building and running the examples +--------------------------------- + +``-D TEST_EXAMPLES=ON``, together with ``BUILD_TESTS=ON``, compiles every example +of ``examples/`` against the library being built and registers each one as a CTest +integration test: + +.. code-block:: bash + + cmake -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D WITH_PYTHON=ON .. + make + ctest -R codac2_examples + +The names follow the same rule as the tests, from the path under ``examples/``: +``examples/03_sivia/main.cpp`` becomes ``codac2_examples_03_sivia_main_cpp``, and +its Python counterpart ``..._py``. Examples that need an optional dependency -- +CAPD, or SymPy -- are registered only when it is available. + +They are worth running for more than the fact that they still produce a result. +The examples are what a user compiles against the *installed* headers, and only +the public headers are made visible to them here, so an example that stops +compiling in this build is an example a user could no longer build either. + +.. _sec-dev-info-coverage: + +Measuring the code coverage +--------------------------- + +Locally: ``make coverage`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Configuring with ``-D WITH_COVERAGE=ON`` instruments the build and adds two +targets. It needs `gcovr `_ (``pip install gcovr``) and a +GCC or Clang toolchain -- MSVC provides no ``gcov`` and the option refuses to +configure there. + +.. code-block:: bash + + mkdir build_coverage ; cd build_coverage + cmake -D CMAKE_BUILD_TYPE=Release \ + -D WITH_COVERAGE=ON \ + -D BUILD_TESTS=ON -D TEST_EXAMPLES=ON -D WITH_PYTHON=ON .. + make + make coverage + +``make coverage`` runs the whole CTest suite and turns the counters ``gcov`` +leaves behind into a report: a summary printed on the console, ``coverage.html`` +to open in a browser (with one page per source file next to it), and +``coverage.xml`` in the Cobertura format that most editors and code-review tools +read. + +``make coverage-report`` is the second half on its own. It re-reads whatever the +last ``ctest`` invocation happened to touch, without running anything, which is +what to use after running part of the suite by hand: + +.. code-block:: bash + + ctest -R codac2_tests_core_domains_interval + make coverage-report + +Turning ``BUILD_TESTS`` and ``TEST_EXAMPLES`` on is what makes the figure mean +anything -- coverage answers "what do the tests reach", and the examples are +integration tests that reach code the unit tests do not. + +Everything else the measurement needs is decided by the top-level +``CMakeLists.txt`` rather than left to the caller: the instrumentation flags, the +``gcov`` matching the compiler (Clang's counters 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 exclusion of the dependencies CMake fetches into +the build tree, and ``CMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF`` -- which is not +optional, pybind11 adding ``-flto`` to the modules it builds and GCC being unable +to combine that with ``gcov`` instrumentation, the link then failing on an +undefined vtable. + +From GitHub Actions +^^^^^^^^^^^^^^^^^^^ + +Every push runs the same measurement in CI, in the job named **Ubuntu 26.04 GCC +x86_64 Release coverage** of ``.github/workflows/unixdebug.yml``. It configures +with ``-D WITH_COVERAGE=ON``, runs the whole suite -- the C++ and Python unit +tests, plus every example -- and calls the very same ``coverage-report`` target, +so what CI measures and what ``make coverage`` measures cannot drift apart. The +summary appears in the job log, and the report is uploaded as an artifact named +``coverage-report``, kept for 14 days. + +1. **Download the report**: with the `GitHub CLI `_, + from a clone of the repository: + + .. code-block:: bash + + # the latest run of the workflow on the current branch + gh run list --workflow unixdebug.yml --limit 1 + gh run download -n coverage-report -D coverage + + The directory then holds ``coverage.xml`` and the ``coverage.*.html`` files, + ``coverage.html`` being the index to open in a browser. + +2. **Read the summary without downloading anything**: the three totals are printed + at the end of the *Coverage report* step of the job, and can be pulled from the + log directly: + + .. code-block:: bash + + gh run view --log | grep -E "^(lines|functions|branches):" + +.. admonition:: Reading the figures + + Two things make the raw percentages narrower than they look. + + A header compiled into several translation units appears once per include + path in gcovr's output -- typically once under ``src/`` and once under + ``build/include/`` -- so summing those entries counts each of its lines twice. + A line reached through either path is covered, and the two entries have to be + merged line by line to get a per-file figure. + + And on heavily templated headers, gcov counts every instantiated line: the + denominators are inflated, and the percentages are best read as orders of + magnitude rather than to the decimal. + + .. admonition:: For admins Get all the *wheels* generated by GitHub Actions (the generated release needs to be explicitely set as latest): From 17f21c0094d0a6abdb6b80c346cff1eb496868f1 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 38/45] Set C++20 without compiler extensions on every target --- CMakeLists.txt | 1 + src/core/CMakeLists.txt | 11 ++++++----- tests/CMakeLists.txt | 11 +++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 184d9b314..4d4dd3d13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,6 +357,7 @@ #if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) #endif() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 505aac16c..977aeb023 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -291,13 +291,14 @@ # Create the target for libcodac-core ################################################################################ - #if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - #endif() - add_library(${PROJECT_NAME}-core ${CODAC_CORE_SRC}) + set_target_properties(${PROJECT_NAME}-core PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + ) + # 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 791566ae3..58e4187d3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -503,6 +503,17 @@ foreach(SRC_TEST ${SRC_TESTS}) endif() + # C++20 + set_target_properties( + ${TEST_NAME} + PROPERTIES + + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + ) + + add_dependencies( check ${TEST_NAME} From 30b629d310559f00901ddab6b93d4a5d379abf72 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 39/45] Treat the Eigen and Catch2 headers from FetchContent as system headers --- CMakeLists.txt | 37 +++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 6 ++++++ 2 files changed, 43 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d4dd3d13..2cafcef45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -405,6 +405,40 @@ string(APPEND CMAKE_CXX_FLAGS " ${_codac_portability_cxx_flags}") +################################################################################ +# Treating dependencies' headers as system headers +################################################################################ + + # This project compiles with /W4 or -Wall -Wextra -Wpedantic, and a dependency + # pulled in with FetchContent inherits that level: its headers are ordinary + # include directories, not system ones. Eigen and Catch2 alone accounted for + # about 8300 MSVC warnings per job (C4459, C5054, C4308, C4324) and the 557 + # -Wc2y-extensions Clang reports for Catch2's __COUNTER__, drowning the + # handful of warnings that actually belong to Codac. + # + # FetchContent_Declare(... SYSTEM) does exactly this, but only since CMake + # 3.25, while this project accepts 3.14. Setting the property by hand is + # equivalent and has worked for far longer: the compiler then receives + # -isystem (or /external:I, which CMake already emits for the Visual Studio + # generator alongside /external:W0). + function(codac_mark_target_system tgt) + if(NOT TARGET ${tgt}) + return() + endif() + # A property cannot be set on an ALIAS, so resolve it to the target it names + # (Eigen exports Eigen3::Eigen as an alias of eigen). + get_target_property(_aliased ${tgt} ALIASED_TARGET) + if(_aliased) + set(tgt ${_aliased}) + endif() + get_target_property(_inc ${tgt} INTERFACE_INCLUDE_DIRECTORIES) + if(_inc) + set_target_properties(${tgt} PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_inc}") + endif() + endfunction() + + # 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, @@ -456,6 +490,9 @@ FetchContent_Declare(Eigen3 URL http://github.com/codac-team/eigen/archive/refs/heads/eigen-5.0.zip) #FetchContent_Declare(Eigen3 URL ${CMAKE_CURRENT_SOURCE_DIR}/3rd/eigen-a0dc3994bd5d4e804dab58decb78c56a5af516ba.zip) # If needed to be self-contained... FetchContent_MakeAvailable(Eigen3) + # Only needed for the FetchContent path: a target coming from + # find_package() is IMPORTED, and CMake already treats those as system. + codac_mark_target_system(Eigen3::Eigen) endif() # Adds Eigen3::Eigen diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 58e4187d3..c08e3a844 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,6 +15,12 @@ else() FetchContent_Declare(Catch2 URL https://github.com/catchorg/Catch2/archive/refs/tags/v3.6.0.zip) #FetchContent_Declare(Catch2 URL ${CMAKE_CURRENT_SOURCE_DIR}/../3rd/Catch2-3.6.0.zip) # If needed to be self-contained... FetchContent_MakeAvailable(Catch2) + # See codac_mark_target_system() in the top-level CMakeLists.txt: without + # this, Catch2's headers inherit this project's warning level. Only the + # FetchContent path needs it -- a find_package() target is IMPORTED, and + # CMake already treats those as system. + codac_mark_target_system(Catch2) + codac_mark_target_system(Catch2::Catch2WithMain) endif() # Adds Catch2::Catch2WithMain From ffc97868160735eff578b39380a4fd57b893b17a Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 40/45] Set CMP0156 so that each static library is linked once --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cafcef45..6c927273e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,15 @@ cmake_policy(SET CMP0091 NEW) endif() + # CMP0156 (CMake >=3.29) lets CMake name a static library once on a link + # line for the linkers that do not need it repeated -- Apple's, MSVC's, LLD. + # With the OLD behavior, every test and example linking codac-core and the + # modules depending on it named libcodac-core.a twice, and the Apple linker + # warned "ignoring duplicate libraries" for each of them. + if(POLICY CMP0156) + cmake_policy(SET CMP0156 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 From a38d612328f9390d929d0352ccd8af0530db4cfa Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 41/45] Add the DEB CPack generator only when dpkg-deb is available --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c927273e..c3dcac56a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -742,7 +742,15 @@ # Archives and packages ################################################################################ - set(CPACK_GENERATOR "TGZ" "ZIP" "DEB") + set(CPACK_GENERATOR "TGZ" "ZIP") + # The DEB generator shells out to dpkg-deb, which only exists on + # Debian-based systems -- adding it unconditionally would make `cpack`/ + # `make package` fail on Windows, macOS, and non-Debian Linux, even + # though nothing else in the build depends on it. + find_program(DPKG_DEB_EXECUTABLE dpkg-deb) + if(DPKG_DEB_EXECUTABLE) + list(APPEND CPACK_GENERATOR "DEB") + endif() string(TOLOWER "${CMAKE_PROJECT_NAME}" CPACK_PACKAGE_NAME) set(CPACK_PACKAGE_VENDOR "Codac Team") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${CODAC_DESCRIPTION}) From b6bd2ea9b96ec554db2b26511eb6e9deee1ac75b Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 42/45] Set CMAKE_POSITION_INDEPENDENT_CODE before src/, drop the manual -fPIC --- .github/workflows/macosmatrix.yml | 22 +++++++++---------- .github/workflows/tests.yml | 2 +- .github/workflows/unixdebug.yml | 2 -- CMakeLists.txt | 16 +++++++++++--- 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 +- 8 files changed, 30 insertions(+), 22 deletions(-) diff --git a/.github/workflows/macosmatrix.yml b/.github/workflows/macosmatrix.yml index 058b1e69a..d607f26d6 100644 --- a/.github/workflows/macosmatrix.yml +++ b/.github/workflows/macosmatrix.yml @@ -36,17 +36,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 07db456ca..b79fc5abb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -105,7 +105,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/CMakeLists.txt b/CMakeLists.txt index c3dcac56a..28f371623 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -682,13 +682,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() diff --git a/doc/manual/development/info_dev.rst b/doc/manual/development/info_dev.rst index ad0ab52ca..18bccd9c9 100644 --- a/doc/manual/development/info_dev.rst +++ b/doc/manual/development/info_dev.rst @@ -92,12 +92,12 @@ If you simply want to use the latest Codac release in Python, you can download t cd $HOME/codac - 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 8afdd9303..e4fae0f05 100755 --- a/scripts/docker/build_pybinding.sh +++ b/scripts/docker/build_pybinding.sh @@ -80,7 +80,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 df69d308d..fd73bdf66 100644 --- a/scripts/docker/build_pybinding_codac4matlab.sh +++ b/scripts/docker/build_pybinding_codac4matlab.sh @@ -80,7 +80,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 From 5d60d9135f161c211f69f343276d87e83b122328 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 43/45] Initialize PYTHON_EXECUTABLE from the interpreter pybind11 found --- python/CMakeLists.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index e57b28372..6e0a87322 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -16,6 +16,31 @@ endif() # Adds pybind11::headers, pybind11::module, pybind11::embed + # The Python interpreter, which this directory, tests/ and examples/ run + # through PYTHON_EXECUTABLE: to generate the docstrings below, to build the + # wheel, and to run the Python tests and examples. pybind11 sets it where this + # directory sees it only with PYBIND11_FINDPYTHON=OFF, through + # FindPythonLibsNew.cmake. By default (COMPAT, or NEW) it uses FindPython, + # which sets Python_EXECUTABLE (or Python3_EXECUTABLE), and sets + # PYTHON_EXECUTABLE, in COMPAT, only within its own directory: here it stayed + # empty, execute_process() ran doxygen2docstring.py as a program, and the + # configuration stopped on "permission denied". It is initialized here with + # the interpreter pybind11 builds the modules for, unless it was given, and + # cached, so that tests/ and examples/, configured after this directory, get + # it too. + if(NOT PYTHON_EXECUTABLE) + if(Python_EXECUTABLE) + set(_codac_python_executable "${Python_EXECUTABLE}") + elseif(Python3_EXECUTABLE) + set(_codac_python_executable "${Python3_EXECUTABLE}") + else() + find_package(Python COMPONENTS Interpreter REQUIRED) + set(_codac_python_executable "${Python_EXECUTABLE}") + endif() + set(PYTHON_EXECUTABLE "${_codac_python_executable}" CACHE FILEPATH "Python interpreter" FORCE) + endif() + message(STATUS "Python interpreter: ${PYTHON_EXECUTABLE}") + set(PYTHON_PACKAGE_NAME ${PROJECT_NAME}) set(PYTHON_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/python_package" CACHE INTERNAL "Codac Python package build directory" From e4bd43640b73e2cc2816a8a459387757b4e5c906 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 44/45] Require CMake 3.10 in the standalone example and test projects --- doc/manual/tuto/cp_robotics/src/CMakeLists.txt | 2 +- examples/00_graphics/CMakeLists.txt | 2 +- examples/01_batman/CMakeLists.txt | 2 +- examples/02_centered_form/CMakeLists.txt | 2 +- examples/03_sivia/CMakeLists.txt | 2 +- examples/04_explored_area/CMakeLists.txt | 2 +- examples/05_capd_solver/CMakeLists.txt | 2 +- examples/06_graphics_3D/CMakeLists.txt | 2 +- examples/07_centered_2D/CMakeLists.txt | 2 +- examples/08_centered_3D/CMakeLists.txt | 2 +- examples/09_robot_simu/CMakeLists.txt | 2 +- examples/10_lie_groups/CMakeLists.txt | 2 +- examples/11_peibos/CMakeLists.txt | 2 +- examples/12_peibos_capd/CMakeLists.txt | 2 +- examples/13_qinter/CMakeLists.txt | 2 +- examples/14_lohner/CMakeLists.txt | 2 +- examples/15_sympy/CMakeLists.txt | 2 +- examples/16_visibility/CMakeLists.txt | 2 +- examples/ellipsoid_example/CMakeLists.txt | 2 +- tests/test_codac/CMakeLists.txt | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/manual/tuto/cp_robotics/src/CMakeLists.txt b/doc/manual/tuto/cp_robotics/src/CMakeLists.txt index ad8a88fc7..d749449f4 100644 --- a/doc/manual/tuto/cp_robotics/src/CMakeLists.txt +++ b/doc/manual/tuto/cp_robotics/src/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/00_graphics/CMakeLists.txt b/examples/00_graphics/CMakeLists.txt index 95b125893..f67374ceb 100644 --- a/examples/00_graphics/CMakeLists.txt +++ b/examples/00_graphics/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/01_batman/CMakeLists.txt b/examples/01_batman/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/01_batman/CMakeLists.txt +++ b/examples/01_batman/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/02_centered_form/CMakeLists.txt b/examples/02_centered_form/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/02_centered_form/CMakeLists.txt +++ b/examples/02_centered_form/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/03_sivia/CMakeLists.txt b/examples/03_sivia/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/03_sivia/CMakeLists.txt +++ b/examples/03_sivia/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/04_explored_area/CMakeLists.txt b/examples/04_explored_area/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/04_explored_area/CMakeLists.txt +++ b/examples/04_explored_area/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/05_capd_solver/CMakeLists.txt b/examples/05_capd_solver/CMakeLists.txt index 2d3daae24..a96e2d9ff 100644 --- a/examples/05_capd_solver/CMakeLists.txt +++ b/examples/05_capd_solver/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/06_graphics_3D/CMakeLists.txt b/examples/06_graphics_3D/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/06_graphics_3D/CMakeLists.txt +++ b/examples/06_graphics_3D/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/07_centered_2D/CMakeLists.txt b/examples/07_centered_2D/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/07_centered_2D/CMakeLists.txt +++ b/examples/07_centered_2D/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/08_centered_3D/CMakeLists.txt b/examples/08_centered_3D/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/08_centered_3D/CMakeLists.txt +++ b/examples/08_centered_3D/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/09_robot_simu/CMakeLists.txt b/examples/09_robot_simu/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/09_robot_simu/CMakeLists.txt +++ b/examples/09_robot_simu/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/10_lie_groups/CMakeLists.txt b/examples/10_lie_groups/CMakeLists.txt index 87b2ee865..027a69fb1 100644 --- a/examples/10_lie_groups/CMakeLists.txt +++ b/examples/10_lie_groups/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/11_peibos/CMakeLists.txt b/examples/11_peibos/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/11_peibos/CMakeLists.txt +++ b/examples/11_peibos/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/12_peibos_capd/CMakeLists.txt b/examples/12_peibos_capd/CMakeLists.txt index efb53fdfa..b6a33d97d 100644 --- a/examples/12_peibos_capd/CMakeLists.txt +++ b/examples/12_peibos_capd/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/13_qinter/CMakeLists.txt b/examples/13_qinter/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/13_qinter/CMakeLists.txt +++ b/examples/13_qinter/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/14_lohner/CMakeLists.txt b/examples/14_lohner/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/14_lohner/CMakeLists.txt +++ b/examples/14_lohner/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/15_sympy/CMakeLists.txt b/examples/15_sympy/CMakeLists.txt index 336db6b62..956d0857e 100644 --- a/examples/15_sympy/CMakeLists.txt +++ b/examples/15_sympy/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/16_visibility/CMakeLists.txt b/examples/16_visibility/CMakeLists.txt index 23d13fc6e..cc24aecb4 100644 --- a/examples/16_visibility/CMakeLists.txt +++ b/examples/16_visibility/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/examples/ellipsoid_example/CMakeLists.txt b/examples/ellipsoid_example/CMakeLists.txt index 0947d9686..0364999c8 100644 --- a/examples/ellipsoid_example/CMakeLists.txt +++ b/examples/ellipsoid_example/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / ellipsoid example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(codac_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/tests/test_codac/CMakeLists.txt b/tests/test_codac/CMakeLists.txt index c94d7dfb7..fd82398c7 100644 --- a/tests/test_codac/CMakeLists.txt +++ b/tests/test_codac/CMakeLists.txt @@ -2,7 +2,7 @@ # codac / basics example - cmake configuration file # ================================================================== - cmake_minimum_required(VERSION 3.5) + cmake_minimum_required(VERSION 3.10) project(my_project LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) From df18ef961e90a9d79782918dae9e0b1f65291032 Mon Sep 17 00:00:00 2001 From: Jordan08 Date: Sat, 19 Sep 2026 05:30:40 +0200 Subject: [PATCH 45/45] Export the Codac targets and fix codac.pc for installed consumers --- CMakeLists.txt | 3 +- scripts/CMakeModules/codac_gaol.cmake | 9 +- src/CMakeLists.txt | 234 +++++++++++++++++++++----- src/core/CMakeLists.txt | 28 ++- src/extensions/capd/CMakeLists.txt | 25 ++- src/extensions/sympy/CMakeLists.txt | 24 ++- src/graphics/CMakeLists.txt | 17 +- src/unsupported/CMakeLists.txt | 17 +- 8 files changed, 289 insertions(+), 68 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28f371623..ab0a2e351 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -512,7 +512,8 @@ # 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. + # -pthread, and codac.pc -- written from what is found here -- would then + # describe something other than what find_package(CODAC) hands out. set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) diff --git a/scripts/CMakeModules/codac_gaol.cmake b/scripts/CMakeModules/codac_gaol.cmake index e0afb693d..9f5851460 100644 --- a/scripts/CMakeModules/codac_gaol.cmake +++ b/scripts/CMakeModules/codac_gaol.cmake @@ -466,7 +466,7 @@ endfunction() # 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 +# the exported Codac targets name 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) @@ -700,7 +700,7 @@ endfunction() ################################################################################ # # Returns the lines of codac-config.cmake that define Codac::gaol for a consumer -# of the installed Codac, which CODAC_LIBRARIES names -- the +# of the installed Codac, which the exported Codac targets name -- the # counterpart of the ibex-config-gaol.cmake and ibex-config-ultim.cmake files # that create_target_import_and_export() writes for IBEX. # @@ -732,8 +732,9 @@ function(codac_gaol_config_snippet outvar) 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. + # (libultim) and the flags of interval arithmetic, as Codac::gaol. The + # exported Codac targets link it, so it has to be defined before they are + # included. ${_find} if(NOT TARGET Codac::gaol) add_library(Codac::gaol INTERFACE IMPORTED) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3f7123efb..38b77d347 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,36 @@ set(CODAC_GENERATED_INCLUDE_DIRS "" CACHE INTERNAL "Directories holding the umbrella headers generated for the codac modules") + # Seeded before the modules are added, because each of them appends its own + # flags to these through PARENT_SCOPE (see the pkg-config section below). + # They used to be set after the add_subdirectory() calls, which threw every + # module's contribution away: the installed codac.pc then named no Codac + # library and no Codac include directory at all. + # + # The two directories the modules do not add themselves: the include root, + # through which is reached, and include/eigen3, where the Eigen + # headers the public headers include are installed. Without them a + # pkg-config consumer cannot compile a single translation unit -- this is + # the same pair that codac-core carries in its $. + # + # GAOL's directories are not among them: where they are depends on where GAOL + # was found, or on its being built with Codac, and the part on the + # dependencies below adds them as Codac::gaol gives them. The -I\${includedir}/ibex that + # used to sit here, when Codac reached GAOL through IBEX, named a directory + # under *Codac's* prefix, which only ever existed when the two happened to + # share one. + set(CODAC_PKG_CONFIG_CFLAGS "-I\${includedir} -I\${includedir}/eigen3") + set(CODAC_PKG_CONFIG_MODULE_LIBS "") + + # The "Requires:" line: a module that depends on an outside library appends + # its pkg-config name here and lets that library's own .pc answer for its + # flags. GAOL is not named there: 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; its flags and libraries are written below instead, as + # Codac::gaol gives them. The line starts empty; CAPD joins it from + # src/extensions/capd/CMakeLists.txt when that module is built. + set(CODAC_PKG_CONFIG_REQUIRES "") + add_subdirectory(core) add_subdirectory(graphics) add_subdirectory(unsupported) @@ -36,16 +66,34 @@ set(CODAC_PKG_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc) - set(CODAC_PKG_CONFIG_CFLAGS "") - set(CODAC_PKG_CONFIG_LIBS "-L\${libdir}") + # The modules appended their "-l" flags in dependency order: core first, then + # the modules that link against it. GNU ld resolves static archives left to + # right and keeps from each only what is undefined by the time it reads it, + # so a consumer's link line needs them the other way round -- a module before + # the module it depends on. Reversing the list is what turns the order the + # modules were configured in into a usable link order. CODAC_LIBRARIES, in + # the CMake config file below, gets there instead by exporting real targets + # and letting CMake order them. + if(CODAC_PKG_CONFIG_MODULE_LIBS) + string(STRIP "${CODAC_PKG_CONFIG_MODULE_LIBS}" CODAC_PKG_CONFIG_LIBS) + string(REPLACE " " ";" CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS}") + list(REVERSE CODAC_PKG_CONFIG_LIBS) + string(REPLACE ";" " " CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS}") + else() + set(CODAC_PKG_CONFIG_LIBS "") + endif() # 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. + # and the flags GAOL gives through Codac::gaol, CODAC_INTERVAL_CXX_FLAGS, set + # in the top-level CMakeLists.txt: those of interval arithmetic, and those of + # the fused multiply-add instructions GAOL was compiled with (-mfma and the + # like). A CMake consumer gets them through CODAC_CXX_FLAGS, which is what + # every example uses and what the manual tells users to use; a pkg-config + # consumer has no equivalent. Compiling Codac's headers without + # -frounding-math silently gives up the guarantee the whole library rests on, + # and compiling Eigen's headers under other instruction-set assumptions than + # the prebuilt archives were built with is an ODR/ABI mismatch. foreach(_codac_flags CODAC_PORTABILITY_CXX_FLAGS CODAC_INTERVAL_CXX_FLAGS) if(${_codac_flags}) string(REPLACE ";" " " _codac_pkg_config_flags "${${_codac_flags}}") @@ -56,25 +104,45 @@ # 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. + # installed with Codac, under ${prefix}. The directories the compiler searches + # of its own accord are left out, as CMake leaves them out of its command + # lines. 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() + # The libraries are named by their full path, as CMake hands them to the + # linker on the find_package side, rather than as -L plus -l: there is then no + # chance of another directory on the search path answering for libgaol first, + # and nothing to guess about how the file is named on each platform. They come + # after the Codac archives, which depend on them, and gaol before ultim, which + # it depends on. foreach(item ${CODAC_GAOL_LINK_ITEMS}) codac_gaol_pkg_config_path(item) string(APPEND CODAC_PKG_CONFIG_LIBS " ${item}") endforeach() + # codac-core links PUBLIC against Threads::Threads, and so must anything that + # links codac-core: without it, a program that reaches the peibos threading + # code (the 11_peibos example, say) fails with an undefined reference to + # pthread_create. codac-config.cmake gets there through find_package(Threads); + # a .pc file has to name what that produces itself. + # + # What it produces, and not -pthread on principle: since glibc 2.34 the + # pthread functions are in libc, FindThreads finds them there, and + # Threads::Threads then contributes nothing at all -- so a .pc file naming + # -pthread would be describing a flag the CMake side does not pass. The two + # variables below are FindThreads' own answer: whether the compiler needs the + # flag, and what has to go on the link line, either of which may be empty. + if(THREADS_HAVE_PTHREAD_ARG) + string(APPEND CODAC_PKG_CONFIG_CFLAGS " -pthread") + endif() + if(CMAKE_THREAD_LIBS_INIT) + string(APPEND CODAC_PKG_CONFIG_LIBS " ${CMAKE_THREAD_LIBS_INIT}") + endif() + file(GENERATE OUTPUT ${CODAC_PKG_CONFIG_FILE} CONTENT " prefix=${CMAKE_INSTALL_PREFIX} includedir=\${prefix}/${CMAKE_INSTALL_INCLUDEDIR} @@ -84,9 +152,9 @@ Description: ${PROJECT_DESCRIPTION} Url: ${PROJECT_HOMEPAGE_URL} Version: ${PROJECT_VERSION} - Requires: + Requires: ${CODAC_PKG_CONFIG_REQUIRES} Cflags: ${CODAC_PKG_CONFIG_CFLAGS} - Libs: ${CODAC_PKG_CONFIG_LIBS} + Libs: -L\${libdir} ${CODAC_PKG_CONFIG_LIBS} ") install(FILES ${CODAC_PKG_CONFIG_FILE} DESTINATION ${CMAKE_INSTALL_PKGCONFIG}) @@ -95,20 +163,103 @@ # Generating codac-config.cmake file # ================================== + # The modules are installed as a CMake export set rather than as a handful of + # loose library files, and two things follow from that. + # + # - The dependencies between them are exported with them. codac-graphics + # declares target_link_libraries(... PUBLIC codac-core), so a consumer + # that links Codac::codac-graphics gets Codac::codac-core behind it, in + # that order and repeated as often as the linker needs. Handing out plain + # library paths could not carry that: CODAC_LIBRARIES listed them in + # declaration order, core first, and GNU ld -- which resolves a static + # archive against whatever is undefined when it reads it, and never looks + # back -- left every reference from codac-graphics into codac-core + # unresolved. Every Linux and MinGW job that built an example against an + # installed Codac failed on it; macOS and MSVC, whose linkers rescan, did + # not. + # + # - The rest of the usage requirements travel too: include directories, + # Threads, GAOL, CAPD. + # + # What an export set cannot carry are the dependencies this build fetches + # rather than finds, Eigen and pybind11: they are targets of this project, so + # no consumer's find_package() could define them. The modules keep those as + # build-tree-only dependencies (see src/core/CMakeLists.txt), and the install + # tree reaches Eigen's headers as a plain include directory instead. + install(EXPORT ${PROJECT_NAME}-targets + FILE ${PROJECT_NAME}-targets.cmake + NAMESPACE Codac:: + DESTINATION ${CMAKE_INSTALL_CMAKE}) + + # Builds CODAC_LIBRARIES and CODAC_INCLUDE_DIRS in a single place + function(codac_build_config_vars) + # Base modules, always included. These name the imported targets that + # codac-targets.cmake defines, rather than library paths: naming the + # targets is what lets CMake work the link order out for itself. + set(libraries + Codac::${PROJECT_NAME}-core + Codac::${PROJECT_NAME}-graphics + Codac::${PROJECT_NAME}-unsupported + Codac::gaol + Threads::Threads + ) + + set(include_dirs + \${CODAC_CORE_INCLUDE_DIR}/../ # header root, for + \${CODAC_CORE_INCLUDE_DIR}/../eigen3/ + \${CODAC_CORE_INCLUDE_DIR} + \${CODAC_GRAPHICS_INCLUDE_DIR} + \${CODAC_UNSUPPORTED_INCLUDE_DIR} + ) + + # Optional dependencies (CAPD, SymPy). Both lists are local until every + # module has been added, and only then written to the cache: list(APPEND) + # on a cache variable leaves a local copy behind and the cache entry as it + # was, so the optional modules never used to reach the generated file. + if(WITH_CAPD) + list(APPEND libraries Codac::${PROJECT_NAME}-capd) + list(APPEND include_dirs \${CODAC_CAPD_INCLUDE_DIR}) + # CODAC_CAPD_LIBRARY is kept as its own variable too, for any consumer + # that prefers to opt into CAPD explicitly rather than pull it in via + # the general CODAC_LIBRARIES. The target brings capd::capd and + # codac-core along with it. + set(CODAC_CAPD_LIBRARY Codac::${PROJECT_NAME}-capd CACHE INTERNAL "CAPD module of Codac, with all its dependencies") + endif() + + if(WITH_PYTHON) + list(APPEND libraries Codac::${PROJECT_NAME}-sympy) + list(APPEND include_dirs \${CODAC_SYMPY_INCLUDE_DIR}) + set(CODAC_SYMPY_LIBRARY Codac::${PROJECT_NAME}-sympy CACHE INTERNAL "SymPy module of Codac, with all its dependencies") + endif() + + set(CODAC_LIBRARIES ${libraries} CACHE INTERNAL "Libraries to link against for CODAC") + set(CODAC_INCLUDE_DIRS ${include_dirs} CACHE INTERNAL "Include directories for CODAC") + + # Propagate Codac's own flags (codac_gaol_portability_flags()) and the flags + # GAOL gives (-frounding-math, -mfma, etc.) to 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(cxx_flags ${CODAC_PORTABILITY_CXX_FLAGS} ${CODAC_INTERVAL_CXX_FLAGS}) + set(CODAC_CXX_FLAGS "${cxx_flags}" CACHE INTERNAL "C++ flags for CODAC") + endfunction() + + # Build the variables written into codac-config.cmake below + codac_build_config_vars() + 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}) - + # The file is written in three passes: what locates the installed pieces + # first, then the optional modules, then -- once every dependency the export + # set names is in scope -- the targets file itself and the variables that + # describe it. That last pass has to come last: the CAPD and SymPy blocks + # used to be appended after CODAC_LIBRARIES had already been written out, so + # the libraries they look for could never appear in it. file(WRITE ${CODAC_CMAKE_CONFIG_FILE} "# Try to find Codac # This file has been generated by CMake @@ -140,20 +291,13 @@ ${CODAC_GAOL_CONFIG_SNIPPET} 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 + # e.g. for peibos/threading code -- without it, a downstream target that # actually pulls in that code (such as the 11_peibos example) fails to link - # with an undefined reference to pthread_create. + # with an undefined reference to pthread_create. The imported targets + # included below name Threads::Threads, so it also has to be in scope by + # the time they are defined. 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 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 \"\") - set(CODAC_CXX_FLAGS \"${CODAC_CXX_FLAGS}\") ") if(WITH_PYTHON) @@ -166,8 +310,6 @@ ${CODAC_GAOL_CONFIG_SNIPPET} PATH_SUFFIXES include/${PROJECT_NAME}-sympy) find_library(CODAC_SYMPY_LIBRARY NAMES ${PROJECT_NAME}-sympy PATH_SUFFIXES lib) - - set(CODAC_SYMPY_LIBRARY \${CODAC_SYMPY_LIBRARY} \${CODAC_LIBRARIES}) ") endif() @@ -183,15 +325,23 @@ ${CODAC_GAOL_CONFIG_SNIPPET} PATH_SUFFIXES include/${PROJECT_NAME}-capd) find_library(CODAC_CAPD_LIBRARY NAMES ${PROJECT_NAME}-capd PATH_SUFFIXES lib) - - set(CODAC_CAPD_LIBRARY \${CODAC_CAPD_LIBRARY} \${CODAC_LIBRARIES}) ") endif() file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " - set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY} Codac::gaol Threads::Threads) - ") + + # The Codac modules, as imported targets: + + include(\${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}-targets.cmake) + + set(CODAC_VERSION ${PROJECT_VERSION}) + set(CODAC_LIBRARIES ${CODAC_LIBRARIES}) + set(CODAC_INCLUDE_DIRS ${CODAC_INCLUDE_DIRS}) + + set(CODAC_C_FLAGS \"\") + set(CODAC_CXX_FLAGS \"${CODAC_CXX_FLAGS}\") + ") install(FILES ${CODAC_CMAKE_CONFIG_FILE} DESTINATION ${CMAKE_INSTALL_CMAKE}) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 977aeb023..fb99d18c1 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -340,9 +340,25 @@ ${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}) + # The source directories describe the build tree only: they are gone once + # Codac is installed, and install(EXPORT) below refuses to export a target + # whose interface names a path inside the source tree. The install tree + # answers with the directories the headers were copied into -- include/ + # itself, through which the umbrella header is reached, and + # include/eigen3, where Eigen's headers land. + # + # Eigen is a build-tree dependency for the same reason: it is fetched by this + # build rather than found, so Eigen3::Eigen is a target of ours that no + # consumer's find_package() could produce. Downstream it is reached as the + # plain include directory above, which is all a header-only library needs. + target_include_directories(${PROJECT_NAME}-core PUBLIC + "$" + $ + $ + $ + ) codac_publish_include_dirs(${CODAC_CORE_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME}-core PUBLIC Codac::gaol Eigen3::Eigen Threads::Threads) + target_link_libraries(${PROJECT_NAME}-core PUBLIC Codac::gaol $ Threads::Threads) ################################################################################ @@ -350,7 +366,7 @@ ################################################################################ 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) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-core" PARENT_SCOPE) ################################################################################ @@ -399,6 +415,10 @@ # Install files in system directories # ==================================== - install(TARGETS ${PROJECT_NAME}-core DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-core + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_CORE_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-core) install(FILES ${CODAC_CORE_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-core) diff --git a/src/extensions/capd/CMakeLists.txt b/src/extensions/capd/CMakeLists.txt index c5e3a3758..05326b2f5 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 Codac::gaol Eigen3::Eigen capd::capd) + target_link_libraries(${PROJECT_NAME}-capd PUBLIC ${PROJECT_NAME}-core Codac::gaol $ 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 @@ -30,8 +30,14 @@ list(APPEND CODAC_CAPD_SRC # 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. + # + # Build-tree paths on one side, install-tree paths on the other: see the + # same split in src/core/CMakeLists.txt for why install(EXPORT) needs it. set(CODAC_CAPD_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) - target_include_directories(${PROJECT_NAME}-capd PUBLIC ${CODAC_CAPD_INCLUDE_DIRS}) + target_include_directories(${PROJECT_NAME}-capd PUBLIC + "$" + $ + ) codac_publish_include_dirs(${CODAC_CAPD_INCLUDE_DIRS}) @@ -40,7 +46,14 @@ list(APPEND CODAC_CAPD_SRC ################################################################################ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-capd" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-capd" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-capd" PARENT_SCOPE) + + # libcodac-capd carries unresolved references into CAPD, so a consumer has to + # link CAPD as well -- codac-config.cmake gets there through the capd::capd + # that the exported target names, and this is the pkg-config counterpart. + # "capd" is the single module CAPD installs (capdMake/libcapd/CMakeLists.txt + # writes one capd.pc, into /lib/pkgconfig). + set(CODAC_PKG_CONFIG_REQUIRES "${CODAC_PKG_CONFIG_REQUIRES} capd" PARENT_SCOPE) ################################################################################ @@ -84,6 +97,10 @@ list(APPEND CODAC_CAPD_SRC # Install files in system directories - install(TARGETS ${PROJECT_NAME}-capd DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-capd + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_CAPD_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-capd) install(FILES ${CODAC_CAPD_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-capd) \ No newline at end of file diff --git a/src/extensions/sympy/CMakeLists.txt b/src/extensions/sympy/CMakeLists.txt index 99698751d..27c821323 100644 --- a/src/extensions/sympy/CMakeLists.txt +++ b/src/extensions/sympy/CMakeLists.txt @@ -31,8 +31,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(${PROJECT_NAME}-sympy ${CODAC_SYMPY_SRC}) - target_link_libraries(${PROJECT_NAME}-sympy PUBLIC ${PROJECT_NAME}-core Codac::gaol Eigen3::Eigen) - target_link_libraries(${PROJECT_NAME}-sympy PRIVATE pybind11::pybind11) + target_link_libraries(${PROJECT_NAME}-sympy PUBLIC ${PROJECT_NAME}-core Codac::gaol $) + # pybind11 is fetched by this build too, so it is a build-tree dependency for + # the same reason as Eigen -- see src/core/CMakeLists.txt. A PRIVATE link on + # a static library is still recorded in the interface (as $), + # which is enough for install(EXPORT) to refuse the target otherwise. + target_link_libraries(${PROJECT_NAME}-sympy PRIVATE $) # The directory holding this module's public headers, in its canonical # location under src/. It had none declared at all: its headers were only @@ -40,8 +44,14 @@ # 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. + # + # Build-tree paths on one side, install-tree paths on the other: see the + # same split in src/core/CMakeLists.txt for why install(EXPORT) needs it. set(CODAC_SYMPY_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) - target_include_directories(${PROJECT_NAME}-sympy PUBLIC ${CODAC_SYMPY_INCLUDE_DIRS}) + target_include_directories(${PROJECT_NAME}-sympy PUBLIC + "$" + $ + ) codac_publish_include_dirs(${CODAC_SYMPY_INCLUDE_DIRS}) ################################################################################ @@ -49,7 +59,7 @@ ################################################################################ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-sympy" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-sympy" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-sympy" PARENT_SCOPE) ################################################################################ # Installation of libcodac-sympy files @@ -81,6 +91,10 @@ file(WRITE "${CODAC_SYMPY_MAIN_HEADER}" "${CODAC_SYMPY_HEADER_CONTENT}") endif() - install(TARGETS ${PROJECT_NAME}-sympy DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-sympy + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_SYMPY_PUBLIC_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-sympy) install(FILES ${CODAC_SYMPY_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-sympy) diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index a9e691253..da754f829 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -60,9 +60,14 @@ ${CMAKE_CURRENT_SOURCE_DIR}/styles ) - target_include_directories(${PROJECT_NAME}-graphics PUBLIC ${CODAC_GRAPHICS_INCLUDE_DIRS}) + # Build-tree paths on one side, install-tree paths on the other: see the + # same split in src/core/CMakeLists.txt for why install(EXPORT) needs it. + target_include_directories(${PROJECT_NAME}-graphics PUBLIC + "$" + $ + ) 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) + target_link_libraries(${PROJECT_NAME}-graphics PUBLIC ${PROJECT_NAME}-core Codac::gaol $) ################################################################################ @@ -70,7 +75,7 @@ ################################################################################ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-graphics" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-graphics" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-graphics" PARENT_SCOPE) ################################################################################ @@ -115,6 +120,10 @@ # Install files in system directories - install(TARGETS ${PROJECT_NAME}-graphics DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-graphics + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_GRAPHICS_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-graphics) install(FILES ${CODAC_GRAPHICS_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-graphics) \ No newline at end of file diff --git a/src/unsupported/CMakeLists.txt b/src/unsupported/CMakeLists.txt index 2f5f1b1a4..0f49db62d 100644 --- a/src/unsupported/CMakeLists.txt +++ b/src/unsupported/CMakeLists.txt @@ -27,9 +27,14 @@ # 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}) + # Build-tree paths on one side, install-tree paths on the other: see the + # same split in src/core/CMakeLists.txt for why install(EXPORT) needs it. + target_include_directories(${PROJECT_NAME}-unsupported PUBLIC + "$" + $ + ) codac_publish_include_dirs(${CODAC_UNSUPPORTED_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME}-unsupported PUBLIC ${PROJECT_NAME}-core Eigen3::Eigen) + target_link_libraries(${PROJECT_NAME}-unsupported PUBLIC ${PROJECT_NAME}-core $) ################################################################################ @@ -37,7 +42,7 @@ ################################################################################ set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-unsupported" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-unsupported" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_MODULE_LIBS "${CODAC_PKG_CONFIG_MODULE_LIBS} -l${PROJECT_NAME}-unsupported" PARENT_SCOPE) ################################################################################ @@ -81,6 +86,10 @@ # Install files in system directories - install(TARGETS ${PROJECT_NAME}-unsupported DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(TARGETS ${PROJECT_NAME}-unsupported + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CODAC_UNSUPPORTED_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-unsupported) install(FILES ${CODAC_UNSUPPORTED_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-unsupported) \ No newline at end of file