Repository: conan-io/conan

Issue:
Run cmake wrapped with scan-build
I would like to be able to run cmake wrapped with clang's scan-build to perform a static analysis while building. However the CMake class hard-codes the name of the executable to be 'cmake'. It would be great if it were possible to override this.


Discussion:
Ok, I see a workaround: I can use the "helper attributes" approach as described under https://docs.conan.io/en/latest/reference/build_helpers/cmake.html.
We could also add a `CONAN_CMAKE_PROGRAM` (similar to the current `CONAN_MAKE_PROGRAM`)
A CONAN_CMAKE_PROGRAM option (?) sounds good as well, and is probably more consistent.
> A CONAN_CMAKE_PROGRAM option

An entry in the `conan.conf` /  `environment variable`

Resolving patch:
diff --git a/conans/client/build/cmake.py b/conans/client/build/cmake.py
--- a/conans/client/build/cmake.py
+++ b/conans/client/build/cmake.py
@@ -21,7 +21,7 @@ class CMake(object):
 
     def __init__(self, conanfile, generator=None, cmake_system_name=True,
                  parallel=True, build_type=None, toolset=None, make_program=None,
-                 set_cmake_flags=False, msbuild_verbosity=None):
+                 set_cmake_flags=False, msbuild_verbosity=None, cmake_program=None):
         """
         :param conanfile: Conanfile instance
         :param generator: Generator name to use or none to autodetect
@@ -33,7 +33,8 @@ def __init__(self, conanfile, generator=None, cmake_system_name=True,
                 applies only to certain generators (e.g. Visual Studio)
         :param set_cmake_flags: whether or not to set CMake flags like CMAKE_CXX_FLAGS, CMAKE_C_FLAGS, etc.
                it's vital to set for certain projects (e.g. using CMAKE_SIZEOF_VOID_P or CMAKE_LIBRARY_ARCHITECTURE)
-        "param msbuild_verbosity: verbosity level for MSBuild (in case of Visual Studio generator)
+        :param msbuild_verbosity: verbosity level for MSBuild (in case of Visual Studio generator)
+        :param cmake_program: Path to the custom cmake executable
         """
         if not isinstance(conanfile, ConanFile):
             raise ConanException("First argument of CMake() has to be ConanFile. Use CMake(self)")
@@ -41,6 +42,7 @@ def __init__(self, conanfile, generator=None, cmake_system_name=True,
         self._conanfile = conanfile
         self._settings = conanfile.settings
         self._build_type = build_type or conanfile.settings.get_safe("build_type")
+        self._cmake_program = os.getenv("CONAN_CMAKE_PROGRAM") or cmake_program or "cmake"
 
         self.generator = generator or get_generator(conanfile.settings)
         self.parallel = parallel
@@ -187,7 +189,7 @@ def configure(self, args=None, defs=None, source_dir=None, build_dir=None,
             pkg_env = {"PKG_CONFIG_PATH": self._conanfile.install_folder} if set_env else {}
 
         with tools.environment_append(pkg_env):
-            command = "cd %s && cmake %s" % (args_to_string([self.build_dir]), arg_list)
+            command = "cd %s && %s %s" % (args_to_string([self.build_dir]), self._cmake_program, arg_list)
             if platform.system() == "Windows" and self.generator == "MinGW Makefiles":
                 with tools.remove_from_path("sh"):
                     self._run(command)
@@ -230,7 +232,7 @@ def _build(self, args=None, build_dir=None, target=None):
             self.build_config,
             args_to_string(args)
         ])
-        command = "cmake --build %s" % arg_list
+        command = "%s --build %s" % (self._cmake_program, arg_list)
         self._run(command)
 
     def install(self, args=None, build_dir=None):
diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py
--- a/conans/client/conf/__init__.py
+++ b/conans/client/conf/__init__.py
@@ -105,6 +105,7 @@
 # non_interactive = False             # environment CONAN_NON_INTERACTIVE
 
 # conan_make_program = make           # environment CONAN_MAKE_PROGRAM (overrides the make program used in AutoToolsBuildEnvironment.make)
+# conan_cmake_program = cmake         # environment CONAN_CMAKE_PROGRAM (overrides the make program used in CMake.cmake_program)
 
 # cmake_generator                     # environment CONAN_CMAKE_GENERATOR
 # http://www.vtk.org/Wiki/CMake_Cross_Compiling
@@ -205,6 +206,7 @@ def env_vars(self):
 
                "CONAN_BASH_PATH": self._env_c("general.bash_path", "CONAN_BASH_PATH", None),
                "CONAN_MAKE_PROGRAM": self._env_c("general.conan_make_program", "CONAN_MAKE_PROGRAM", None),
+               "CONAN_CMAKE_PROGRAM": self._env_c("general.conan_cmake_program", "CONAN_CMAKE_PROGRAM", None),
                "CONAN_TEMP_TEST_FOLDER": self._env_c("general.temp_test_folder", "CONAN_TEMP_TEST_FOLDER", "False"),
                "CONAN_SKIP_VS_PROJECTS_UPGRADE": self._env_c("general.skip_vs_projects_upgrade", "CONAN_SKIP_VS_PROJECTS_UPGRADE", "False"),
                "CONAN_HOOKS": self._env_c("hooks", "CONAN_HOOKS", None),


Test patch:
diff --git a/conans/test/unittests/client/build/cmake_test.py b/conans/test/unittests/client/build/cmake_test.py
--- a/conans/test/unittests/client/build/cmake_test.py
+++ b/conans/test/unittests/client/build/cmake_test.py
@@ -1161,6 +1161,32 @@ def test_vcvars_applied(self, generator, compiler, version):
             cmake.build()
             self.assertTrue(vcvars_mock.called, "vcvars weren't called")
 
+    def test_cmake_program(self):
+        conanfile = ConanFileMock()
+        settings = Settings.loads(default_settings_yml)
+        settings.os = "Windows"
+        settings.compiler = "Visual Studio"
+        settings.compiler.version = "14"
+        conanfile.settings = settings
+
+        cmake = CMake(conanfile, parallel=False)
+        cmake.build()
+        self.assertEqual("cmake --build %s" % CMakeTest.scape("."), conanfile.command)
+
+        cmake = CMake(conanfile, cmake_program="use_another_cmake", parallel=False)
+        cmake.build()
+        self.assertEqual("use_another_cmake --build %s" % CMakeTest.scape("."), conanfile.command)
+
+        with tools.environment_append({"CONAN_CMAKE_PROGRAM": "my_custom_cmake"}):
+            cmake = CMake(conanfile, parallel=False)
+            cmake.build()
+            self.assertEqual("my_custom_cmake --build %s"  % CMakeTest.scape("."), conanfile.command)
+
+        with tools.environment_append({"CONAN_CMAKE_PROGRAM": "cmake_from_environment_has_priority"}):
+            cmake = CMake(conanfile, cmake_program="but_not_cmake_from_the_ctor", parallel=False)
+            cmake.build()
+            self.assertEqual("cmake_from_environment_has_priority --build %s" % CMakeTest.scape("."), conanfile.command)
+
     def test_msbuild_verbosity(self):
         settings = Settings.loads(default_settings_yml)
         settings.os = "Windows"
@@ -1204,3 +1230,4 @@ def test_ctest_variables(self):
             cmake.test(output_on_failure=True)
             self.assertEquals(conanfile.captured_env["CTEST_OUTPUT_ON_FAILURE"], "1")
             self.assertEquals(conanfile.captured_env["CTEST_PARALLEL_LEVEL"], "666")
+