Repository: conan-io/conan-center-index

Issue:
[package] cmake/3.x.x Does not work with Conan 2.0 due to multiple issues
### Description

Attempting to build CMake with the current CCI recipe results in the following errors due to multiple compatibility issues:

> Exporting the recipe
> ERROR: Error loading conanfile at '/local/cmake/conanfile.py': Unable to load conanfile in /local/cmake/conanfile.py
>   File "<frozen importlib._bootstrap_external>", line 728, in exec_module
>   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
>   File "/local/cmake/conanfile.py", line 5, in <module>
>     from conans import tools, AutoToolsBuildEnvironment, CMake
> ImportError: cannot import name 'tools' from 'conans' (/mathworks/devel/sandbox/marc/conan-2.0/conan-io/conans/__init__.py)

### Package and Environment Details

* Package Name/Version: **cmake/3.24.2**
* Operating System+version: **Debian 10.3**
* Compiler+version: **GCC 8**
* Conan version: **conan 2.0.0.0beta4**
* Python version: **Python 3.7.3**


### Conan profile

-------- Input profiles --------
Profile host:
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.cppstd=gnu14
compiler.libcxx=libstdc++11
compiler.version=8
os=Linux

Profile build:
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.cppstd=gnu14
compiler.libcxx=libstdc++11
compiler.version=8
os=Linux

### Steps to reproduce

conan-2.0 create . --version 3.24.2

### Logs

<details><summary>Click to expand log</summary>

```
Exporting the recipe
ERROR: Error loading conanfile at '/local/cmake/conanfile.py': Unable to load conanfile in /local/cmake/conanfile.py
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/local/cmake/conanfile.py", line 5, in <module>
    from conans import tools, AutoToolsBuildEnvironment, CMake
ImportError: cannot import name 'tools' from 'conans' (/mathworks/devel/sandbox/marc/conan-2.0/conan-io/conans/__init__.py)
```

</details>



Discussion:
I have proposed fixes for these issues that I will submit as pull request.
don't forget to add "relates to https://github.com/conan-io/conan-center-index/issues/13613"
in the PR

Resolving patch:
diff --git a/recipes/cmake/3.x.x/conanfile.py b/recipes/cmake/3.x.x/conanfile.py
--- a/recipes/cmake/3.x.x/conanfile.py
+++ b/recipes/cmake/3.x.x/conanfile.py
@@ -1,20 +1,24 @@
-import os
 from conan import ConanFile
+from conan.tools.files import chdir, copy, rmdir, get, save, load
+from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
+from conan.tools.gnu import Autotools, AutotoolsToolchain, AutotoolsDeps
+from conan.tools.layout import basic_layout
+from conan.tools.build import build_jobs, cross_building, check_min_cppstd
 from conan.tools.scm import Version
-from conan.tools.files import rmdir, get
-from conans import tools, AutoToolsBuildEnvironment, CMake
-from conan.errors import ConanInvalidConfiguration, ConanException
+from conan.errors import ConanInvalidConfiguration
+import os
+import json
 
-required_conan_version = ">=1.49.0"
+required_conan_version = ">=1.50.0"
 
 class CMakeConan(ConanFile):
     name = "cmake"
+    package_type = "application"
     description = "Conan installer for CMake"
     topics = ("cmake", "build", "installer")
     url = "https://github.com/conan-io/conan-center-index"
     homepage = "https://github.com/Kitware/CMake"
     license = "BSD-3-Clause"
-    generators = "cmake"
     settings = "os", "arch", "compiler", "build_type"
 
     options = {
@@ -26,9 +30,6 @@ class CMakeConan(ConanFile):
         "bootstrap": False,
     }
 
-    _source_subfolder = "source_subfolder"
-    _cmake = None
-
     def config_options(self):
         if self.settings.os == "Windows":
             self.options.with_openssl = False
@@ -38,103 +39,112 @@ def requirements(self):
             self.requires("openssl/1.1.1s")
 
     def validate(self):
-        if self.settings.os == "Macos" and self.settings.arch == "x86":
-            raise ConanInvalidConfiguration("CMake does not support x86 for macOS")
-
         if self.settings.os == "Windows" and self.options.bootstrap:
             raise ConanInvalidConfiguration("CMake does not support bootstrapping on Windows")
 
+        if self.settings.os == "Macos" and self.settings.arch == "x86":
+            raise ConanInvalidConfiguration("CMake does not support x86 for macOS")
+
         minimal_cpp_standard = "11"
-        if self.settings.compiler.cppstd:
-            tools.check_min_cppstd(self, minimal_cpp_standard)
+        if self.settings.get_safe("compiler.cppstd"):
+            check_min_cppstd(self, minimal_cpp_standard)
 
         minimal_version = {
             "gcc": "4.8",
             "clang": "3.3",
             "apple-clang": "9",
             "Visual Studio": "14",
+            "msvc": "190",
         }
 
         compiler = str(self.settings.compiler)
         if compiler not in minimal_version:
-            self.output.warn(
-                "{} recipe lacks information about the {} compiler standard version support".format(self.name, compiler))
-            self.output.warn(
-                "{} requires a compiler that supports at least C++{}".format(self.name, minimal_cpp_standard))
+            self.output.warning(
+                f"{self.name} recipe lacks information about the {compiler} compiler standard version support")
+            self.output.warning(
+                f"{self.name} requires a compiler that supports at least C++{minimal_cpp_standard}")
             return
 
         version = Version(self.settings.compiler.version)
         if version < minimal_version[compiler]:
             raise ConanInvalidConfiguration(
-                "{} requires a compiler that supports at least C++{}".format(self.name, minimal_cpp_standard))
+                f"{self.name} requires a compiler that supports at least C++{minimal_cpp_standard}")
+
+    def layout(self):
+        if self.options.bootstrap:
+            basic_layout(self, src_folder="src")
+        else:
+            cmake_layout(self, src_folder="src")
 
     def source(self):
-        get(self, **self.conan_data["sources"][self.version], strip_root=True, destination=self._source_subfolder)
-        rmdir(self, os.path.join(self._source_subfolder, "Tests", "RunCMake", "find_package"))
+        get(self, **self.conan_data["sources"][self.version],
+            destination=self.source_folder, strip_root=True)
+        rmdir(self, os.path.join(self.source_folder, "Tests", "RunCMake", "find_package"))
 
-    def _configure_cmake(self):
-        if not self._cmake:
-            self._cmake = CMake(self)
+    def generate(self):
+        if self.options.bootstrap:
+            tc = AutotoolsToolchain(self)
+            tc.generate()
+            tc = AutotoolsDeps(self)
+            tc.generate()
+            bootstrap_cmake_options = ["--"]
+            bootstrap_cmake_options.append(f'-DCMAKE_CXX_STANDARD={"11" if not self.settings.compiler.cppstd else self.settings.compiler.cppstd}')
+            if self.settings.os == "Linux":
+                if self.options.with_openssl:
+                    openssl = self.dependencies["openssl"]
+                    bootstrap_cmake_options.append("-DCMAKE_USE_OPENSSL=ON")
+                    bootstrap_cmake_options.append(f'-DOPENSSL_USE_STATIC_LIBS={"FALSE" if openssl.options.shared else "TRUE"}')
+                else:
+                    bootstrap_cmake_options.append("-DCMAKE_USE_OPENSSL=OFF")
+            save(self, "bootstrap_args", json.dumps({"bootstrap_cmake_options": ' '.join(arg for arg in bootstrap_cmake_options)}))
+        else:
+            tc = CMakeToolchain(self)
             if not self.settings.compiler.cppstd:
-                self._cmake.definitions["CMAKE_CXX_STANDARD"] = 11
-            self._cmake.definitions["CMAKE_BOOTSTRAP"] = False
+                tc.variables["CMAKE_CXX_STANDARD"] = 11
+            tc.variables["CMAKE_BOOTSTRAP"] = False
             if self.settings.os == "Linux":
-                self._cmake.definitions["CMAKE_USE_OPENSSL"] = self.options.with_openssl
+                tc.variables["CMAKE_USE_OPENSSL"] = self.options.with_openssl
                 if self.options.with_openssl:
-                    self._cmake.definitions["OPENSSL_USE_STATIC_LIBS"] = not self.options["openssl"].shared
-            if tools.cross_building(self):
-                self._cmake.definitions["HAVE_POLL_FINE_EXITCODE"] = ''
-                self._cmake.definitions["HAVE_POLL_FINE_EXITCODE__TRYRUN_OUTPUT"] = ''
-            self._cmake.configure(source_folder=self._source_subfolder)
-
-        return self._cmake
+                    openssl = self.dependencies["openssl"]
+                    tc.variables["OPENSSL_USE_STATIC_LIBS"] = not openssl.options.shared
+            if cross_building(self):
+                tc.variables["HAVE_POLL_FINE_EXITCODE"] = ''
+                tc.variables["HAVE_POLL_FINE_EXITCODE__TRYRUN_OUTPUT"] = ''
+            tc.generate()
 
     def build(self):
         if self.options.bootstrap:
-            with tools.chdir(self._source_subfolder):
-                self.run(['./bootstrap', '--prefix={}'.format(self.package_folder), '--parallel={}'.format(tools.cpu_count())])
-                autotools = AutoToolsBuildEnvironment(self)
+            toolchain_file_content = json.loads(load(self, os.path.join(self.generators_folder, "bootstrap_args")))
+            bootstrap_cmake_options = toolchain_file_content.get("bootstrap_cmake_options")
+            with chdir(self, self.source_folder):
+                self.run(f'./bootstrap --prefix="" --parallel={build_jobs(self)} {bootstrap_cmake_options}')
+                autotools = Autotools(self)
                 autotools.make()
         else:
-            tools.replace_in_file(os.path.join(self._source_subfolder, "CMakeLists.txt"),
-                                  "project(CMake)",
-                                  "project(CMake)\ninclude(\"{}/conanbuildinfo.cmake\")\nconan_basic_setup(NO_OUTPUT_DIRS)".format(
-                                      self.install_folder.replace("\\", "/")))
-            if self.settings.os == "Linux":
-                tools.replace_in_file(os.path.join(self._source_subfolder, "Utilities", "cmcurl", "CMakeLists.txt"),
-                                      "list(APPEND CURL_LIBS ${OPENSSL_LIBRARIES})",
-                                      "list(APPEND CURL_LIBS ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS} pthread)")
-
-            cmake = self._configure_cmake()
+            cmake = CMake(self)
+            cmake.configure()
             cmake.build()
 
     def package(self):
-        self.copy("Copyright.txt", dst="licenses", src=self._source_subfolder)
+        copy(self, "Copyright.txt", self.source_folder, os.path.join(self.package_folder, "licenses"), keep_path=False)
         if self.options.bootstrap:
-            with tools.chdir(self._source_subfolder):
-                autotools = AutoToolsBuildEnvironment(self)
+            with chdir(self, self.source_folder):
+                autotools = Autotools(self)
                 autotools.install()
         else:
-            cmake = self._configure_cmake()
+            cmake = CMake(self)
             cmake.install()
         rmdir(self, os.path.join(self.package_folder, "doc"))
 
     def package_id(self):
         del self.info.settings.compiler
+        del self.info.options.bootstrap
 
     def package_info(self):
-        module_version = "{}.{}".format(Version(self.version).major, Version(self.version).minor)
+        self.cpp_info.includedirs = []
+        self.cpp_info.libdirs = []
 
+        # Needed for compatibility with v1.x - Remove when 2.0 becomes the default
         bindir = os.path.join(self.package_folder, "bin")
-        self.output.info("Appending PATH environment variable: {}".format(bindir))
+        self.output.info(f"Appending PATH environment variable: {bindir}")
         self.env_info.PATH.append(bindir)
-
-        self.buildenv_info.prepend_path("CMAKE_ROOT", self.package_folder)
-        self.env_info.CMAKE_ROOT = self.package_folder
-        mod_path = os.path.join(self.package_folder, "share", f"cmake-{module_version}", "Modules")
-        self.buildenv_info.prepend_path("CMAKE_MODULE_PATH", mod_path)
-        self.env_info.CMAKE_MODULE_PATH = mod_path
-        if not os.path.exists(mod_path):
-            raise ConanException("Module path not found: %s" % mod_path)
-
-        self.cpp_info.includedirs = []


Test patch:
diff --git a/recipes/cmake/3.x.x/test_package/conanfile.py b/recipes/cmake/3.x.x/test_package/conanfile.py
--- a/recipes/cmake/3.x.x/test_package/conanfile.py
+++ b/recipes/cmake/3.x.x/test_package/conanfile.py
@@ -1,23 +1,24 @@
-import os
 from six import StringIO
 from conan import ConanFile
-from conan.tools.build import can_run
+import re
 
 
 class TestPackageConan(ConanFile):
-    settings = "os", "arch"
-    generators = "VirtualRunEnv"
+    settings = "os", "arch", "compiler", "build_type"
+    generators = "VirtualBuildEnv"
+    test_type = "explicit"
 
-    def requirements(self):
-        self.requires(self.tested_reference_str)
+    def build_requirements(self):
+        self.tool_requires(self.tested_reference_str)
 
     def test(self):
-        if can_run(self):
-            output = StringIO()
-            self.run("cmake --version", env="conanrun", output=output)
-            output_str = str(output.getvalue())
-            self.output.info("Installed version: {}".format(output_str))
-            require_version = str(self.deps_cpp_info["cmake"].version)
-            self.output.info("Expected version: {}".format(require_version))
-            assert_cmake_version = "cmake version %s" % require_version
-            assert(assert_cmake_version in output_str)
+        output = StringIO()
+        # Third arg to self.run renamed "stdout" in Conan 2.0 but 1.x linter doesn't like it
+        self.run("cmake --version", output)
+        output_str = str(output.getvalue())
+        self.output.info("Installed version: {}".format(output_str))
+        tokens = re.split('[@#]', self.tested_reference_str)
+        require_version = tokens[0].split("/", 1)[1]
+        self.output.info("Expected version: {}".format(require_version))
+        assert_cmake_version = "cmake version %s" % require_version
+        assert(assert_cmake_version in output_str)
diff --git a/recipes/cmake/3.x.x/test_v1_package/conanfile.py b/recipes/cmake/3.x.x/test_v1_package/conanfile.py
--- a/recipes/cmake/3.x.x/test_v1_package/conanfile.py
+++ b/recipes/cmake/3.x.x/test_v1_package/conanfile.py
@@ -1,23 +1,23 @@
 import os
 from six import StringIO
 from conan import ConanFile
-from conan.tools.build import can_run
+import re
 
 
 class TestPackageConan(ConanFile):
-    settings = "os", "arch"
+    settings = "os", "arch", "compiler", "build_type"
     test_type = "explicit"
 
-    def requirements(self):
-        self.requires(self.tested_reference_str)
+    def build_requirements(self):
+        self.tool_requires(self.tested_reference_str)
 
     def test(self):
-        if can_run(self):
-            output = StringIO()
-            self.run("cmake --version", output=output, run_environment=True)
-            output_str = str(output.getvalue())
-            self.output.info("Installed version: {}".format(output_str))
-            require_version = str(self.deps_cpp_info["cmake"].version)
-            self.output.info("Expected version: {}".format(require_version))
-            assert_cmake_version = "cmake version %s" % require_version
-            assert(assert_cmake_version in output_str)
+        output = StringIO()
+        self.run("cmake --version", output=output, run_environment=False)
+        output_str = str(output.getvalue())
+        self.output.info("Installed version: {}".format(output_str))
+        tokens = re.split('[@#]', self.tested_reference_str)
+        require_version = tokens[0].split("/", 1)[1]
+        self.output.info("Expected version: {}".format(require_version))
+        assert_cmake_version = "cmake version %s" % require_version
+        assert(assert_cmake_version in output_str)