Repository: conan-io/conan-center-index

Issue:
[package] mpfr/4.1.0 lacks support for Conan 2.0
### Description

mpfr has yet to be ported to Conan 2.0

### Package and Environment Details

* Package Name/Version: **mpfr/4.1.0**
* Operating System+version: **Debian 10**
* Compiler+version: **gcc 8**
* Conan version: **conan 2.0.0-beta6**
* Python version: **Python 3.7.3**


### Conan profile

Profile host:
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=8
os=Linux
[options]
cmake*:bootstrap=True
[tool_requires]
!openssl*: cmake/3.24.2

### Steps to reproduce

conan-2.0 build -pr:b tools.jinja -pr:h tools.jinja --name mpfr --version 4.1.0 .

### Logs

<details><summary>Click to expand log</summary>

```
conan-2.0 build -pr:b tools.jinja -pr:h tools.jinja --name mpfr --version 4.1.0 .  # --build=never

-------- Input profiles --------
Profile host:
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=8
os=Linux
[options]
cmake*:bootstrap=True
[tool_requires]
!openssl*: cmake/3.24.2

Profile build:
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=8
os=Linux
[options]
cmake*:bootstrap=True
[tool_requires]
!openssl*: cmake/3.24.2

ERROR: Error loading conanfile at '/tmp/tmp.Sid2gTN7Pg/mpfr/conanfile.py': Unable to load conanfile in /tmp/tmp.Sid2gTN7Pg/mpfr/conanfile.py
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/tmp/tmp.Sid2gTN7Pg/mpfr/conanfile.py", line 1, in <module>
    from conans import ConanFile, AutoToolsBuildEnvironment, CMake, tools
ImportError: cannot import name 'ConanFile' from 'conans' (/mathworks/devel/sandbox/marc/conan-2.0/conan-io/conans/__init__.py)

ERROR: Conan-2.0 build failed for 'mpfr'```

</details>



Discussion:
Will submit proposed compatibility changes shortly

Resolving patch:
diff --git a/recipes/mpfr/all/conanfile.py b/recipes/mpfr/all/conanfile.py
--- a/recipes/mpfr/all/conanfile.py
+++ b/recipes/mpfr/all/conanfile.py
@@ -1,15 +1,21 @@
-from conans import ConanFile, AutoToolsBuildEnvironment, CMake, tools
-from conans.errors import ConanException
-import contextlib
+from conan import ConanFile
+from conan.tools.files import save, load, copy, get, rmdir, replace_in_file, apply_conandata_patches
+from conan.tools.layout import basic_layout
+from conan.tools.cmake import CMakeToolchain, CMakeDeps, CMake, cmake_layout
+from conan.tools.gnu import Autotools, AutotoolsToolchain, AutotoolsDeps
+from conan.tools.microsoft import is_msvc, check_min_vs, unix_path
+from conan.tools.apple import fix_apple_shared_install_name
+from conan.errors import ConanException
 import os
 import re
 import shlex
 
-required_conan_version = ">=1.33.0"
+required_conan_version = ">=1.58.0"
 
 
 class MpfrConan(ConanFile):
     name = "mpfr"
+    package_type = "library"
     description = "The MPFR library is a C library for multiple-precision floating-point computations with " \
                   "correct rounding"
     topics = ("mpfr", "multiprecision", "math", "mathematics")
@@ -20,7 +26,7 @@ class MpfrConan(ConanFile):
     options = {
         "shared": [True, False],
         "fPIC": [True, False],
-        "exact_int": ["mpir", "gmp",]
+        "exact_int": ["mpir", "gmp",],
     }
     default_options = {
         "shared": False,
@@ -29,14 +35,6 @@ class MpfrConan(ConanFile):
     }
 
     exports_sources = "CMakeLists.txt.in", "patches/**"
-    generators = "cmake"
-
-    _autotools = None
-    _cmake = None
-
-    @property
-    def _source_subfolder(self):
-        return "source_subfolder"
 
     @property
     def _settings_build(self):
@@ -44,131 +42,139 @@ def _settings_build(self):
 
     def config_options(self):
         if self.settings.os == "Windows":
-            del self.options.fPIC
+            self.options.rm_safe("fPIC")
 
     def configure(self):
         if self.options.shared:
-            del self.options.fPIC
-        del self.settings.compiler.libcxx
-        del self.settings.compiler.cppstd
+            self.options.rm_safe("fPIC")
+        self.settings.rm_safe("compiler.libcxx")
+        self.settings.rm_safe("compiler.cppstd")
 
     def requirements(self):
         if self.options.exact_int == "gmp":
-            self.requires("gmp/6.2.1")
+            self.requires("gmp/6.2.1", transitive_headers=True)
         elif self.options.exact_int == "mpir":
             self.requires("mpir/3.0.0")
 
     def build_requirements(self):
-        if self._settings_build.os == "Windows" and not tools.get_env("CONAN_BASH_PATH"):
-            self.build_requires("msys2/cci.latest")
+        if self._settings_build.os == "Windows":
+            self.win_bash = True
+            if not self.conf.get("tools.microsoft.bash:path", check_type=str):
+                self.tool_requires("msys2/cci.latest")
+
+    def layout(self):
+        if self.settings.os == "Windows":
+            cmake_layout(self, src_folder="src")
+        else:
+            basic_layout(self, src_folder="src")
 
     def source(self):
-        tools.get(**self.conan_data["sources"][self.version],
-                  destination=self._source_subfolder, strip_root=True)
-
-    def _configure_autotools(self):
-        if self._autotools:
-            return self._autotools
-        self._autotools = AutoToolsBuildEnvironment(self, win_bash=tools.os_info.is_windows)
-        yes_no = lambda v: "yes" if v else "no"
-        args = [
-            "--enable-thread-safe",
-            "--with-gmp-include={}".format(tools.unix_path(os.path.join(self.deps_cpp_info[str(self.options.exact_int)].rootpath, "include"))),
-            "--with-gmp-lib={}".format(tools.unix_path(os.path.join(self.deps_cpp_info[str(self.options.exact_int)].rootpath, "lib"))),
-            "--enable-shared={}".format(yes_no(self.options.shared)),
-            "--enable-static={}".format(yes_no(not self.options.shared)),
-        ]
+        get(self, **self.conan_data["sources"][self.version],
+            destination=self.source_folder, strip_root=True)
+
+    def generate(self):
+        if self.settings.os == "Windows":
+            if is_msvc(self) and not check_min_vs(self, 193, raise_invalid=False) and \
+                not self.conf.get("tools.cmake.cmaketoolchain:generator", check_type=str):
+                # Use NMake to workaround bug in MSBuild versions prior to 2022 that shows up as:
+                #    error MSB6001: Invalid command line switch for "cmd.exe". System.ArgumentException: Item
+                #                   has already been added. Key in dictionary: 'tmp'  Key being added: 'TMP'
+                self.conf.define("tools.cmake.cmaketoolchain:generator", "NMake Makefiles")
+            tc = CMakeToolchain(self)
+            tc.generate()
+            tc = CMakeDeps(self)
+            tc.generate()
+        else: # Even with multiple toolchains (see below), can only have one "deps" generator as multiple ones will collide
+            tc = AutotoolsDeps(self)
+            tc.generate()
+
+        # Setup autotools on all platforms because we need to run autotools.configure when using CMake
+        tc = AutotoolsToolchain(self)
+        tc.configure_args.append("--enable-thread-safe")
+        tc.configure_args.append(f'--with-gmp={unix_path(self, self.dependencies[str(self.options.exact_int)].package_folder)}')
         if self.settings.compiler == "clang":
             # warning: optimization flag '-ffloat-store' is not supported
-            args.append("mpfr_cv_gcc_floatconv_bug=no")
+            tc.configure_args.append("mpfr_cv_gcc_floatconv_bug=no")
             if self.settings.arch == "x86":
                 # fatal error: error in backend: Unsupported library call operation!
-                args.append("--disable-float128")
+                tc.configure_args.append("--disable-float128")
+
         if self.options.exact_int == "mpir":
-            self._autotools.include_paths.append(self.build_folder)
-        if self.settings.compiler == "Visual Studio":
-            self._autotools.flags.append("-FS")
-        self._autotools.libs = []
-        self._autotools.configure(args=args, configure_dir=self._source_subfolder)
-        return self._autotools
-
-    def _configure_cmake(self):
-        if self._cmake:
-            return self._cmake
-        self._cmake = CMake(self)
-        self._cmake.configure(source_dir=os.path.join(self._source_subfolder, "src"))
-        return self._cmake
+            tc.extra_cflags.append(f"-I{self.build_folder}")
+        if is_msvc(self):
+            tc.extra_cflags.append("-FS")
+
+        env = tc.environment()
+        if is_msvc(self):
+            env.define("CC", "cl -nologo")
+            env.define("CXX", "cl -nologo")
+        tc.generate(env) # Create conanbuild.conf
 
     def _extract_makefile_variable(self, makefile, variable):
-        makefile_contents = tools.load(makefile)
-        match = re.search("{}[ \t]*=[ \t]*((?:(?:[a-zA-Z0-9 \t.=/_-])|(?:\\\\\"))*(?:\\\\\n(?:(?:[a-zA-Z0-9 \t.=/_-])|(?:\\\"))*)*)\n".format(variable), makefile_contents)
+        makefile_contents = load(self, makefile)
+        match = re.search(f'{variable}[ \t]*=[ \t]*((?:(?:[a-zA-Z0-9 \t.=/_-])|(?:\\\\\"))*(?:\\\\\n(?:(?:[a-zA-Z0-9 \t.=/_-])|(?:\\\"))*)*)\n', makefile_contents)
         if not match:
-            raise ConanException("Cannot extract variable {} from {}".format(variable, makefile_contents))
+            raise ConanException(f"Cannot extract variable {variable} from {makefile_contents}")
         lines = [line.strip(" \t\\") for line in match.group(1).split()]
         return [item for line in lines for item in shlex.split(line) if item]
 
     def _extract_mpfr_autotools_variables(self):
-        makefile_am = os.path.join(self._source_subfolder, "src", "Makefile.am")
+        makefile_am = os.path.join(self.source_folder, "src", "Makefile.am") # src/src/Makefile.am
         makefile = os.path.join("src", "Makefile")
         sources = self._extract_makefile_variable(makefile_am, "libmpfr_la_SOURCES")
         headers = self._extract_makefile_variable(makefile_am, "include_HEADERS")
         defs = self._extract_makefile_variable(makefile, "DEFS")
         return sources, headers, defs
 
-    @contextlib.contextmanager
-    def _build_context(self):
-        if self.settings.compiler == "Visual Studio":
-            with tools.vcvars(self):
-                env = {
-                    "AR": "lib",
-                    "CC": "cl -nologo",
-                    "CXX": "cl -nologo",
-                    "LD": "link",
-                    "NM": "dumpbin -symbols",
-                    "OBJDUMP": ":",
-                    "RANLIB": ":",
-                    "STRIP": ":",
-                }
-                with tools.environment_append(env):
-                    yield
-        else:
-            yield
-
     def build(self):
-        for patch in self.conan_data.get("patches", {}).get(self.version, []):
-            tools.patch(**patch)
+        apply_conandata_patches(self)
+
+        if self.settings.os == "Windows": # Allow mixed shared and static libs
+            replace_in_file(self, os.path.join(self.source_folder, "configure"),
+                'as_fn_error $? "libgmp isn\'t provided as a DLL: use --enable-static --disable-shared" "$LINENO" 5',
+                '# as_fn_error $? "libgmp isn\'t provided as a DLL: use --enable-static --disable-shared" "$LINENO" 5')
+            replace_in_file(self, os.path.join(self.source_folder, "configure"),
+                'as_fn_error $? "libgmp is provided as a DLL: use --disable-static --enable-shared" "$LINENO" 5',
+                '# as_fn_error $? "libgmp is provided as a DLL: use --disable-static --enable-shared" "$LINENO" 5')
+
         if self.options.exact_int == "mpir":
-            tools.replace_in_file(os.path.join(self._source_subfolder, "configure"),
+            replace_in_file(self, os.path.join(self.source_folder, "configure"),
                                        "-lgmp", "-lmpir")
-            tools.replace_in_file(os.path.join(self._source_subfolder, "src", "mpfr.h"),
+            replace_in_file(self, os.path.join(self.source_folder, "src", "mpfr.h"),
                                        "<gmp.h>", "<mpir.h>")
-            tools.save("gmp.h", "#pragma once\n#include <mpir.h>\n")
-        with self._build_context():
-            autotools = self._configure_autotools()
+            save(self, "gmp.h", "#pragma once\n#include <mpir.h>\n")
+
+        autotools = Autotools(self)
+        autotools.configure() # Need to generate Makefile to extract variables for CMake below
+
         if self.settings.os == "Windows":
-            cmakelists_in = tools.load("CMakeLists.txt.in")
+            cmakelists_in = load(self, os.path.join(self.export_sources_folder, "CMakeLists.txt.in"))
             sources, headers, definitions = self._extract_mpfr_autotools_variables()
-            tools.save(os.path.join(self._source_subfolder, "src", "CMakeLists.txt"), cmakelists_in.format(
+            sources = ["src/" + src for src in sources]
+            headers = ["src/" + hdr for hdr in headers]
+            save(self, os.path.join(self.source_folder, "CMakeLists.txt"), cmakelists_in.format(
                 mpfr_sources=" ".join(sources),
                 mpfr_headers=" ".join(headers),
                 definitions=" ".join(definitions),
             ))
-            cmake = self._configure_cmake()
+            cmake = CMake(self)
+            cmake.configure()
             cmake.build()
         else:
             autotools.make(args=["V=0"])
 
     def package(self):
-        self.copy("COPYING", dst="licenses", src=self._source_subfolder)
+        copy(self, "COPYING", dst=os.path.join(self.package_folder, "licenses"), src=self.source_folder)
         if self.settings.os == "Windows":
-            cmake = self._configure_cmake()
+            cmake = CMake(self)
             cmake.install()
         else:
-            autotools = self._configure_autotools()
+            autotools = Autotools(self)
             autotools.install()
+            fix_apple_shared_install_name(self)
             os.unlink(os.path.join(self.package_folder, "lib", "libmpfr.la"))
-            tools.rmdir(os.path.join(self.package_folder, "share"))
-            tools.rmdir(os.path.join(self.package_folder, "lib", "pkgconfig"))
+            rmdir(self, os.path.join(self.package_folder, "share"))
+            rmdir(self, os.path.join(self.package_folder, "lib", "pkgconfig"))
 
     def package_info(self):
         self.cpp_info.libs = ["mpfr"]


Test patch:
diff --git a/recipes/mpfr/all/test_package/CMakeLists.txt b/recipes/mpfr/all/test_package/CMakeLists.txt
--- a/recipes/mpfr/all/test_package/CMakeLists.txt
+++ b/recipes/mpfr/all/test_package/CMakeLists.txt
@@ -1,8 +1,7 @@
 cmake_minimum_required(VERSION 3.3.0)
-project(test_package C)
+project(test_package LANGUAGES C)
 
-include("${CMAKE_BINARY_DIR}/conanbuildinfo.cmake")
-conan_basic_setup()
+find_package(mpfr REQUIRED)
 
 add_executable(${PROJECT_NAME} test_package.c)
-target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS})
+target_link_libraries(${PROJECT_NAME} PRIVATE mpfr::mpfr)
diff --git a/recipes/mpfr/all/test_package/conanfile.py b/recipes/mpfr/all/test_package/conanfile.py
--- a/recipes/mpfr/all/test_package/conanfile.py
+++ b/recipes/mpfr/all/test_package/conanfile.py
@@ -1,10 +1,18 @@
-from conans import ConanFile, CMake, tools
+from conan import ConanFile
+from conan.tools.cmake import CMake, cmake_layout
+from conan.tools.build import cross_building
 import os
 
 
 class TestPackageConan(ConanFile):
     settings = "os", "compiler", "build_type", "arch"
-    generators = "cmake"
+    generators = "CMakeToolchain", "CMakeDeps", "VirtualRunEnv"
+
+    def layout(self):
+        cmake_layout(self)
+
+    def requirements(self):
+        self.requires(self.tested_reference_str)
 
     def build(self):
         cmake = CMake(self)
@@ -12,6 +20,6 @@ def build(self):
         cmake.build()
 
     def test(self):
-        if not tools.cross_building(self):
-            bin_path = os.path.join("bin", "test_package")
-            self.run(bin_path, run_environment=True)
+        if not cross_building(self):
+            bin_path =  os.path.join(self.cpp.build.bindirs[0], "test_package")
+            self.run(bin_path, env="conanrun")
diff --git a/recipes/mpfr/all/test_v1_package/CMakeLists.txt b/recipes/mpfr/all/test_v1_package/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/recipes/mpfr/all/test_v1_package/CMakeLists.txt
@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 3.3.0)
+project(test_package C)
+
+include("${CMAKE_BINARY_DIR}/conanbuildinfo.cmake")
+conan_basic_setup(TARGETS)
+
+find_package(mpfr CONFIG REQUIRED)
+
+add_executable(${PROJECT_NAME} ../test_package/test_package.c)
+target_link_libraries(${PROJECT_NAME} mpfr::mpfr)
diff --git a/recipes/mpfr/all/test_v1_package/conanfile.py b/recipes/mpfr/all/test_v1_package/conanfile.py
new file mode 100644
--- /dev/null
+++ b/recipes/mpfr/all/test_v1_package/conanfile.py
@@ -0,0 +1,17 @@
+from conans import ConanFile, CMake, tools
+import os
+
+
+class TestPackageConan(ConanFile):
+    settings = "os", "compiler", "build_type", "arch"
+    generators = "cmake", "cmake_find_package_multi"
+
+    def build(self):
+        cmake = CMake(self)
+        cmake.configure()
+        cmake.build()
+
+    def test(self):
+        if not tools.cross_building(self):
+            bin_path = os.path.join("bin", "test_package")
+            self.run(bin_path, run_environment=True)