1import os 2import platform 3from collections import OrderedDict 4 5from conan.tools.microsoft.visual import vs_ide_version 6from conans.client import tools 7from conans.client.build.compiler_flags import architecture_flag, parallel_compiler_cl_flag 8from conans.client.build.cppstd_flags import cppstd_from_settings, cppstd_flag_new as cppstd_flag 9from conans.client.tools import cross_building, Version 10from conans.client.tools.apple import is_apple_os 11from conans.client.tools.oss import get_cross_building_settings 12from conans.errors import ConanException 13from conans.model.build_info import DEFAULT_BIN, DEFAULT_INCLUDE, DEFAULT_LIB, DEFAULT_SHARE 14from conans.util.env_reader import get_env 15from conans.util.log import logger 16 17verbose_definition_name = "CMAKE_VERBOSE_MAKEFILE" 18cmake_install_prefix_var_name = "CMAKE_INSTALL_PREFIX" 19runtime_definition_var_name = "CONAN_LINK_RUNTIME" 20cmake_in_local_cache_var_name = "CONAN_IN_LOCAL_CACHE" 21 22 23def get_toolset(settings, generator): 24 compiler = settings.get_safe("compiler") 25 compiler_base = settings.get_safe("compiler.base") 26 if compiler == "Visual Studio": 27 subs_toolset = settings.get_safe("compiler.toolset") 28 if subs_toolset: 29 return subs_toolset 30 elif compiler == "intel" and compiler_base == "Visual Studio" and "Visual" in generator: 31 compiler_version = settings.get_safe("compiler.version") 32 if compiler_version: 33 compiler_version = compiler_version if "." in compiler_version else \ 34 "%s.0" % compiler_version 35 return "Intel C++ Compiler " + compiler_version 36 return None 37 38 39def get_generator(conanfile): 40 # Returns the name of the generator to be used by CMake 41 if "CONAN_CMAKE_GENERATOR" in os.environ: 42 return os.environ["CONAN_CMAKE_GENERATOR"] 43 44 compiler = conanfile.settings.get_safe("compiler") 45 compiler_base = conanfile.settings.get_safe("compiler.base") 46 arch = conanfile.settings.get_safe("arch") 47 compiler_version = conanfile.settings.get_safe("compiler.version") 48 compiler_base_version = conanfile.settings.get_safe("compiler.base.version") 49 os_build, _, _, _ = get_cross_building_settings(conanfile) 50 51 if not compiler or not compiler_version or not arch: 52 if os_build == "Windows": 53 logger.warning("CMake generator could not be deduced from settings") 54 return None 55 return "Unix Makefiles" 56 57 cmake_years = {'8': '8 2005', 58 '9': '9 2008', 59 '10': '10 2010', 60 '11': '11 2012', 61 '12': '12 2013', 62 '14': '14 2015', 63 '15': '15 2017', 64 '16': '16 2019', 65 '17': '17 2022'} 66 67 if compiler == "msvc": 68 if compiler_version is None: 69 raise ConanException("compiler.version must be defined") 70 vs_version = vs_ide_version(conanfile) 71 return "Visual Studio %s" % cmake_years[vs_version] 72 73 if compiler == "Visual Studio" or compiler_base == "Visual Studio": 74 version = compiler_base_version or compiler_version 75 major_version = version.split('.', 1)[0] 76 _visuals = cmake_years.get(major_version, "UnknownVersion %s" % version) 77 base = "Visual Studio %s" % _visuals 78 return base 79 80 # The generator depends on the build machine, not the target 81 if os_build == "Windows" and compiler != "qcc": 82 return "MinGW Makefiles" # it is valid only under Windows 83 84 return "Unix Makefiles" 85 86 87def get_generator_platform(settings, generator): 88 # Returns the generator platform to be used by CMake 89 if "CONAN_CMAKE_GENERATOR_PLATFORM" in os.environ: 90 return os.environ["CONAN_CMAKE_GENERATOR_PLATFORM"] 91 92 compiler = settings.get_safe("compiler") 93 compiler_base = settings.get_safe("compiler.base") 94 arch = settings.get_safe("arch") 95 96 if settings.get_safe("os") == "WindowsCE": 97 return settings.get_safe("os.platform") 98 99 if (compiler == "Visual Studio" or compiler_base == "Visual Studio") and \ 100 generator and "Visual" in generator: 101 return {"x86": "Win32", 102 "x86_64": "x64", 103 "armv7": "ARM", 104 "armv8": "ARM64"}.get(arch) 105 return None 106 107 108def is_multi_configuration(generator): 109 if not generator: 110 return False 111 return "Visual" in generator or "Xcode" in generator or "Multi-Config" in generator 112 113 114def is_toolset_supported(generator): 115 # https://cmake.org/cmake/help/v3.14/variable/CMAKE_GENERATOR_TOOLSET.html 116 if not generator: 117 return False 118 return "Visual" in generator or "Xcode" in generator or "Green Hills MULTI" in generator 119 120 121def is_generator_platform_supported(generator): 122 # https://cmake.org/cmake/help/v3.14/variable/CMAKE_GENERATOR_PLATFORM.html 123 if not generator: 124 return False 125 return "Visual" in generator or "Green Hills MULTI" in generator 126 127 128def verbose_definition(value): 129 return {verbose_definition_name: "ON" if value else "OFF"} 130 131 132def in_local_cache_definition(value): 133 return {cmake_in_local_cache_var_name: "ON" if value else "OFF"} 134 135 136def runtime_definition(runtime): 137 return {runtime_definition_var_name: "/%s" % runtime} if runtime else {} 138 139 140def build_type_definition(new_build_type, old_build_type, generator, output): 141 if new_build_type and new_build_type != old_build_type: 142 output.warn("Forced CMake build type ('%s') different from the settings build type ('%s')" 143 % (new_build_type, old_build_type)) 144 145 build_type = new_build_type or old_build_type 146 if build_type and not is_multi_configuration(generator): 147 return {"CMAKE_BUILD_TYPE": build_type} 148 return {} 149 150 151class CMakeDefinitionsBuilder(object): 152 153 def __init__(self, conanfile, cmake_system_name=True, make_program=None, 154 parallel=True, generator=None, set_cmake_flags=False, 155 forced_build_type=None, output=None): 156 self._conanfile = conanfile 157 self._forced_cmake_system_name = cmake_system_name 158 self._make_program = make_program 159 self._parallel = parallel 160 self._generator = generator 161 self._set_cmake_flags = set_cmake_flags 162 self._forced_build_type = forced_build_type 163 self._output = output 164 165 def _ss(self, setname): 166 """safe setting""" 167 return self._conanfile.settings.get_safe(setname) 168 169 def _get_cpp_standard_vars(self): 170 cppstd = cppstd_from_settings(self._conanfile.settings) 171 172 if not cppstd: 173 return {} 174 175 definitions = {} 176 if cppstd.startswith("gnu"): 177 definitions["CONAN_CMAKE_CXX_STANDARD"] = cppstd[3:] 178 definitions["CONAN_CMAKE_CXX_EXTENSIONS"] = "ON" 179 else: 180 definitions["CONAN_CMAKE_CXX_STANDARD"] = cppstd 181 definitions["CONAN_CMAKE_CXX_EXTENSIONS"] = "OFF" 182 183 definitions["CONAN_STD_CXX_FLAG"] = cppstd_flag(self._conanfile.settings) 184 return definitions 185 186 def _cmake_cross_build_defines(self, cmake_version): 187 os_ = self._ss("os") 188 arch = self._ss("arch") 189 os_ver_str = "os.api_level" if os_ == "Android" else "os.version" 190 op_system_version = self._ss(os_ver_str) 191 192 env_sn = get_env("CONAN_CMAKE_SYSTEM_NAME", "") 193 env_sn = {"False": False, "True": True, "": None}.get(env_sn, env_sn) 194 cmake_system_name = env_sn or self._forced_cmake_system_name 195 196 os_build, _, _, _ = get_cross_building_settings(self._conanfile) 197 compiler = self._ss("compiler") 198 libcxx = self._ss("compiler.libcxx") 199 200 definitions = OrderedDict() 201 os_ver = get_env("CONAN_CMAKE_SYSTEM_VERSION", op_system_version) 202 toolchain_file = get_env("CONAN_CMAKE_TOOLCHAIN_FILE", "") 203 204 if toolchain_file != "": 205 logger.info("Setting Cross build toolchain file: %s" % toolchain_file) 206 definitions["CMAKE_TOOLCHAIN_FILE"] = toolchain_file 207 return definitions 208 209 if cmake_system_name is False: 210 return definitions 211 212 # System name and system version 213 if cmake_system_name is not True: # String not empty 214 definitions["CMAKE_SYSTEM_NAME"] = cmake_system_name 215 else: # detect if we are cross building and the system name and version 216 skip_x64_x86 = os_ in ['Windows', 'Linux', 'SunOS', 'AIX'] 217 if cross_building(self._conanfile, skip_x64_x86=skip_x64_x86): # We are cross building 218 apple_system_name = "Darwin" if cmake_version and Version(cmake_version) < Version( 219 "3.14") or not cmake_version else None 220 cmake_system_name_map = {"Macos": "Darwin", 221 "iOS": apple_system_name or "iOS", 222 "tvOS": apple_system_name or "tvOS", 223 "watchOS": apple_system_name or "watchOS", 224 "Neutrino": "QNX", 225 "": "Generic", 226 None: "Generic"} 227 definitions["CMAKE_SYSTEM_NAME"] = cmake_system_name_map.get(os_, os_) 228 229 if os_ver: 230 definitions["CMAKE_SYSTEM_VERSION"] = os_ver 231 if is_apple_os(os_): 232 definitions["CMAKE_OSX_DEPLOYMENT_TARGET"] = os_ver 233 234 # system processor 235 cmake_system_processor = os.getenv("CONAN_CMAKE_SYSTEM_PROCESSOR") 236 if cmake_system_processor: 237 definitions["CMAKE_SYSTEM_PROCESSOR"] = cmake_system_processor 238 239 if definitions: # If enabled cross compile 240 for env_var in ["CONAN_CMAKE_FIND_ROOT_PATH", 241 "CONAN_CMAKE_FIND_ROOT_PATH_MODE_PROGRAM", 242 "CONAN_CMAKE_FIND_ROOT_PATH_MODE_LIBRARY", 243 "CONAN_CMAKE_FIND_ROOT_PATH_MODE_INCLUDE"]: 244 245 value = os.getenv(env_var) 246 if value: 247 definitions[env_var] = value 248 249 if self._conanfile and self._conanfile.deps_cpp_info.sysroot: 250 sysroot_path = self._conanfile.deps_cpp_info.sysroot 251 252 if sysroot_path: 253 # Needs to be set here, can't be managed in the cmake generator, CMake needs 254 # to know about the sysroot before any other thing 255 definitions["CMAKE_SYSROOT"] = sysroot_path.replace("\\", "/") 256 257 cmake_sysroot = os.getenv("CONAN_CMAKE_SYSROOT") 258 if cmake_sysroot is not None: 259 definitions["CMAKE_SYSROOT"] = cmake_sysroot.replace("\\", "/") 260 261 # Adjust Android stuff 262 if str(os_) == "Android" and definitions["CMAKE_SYSTEM_NAME"] == "Android": 263 arch_abi_settings = tools.to_android_abi(arch) 264 if arch_abi_settings: 265 definitions["CMAKE_ANDROID_ARCH_ABI"] = arch_abi_settings 266 definitions["ANDROID_ABI"] = arch_abi_settings 267 268 conan_cmake_android_ndk = os.getenv("CONAN_CMAKE_ANDROID_NDK") 269 if conan_cmake_android_ndk: 270 definitions["ANDROID_NDK"] = conan_cmake_android_ndk 271 272 definitions["ANDROID_PLATFORM"] = "android-%s" % op_system_version 273 definitions["ANDROID_TOOLCHAIN"] = compiler 274 275 # More details about supported stdc++ libraries here: 276 # https://developer.android.com/ndk/guides/cpp-support.html 277 if libcxx: 278 definitions["ANDROID_STL"] = libcxx 279 else: 280 definitions["ANDROID_STL"] = 'none' 281 282 logger.info("Setting Cross build flags: %s" 283 % ", ".join(["%s=%s" % (k, v) for k, v in definitions.items()])) 284 return definitions 285 286 def _get_make_program_definition(self): 287 make_program = os.getenv("CONAN_MAKE_PROGRAM") or self._make_program 288 if make_program: 289 if not tools.which(make_program): 290 self._output.warn("The specified make program '%s' cannot be found and will be " 291 "ignored" % make_program) 292 else: 293 self._output.info("Using '%s' as CMAKE_MAKE_PROGRAM" % make_program) 294 return {"CMAKE_MAKE_PROGRAM": make_program} 295 296 return {} 297 298 def get_definitions(self, cmake_version): 299 300 compiler = self._ss("compiler") 301 compiler_base = self._ss("compiler.base") 302 compiler_version = self._ss("compiler.version") 303 arch = self._ss("arch") 304 os_ = self._ss("os") 305 libcxx = self._ss("compiler.libcxx") 306 runtime = self._ss("compiler.runtime") 307 build_type = self._ss("build_type") 308 309 definitions = OrderedDict() 310 definitions.update(runtime_definition(runtime)) 311 definitions.update(build_type_definition(self._forced_build_type, build_type, 312 self._generator, self._output)) 313 314 # don't attempt to override variables set within toolchain 315 if (tools.is_apple_os(os_) and "CONAN_CMAKE_TOOLCHAIN_FILE" not in os.environ 316 and "CMAKE_TOOLCHAIN_FILE" not in definitions): 317 apple_arch = tools.to_apple_arch(arch) 318 if apple_arch: 319 definitions["CMAKE_OSX_ARCHITECTURES"] = apple_arch 320 # xcrun is only available on macOS, otherwise it's cross-compiling and it needs to be 321 # set within CMake toolchain. also, if SDKROOT is set, CMake will use it, and it's not 322 # needed to run xcrun. 323 if platform.system() == "Darwin" and "SDKROOT" not in os.environ: 324 sdk_path = tools.XCRun(self._conanfile.settings).sdk_path 325 if sdk_path: 326 definitions["CMAKE_OSX_SYSROOT"] = sdk_path 327 328 definitions.update(self._cmake_cross_build_defines(cmake_version)) 329 definitions.update(self._get_cpp_standard_vars()) 330 331 definitions.update(in_local_cache_definition(self._conanfile.in_local_cache)) 332 333 if compiler: 334 definitions["CONAN_COMPILER"] = compiler 335 if compiler_version: 336 definitions["CONAN_COMPILER_VERSION"] = str(compiler_version) 337 338 # C, CXX, LINK FLAGS 339 if compiler == "Visual Studio" or compiler_base == "Visual Studio": 340 if self._parallel: 341 flag = parallel_compiler_cl_flag(output=self._output) 342 definitions['CONAN_CXX_FLAGS'] = flag 343 definitions['CONAN_C_FLAGS'] = flag 344 else: # arch_flag is only set for non Visual Studio 345 arch_flag = architecture_flag(self._conanfile.settings) 346 if arch_flag: 347 definitions['CONAN_CXX_FLAGS'] = arch_flag 348 definitions['CONAN_SHARED_LINKER_FLAGS'] = arch_flag 349 definitions['CONAN_C_FLAGS'] = arch_flag 350 if self._set_cmake_flags: 351 definitions['CMAKE_CXX_FLAGS'] = arch_flag 352 definitions['CMAKE_SHARED_LINKER_FLAGS'] = arch_flag 353 definitions['CMAKE_C_FLAGS'] = arch_flag 354 355 if libcxx: 356 definitions["CONAN_LIBCXX"] = libcxx 357 358 # Shared library 359 try: 360 definitions["BUILD_SHARED_LIBS"] = "ON" if self._conanfile.options.shared else "OFF" 361 except ConanException: 362 pass 363 364 # Install to package folder 365 try: 366 if self._conanfile.package_folder: 367 definitions["CMAKE_INSTALL_PREFIX"] = self._conanfile.package_folder 368 definitions["CMAKE_INSTALL_BINDIR"] = DEFAULT_BIN 369 definitions["CMAKE_INSTALL_SBINDIR"] = DEFAULT_BIN 370 definitions["CMAKE_INSTALL_LIBEXECDIR"] = DEFAULT_BIN 371 definitions["CMAKE_INSTALL_LIBDIR"] = DEFAULT_LIB 372 definitions["CMAKE_INSTALL_INCLUDEDIR"] = DEFAULT_INCLUDE 373 definitions["CMAKE_INSTALL_OLDINCLUDEDIR"] = DEFAULT_INCLUDE 374 definitions["CMAKE_INSTALL_DATAROOTDIR"] = DEFAULT_SHARE 375 except AttributeError: 376 pass 377 378 # fpic 379 if not str(os_).startswith("Windows"): 380 fpic = self._conanfile.options.get_safe("fPIC") 381 if fpic is not None: 382 shared = self._conanfile.options.get_safe("shared") 383 fpic_value = "ON" if (fpic or shared) else "OFF" 384 definitions["CONAN_CMAKE_POSITION_INDEPENDENT_CODE"] = fpic_value 385 386 # Adjust automatically the module path in case the conanfile is using the 387 # cmake_find_package or cmake_find_package_multi 388 install_folder = self._conanfile.install_folder.replace("\\", "/") 389 if "cmake_find_package" in self._conanfile.generators: 390 definitions["CMAKE_MODULE_PATH"] = install_folder 391 392 if ("cmake_find_package_multi" in self._conanfile.generators 393 or "CMakeDeps" in self._conanfile.generators): 394 # The cmake_find_package_multi only works with targets and generates XXXConfig.cmake 395 # that require the prefix path and the module path 396 definitions["CMAKE_PREFIX_PATH"] = install_folder 397 definitions["CMAKE_MODULE_PATH"] = install_folder 398 399 definitions.update(self._get_make_program_definition()) 400 401 # Disable CMake export registry #3070 (CMake installing modules in user home's) 402 definitions["CMAKE_EXPORT_NO_PACKAGE_REGISTRY"] = "ON" 403 return definitions 404