Repository: conan-io/conan-center-index

Issue:
[package] CGAL/5.3: recipe does not make consumers aware of -frounding-mode flag with CMakeToolchain/CMakeDeps generators & gcc/g++
# Context
[cgal_test.zip](https://github.com/conan-io/conan-center-index/files/8372793/cgal_test.zip)

CGAL makes use of interval arithmetic in many places; some of these uses are in header files,
which means that they may require the client/consumer to compile using certain flags on some compilers
to make the compiler aware that the code makes use of rounding modes different from the default (round-to-nearest, ties-to-even).

Failure to do so may or may not silently make the compiler output incorrect code, depending on the use case and whether the compiler finds opportunities to apply "optimizations" that break the code because they are based on the assumption of a specific rounding mode.

The relevant flags are /fp:strict for MSVC or -frounding-math for GCC.
See, for example, the following somewhat related CGAL issue: https://github.com/CGAL/cgal/issues/3180 for a discussion where the developers conclude that the flag may still be necessary.

CLang does not support such flags and GCCs support is somewhat flaky,
so CGAL developers use a macro (IA_OPACIFY) with compiler detection macros, typically involving volatile or inline asm to hide certain optimization opportunities from the compiler, preventing breakage in most cases (but not always);
it is therefore not trivial to produce a program that actually breaks without the flag.

Usually, e.g., when building and linking against CGAL without using conan, linking with the cgal targets in CMake makes sure that the flag is added to the compilation of the consumer (i.e., it is a PUBLIC compile_option or compile_definition of the cgal target) if the compiler is GCC or MSVC.
When using the CMakeDeps and CMakeToolchain generators, this flag is lost. I did not test any other generators (cmake or cmake_paths).

## Expected Behavior
Linking a consumer library/executable against the cgal::cgal target provided by the CMakeDeps/CMakeToolchain generators should add the flag `-frounding-math` to the compiler command line.

## Actual Behavior
Linking a consumer library/executable against the cgal::cgal target provided by the CMakeDeps/CMakeToolchain generators does not add the flag `-frounding-math` to the compiler command line.

### Package and Environment Details (include every applicable attribute)
  * Package Name/Version: cgal/5.3
  * Operating System+version: Mac OS, though probably every supported OS
  * Compiler+version: GCC 11.2
  * Docker image: Not a docker image, though conanio/gcc11 should also show this behaviour (not tested).
  * Conan version: 1.45.0
  * Python version: Python 3.9.12

### Conan profile (output of `conan profile show default` or `conan profile show <profile>` if custom profile is in use)
```
Configuration for profile gnu11:

[settings]
os=Macos
os_build=Macos
arch=x86_64
arch_build=x86_64
compiler=gcc
compiler.version=11.2
compiler.libcxx=libstdc++11
build_type=Release
[options]
[conf]
[build_requires]
[env]
CC=gcc-11
CXX=g++-11
```


### Steps to reproduce (Include if Applicable)
Unzip the attached (relatively small & simple) example, 
[cgal_test.zip](https://github.com/conan-io/conan-center-index/files/8372855/cgal_test.zip)
create & enter a build directory, run `conan install -s build_type=Release ..` in it.
Make sure that CMake finds and uses g++ and not clang.
Then run `cmake -DCMAKE_BUILD_TYPE=Release ..` and `VERBOSE=1 make` in it.
Check whether the compiler invocations are done using `-frounding-math`. It should be, but isn't (at least in my case).

### Logs (Include/Attach if Applicable)
[log.txt](https://github.com/conan-io/conan-center-index/files/8372849/log.txt)




Discussion:
Looking at CMake documentation & CMakeLists, we should add to `self.cpp_info.cxxflags` in `package_info()`:
-  if gcc: `-frounding-math`
- if msvc: `/fp:strict` & `/fp:except-`
- if intel: `/fp:strict` if windows else  `-fp-model` & `strict`
I guess this would be the appropriate place for such flags, though I'm by no means an expert when it comes to conan.
Good catch on the `/fp:except-` (allowing the compiler to assume no floating point exceptions is sensible and allows some important optimizations).

Resolving patch:
diff --git a/recipes/cgal/all/conanfile.py b/recipes/cgal/all/conanfile.py
--- a/recipes/cgal/all/conanfile.py
+++ b/recipes/cgal/all/conanfile.py
@@ -1,8 +1,11 @@
 import os
+import textwrap
 from conan import ConanFile
-from conan.tools import files
-from conan.tools import scm
-from conans import CMake
+from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
+from conan.tools.files import get, replace_in_file, rmdir, rm, copy, save, export_conandata_patches, patch
+from conan.tools.build import check_min_cppstd
+from conan.tools.scm import Version
+from conan.errors import ConanInvalidConfiguration
 
 required_conan_version = ">=1.50.0"
 
@@ -14,63 +17,167 @@ class CgalConan(ConanFile):
     description = "C++ library that provides easy access to efficient and reliable algorithms"\
                   " in computational geometry."
     topics = ("cgal", "geometry", "algorithms")
+    package_type = "header-library"
     settings = "os", "compiler", "build_type", "arch"
-    generators = "cmake"
-    exports_sources = "CMakeLists.txt"
+    generators = "CMakeDeps"
     short_paths = True
 
-    _cmake = None
-
     @property
-    def _source_subfolder(self):
-        return "source_subfolder"
+    def _min_cppstd(self):
+        return "14"
 
     @property
-    def _build_subfolder(self):
-        return "build_subfolder"
+    def _minimum_compilers_version(self):
+        return {
+            "Visual Studio": "15",
+            "msvc": "191",
+            "gcc": "5",
+            "clang": "5",
+            "apple-clang": "5.1",
+        }
 
-    def _configure_cmake(self):
-        if not self._cmake:
-            self._cmake = CMake(self)
-            self._cmake.definitions["CGAL_HEADER_ONLY"] = "TRUE"
-            self._cmake.configure(build_folder=self._build_subfolder)
-        return self._cmake
+    def export_sources(self):
+        export_conandata_patches(self)
 
-    def _patch_sources(self):
-        if scm.Version(self.version) < "5.3":
-            files.replace_in_file(self, os.path.join(self._source_subfolder, "CMakeLists.txt"),
-                                "CMAKE_SOURCE_DIR", "CMAKE_CURRENT_SOURCE_DIR")
-        else:
-            files.replace_in_file(self, os.path.join(self._source_subfolder, "CMakeLists.txt"),
-                                "if(NOT PROJECT_NAME)", "if(TRUE)")
+    def layout(self):
+        cmake_layout(self, src_folder="src")
 
     def requirements(self):
-        self.requires("boost/1.75.0")
-        self.requires("eigen/3.3.9")
+        self.requires("boost/1.82.0")
+        self.requires("eigen/3.4.0")
         self.requires("mpfr/4.1.0")
 
     def package_id(self):
         self.info.clear()
 
+    def validate(self):
+        if self.settings.compiler.get_safe("cppstd"):
+            check_min_cppstd(self, self._min_cppstd)
+        minimum_version = self._minimum_compilers_version.get(str(self.settings.compiler), False)
+        if minimum_version and Version(self.settings.compiler.version) < minimum_version:
+            raise ConanInvalidConfiguration(
+                f"{self.ref} requires C++{self._min_cppstd}, which your compiler does not support.",
+            )
+
+    def _patch_sources(self):
+        replace_in_file(self,  os.path.join(self.source_folder, "CMakeLists.txt"),
+                        "if(NOT PROJECT_NAME)", "if(1)", strict=False)
+        for it in self.conan_data.get("patches", {}).get(self.version, []):
+            patch(self, **it, strip=2)
+
     def source(self):
-        files.get(self, **self.conan_data["sources"][self.version], destination=self._source_subfolder, strip_root=True)
+        get(self, **self.conan_data["sources"][self.version], strip_root=True)
+
+    def generate(self):
+        tc = CMakeToolchain(self)
+        tc.generate()
 
     def build(self):
         self._patch_sources()
-        cmake = self._configure_cmake()
+        cmake = CMake(self)
+        cmake.configure()
         cmake.build()
 
     def package(self):
-        self.copy("LICENSE*", dst="licenses", src=self._source_subfolder)
-        cmake = self._configure_cmake()
+        cmake = CMake(self)
         cmake.install()
-        files.rmdir(self, os.path.join(self.package_folder, "share"))
-        files.rmdir(self, os.path.join(self.package_folder, "lib", "cmake"))
-        files.rmdir(self, os.path.join(self.package_folder, "bin"))
+        copy(self, "LICENSE*", dst=os.path.join(self.package_folder, "licenses"), src=self.source_folder)
+        rmdir(self, os.path.join(self.package_folder, "share"))
+        rmdir(self, os.path.join(self.package_folder, "bin"))
+        rm(self, "*Config*.cmake", os.path.join(self.package_folder, "lib", "cmake", "CGAL"))
+        rm(self, "Find*.cmake", os.path.join(self.package_folder, "lib", "cmake", "CGAL"))
+        self._create_cmake_module_variables(
+            os.path.join(self.package_folder, self._cmake_module_file_rel_path)
+        )
+
+    def _create_cmake_module_variables(self, module_file):
+        '''
+        CGAL requires C++14, and specific compilers flags to enable the possibility to set FPU rounding modes.
+        This CMake module, from the upsream CGAL pull-request https://github.com/CGAL/cgal/pull/7512, takes
+        care of all the known compilers CGAL has ever supported.
+        '''
+        content = textwrap.dedent('''\
+function(CGAL_setup_CGAL_flags target)
+  # CGAL now requires C++14. `decltype(auto)` is used as a marker of
+  # C++14.
+  target_compile_features(${target} INTERFACE cxx_decltype_auto)
+
+  if(MSVC)
+    target_compile_options(${target} INTERFACE
+      "-D_SCL_SECURE_NO_DEPRECATE;-D_SCL_SECURE_NO_WARNINGS")
+    if(CMAKE_VERSION VERSION_LESS 3.11)
+      target_compile_options(${target} INTERFACE
+        /fp:strict
+        /fp:except-
+        /wd4503  # Suppress warnings C4503 about "decorated name length exceeded"
+        /bigobj  # Use /bigobj by default
+        )
+    else()
+      # The MSVC generator supports `$<COMPILE_LANGUAGE: >` since CMake 3.11.
+      target_compile_options(${target} INTERFACE
+        $<$<COMPILE_LANGUAGE:CXX>:/fp:strict>
+        $<$<COMPILE_LANGUAGE:CXX>:/fp:except->
+        $<$<COMPILE_LANGUAGE:CXX>:/wd4503>  # Suppress warnings C4503 about "decorated name length exceeded"
+        $<$<COMPILE_LANGUAGE:CXX>:/bigobj>  # Use /bigobj by default
+        )
+    endif()
+  elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang")
+    if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 11.0.3)
+      message(STATUS "Apple Clang version ${CMAKE_CXX_COMPILER_VERSION} compiler detected")
+      message(STATUS "Boost MP is turned off for all Apple Clang versions below 11.0.3!")
+      target_compile_options(${target} INTERFACE "-DCGAL_DO_NOT_USE_BOOST_MP")
+    endif()
+  elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel")
+    message( STATUS "Using Intel Compiler. Adding -fp-model strict" )
+    if(WIN32)
+      target_compile_options(${target} INTERFACE "/fp:strict")
+    else()
+      target_compile_options(${target} INTERFACE "-fp-model" "strict")
+    endif()
+  elseif(CMAKE_CXX_COMPILER_ID MATCHES "SunPro")
+    message( STATUS "Using SunPro compiler, using STLPort 4." )
+    target_compile_options(${target} INTERFACE
+      "-features=extensions;-library=stlport4;-D_GNU_SOURCE")
+    target_link_libraries(${target} INTERFACE "-library=stlport4")
+  elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+    if ( RUNNING_CGAL_AUTO_TEST OR CGAL_TEST_SUITE )
+      target_compile_options(${target} INTERFACE "-Wall")
+    endif()
+    if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3)
+      message( STATUS "Using gcc version 4 or later. Adding -frounding-math" )
+      if(CMAKE_VERSION VERSION_LESS 3.3)
+        target_compile_options(${target} INTERFACE "-frounding-math")
+      else()
+        target_compile_options(${target} INTERFACE "$<$<COMPILE_LANGUAGE:CXX>:-frounding-math>")
+      endif()
+    endif()
+    if ( "${GCC_VERSION}" MATCHES "^4.2" )
+      message( STATUS "Using gcc version 4.2. Adding -fno-strict-aliasing" )
+      target_compile_options(${target} INTERFACE "-fno-strict-aliasing" )
+    endif()
+    if ( "${CMAKE_SYSTEM_PROCESSOR}" MATCHES "alpha" )
+      message( STATUS "Using gcc on alpha. Adding -mieee -mfp-rounding-mode=d" )
+      target_compile_options(${target} INTERFACE "-mieee" "-mfp-rounding-mode=d" )
+    endif()
+  endif()
+endfunction()
+
+CGAL_setup_CGAL_flags(CGAL::CGAL)
+''')
+        save(self, module_file, content)
+
+    @property
+    def _cmake_module_file_rel_path(self):
+        return os.path.join("lib", "cmake", "CGAL", f"conan-official-{self.name}-variables.cmake")
+
+    @property
+    def _module_subfolder(self):
+        return os.path.join("lib", "cmake", "CGAL")
 
     def package_info(self):
-        self.cpp_info.names["cmake_find_package"] = "CGAL"
-        self.cpp_info.names["cmake_find_package_multi"] = "CGAL"
         if self.settings.os in ["Linux", "FreeBSD"]:
             self.cpp_info.system_libs.append("m")
-        self.cpp_info.defines.append("CGAL_HEADER_ONLY=1")
+        self.cpp_info.builddirs.append(self._module_subfolder)
+        self.cpp_info.set_property("cmake_find_package", "CGAL")
+        self.cpp_info.set_property("cmake_target_name", "CGAL::CGAL")
+        self.cpp_info.set_property("cmake_build_modules", [self._cmake_module_file_rel_path])


Test patch:
diff --git a/recipes/cgal/all/test_package/CMakeLists.txt b/recipes/cgal/all/test_package/CMakeLists.txt
--- a/recipes/cgal/all/test_package/CMakeLists.txt
+++ b/recipes/cgal/all/test_package/CMakeLists.txt
@@ -1,9 +1,10 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.10)
 project(test_package LANGUAGES CXX)
 
-include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
-conan_basic_setup()
+find_package(CGAL REQUIRED)
+find_package(Eigen3 REQUIRED)
+include(CGAL_Eigen3_support)
 
 add_executable(${PROJECT_NAME} test_package.cpp)
-target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS})
-set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 14)
+target_link_libraries(${PROJECT_NAME} PRIVATE CGAL::CGAL)
+target_link_libraries(${PROJECT_NAME} PRIVATE CGAL::Eigen3_support)
diff --git a/recipes/cgal/all/test_package/conanfile.py b/recipes/cgal/all/test_package/conanfile.py
--- a/recipes/cgal/all/test_package/conanfile.py
+++ b/recipes/cgal/all/test_package/conanfile.py
@@ -1,20 +1,26 @@
 import os
 
 from conan import ConanFile
-from conan.tools import build
-from conans import CMake
-
+from conan.tools.cmake import CMake, cmake_layout
+from conan.tools.build import can_run
 
 class TestPackageConan(ConanFile):
     settings = "os", "compiler", "build_type", "arch"
-    generators = "cmake"
+    generators = "CMakeDeps", "CMakeToolchain"
+
+    def requirements(self):
+        self.requires("eigen/3.4.0")
+        self.requires(self.tested_reference_str)
 
     def build(self):
         cmake = CMake(self)
         cmake.configure()
         cmake.build()
 
+    def layout(self):
+        cmake_layout(self)
+
     def test(self):
-        if not build.cross_building(self):
-            bin_path = os.path.join("bin", "test_package")
-            self.run(bin_path, run_environment=True)
+        if can_run(self):
+            cmd = os.path.join(self.cpp.build.bindir, "test_package")
+            self.run(cmd, env="conanrun")