File indexing completed on 2025-02-16 05:12:09
0001 import copy 0002 import os 0003 import re 0004 0005 from conans.client import tools 0006 from conans.client.build.visual_environment import (VisualStudioBuildEnvironment, 0007 vs_build_type_flags, vs_std_cpp) 0008 from conans.client.tools.env import environment_append, no_op 0009 from conans.client.tools.intel import intel_compilervars 0010 from conans.client.tools.oss import cpu_count 0011 from conans.client.tools.win import vcvars_command 0012 from conans.errors import ConanException 0013 from conans.model.conan_file import ConanFile 0014 from conans.model.version import Version 0015 from conans.tools import vcvars_command as tools_vcvars_command 0016 from conans.util.env_reader import get_env 0017 from conans.util.files import decode_text, save 0018 from conans.util.runners import version_runner 0019 0020 0021 class MSBuild(object): 0022 0023 def __init__(self, conanfile): 0024 if isinstance(conanfile, ConanFile): 0025 self._conanfile = conanfile 0026 self._settings = self._conanfile.settings 0027 self._output = self._conanfile.output 0028 self.build_env = VisualStudioBuildEnvironment(self._conanfile, 0029 with_build_type_flags=False) 0030 else: # backwards compatible with build_sln_command 0031 self._settings = conanfile 0032 self.build_env = None 0033 0034 def build(self, project_file, targets=None, upgrade_project=True, build_type=None, arch=None, 0035 parallel=True, force_vcvars=False, toolset=None, platforms=None, use_env=True, 0036 vcvars_ver=None, winsdk_version=None, properties=None, output_binary_log=None, 0037 property_file_name=None, verbosity=None, definitions=None, 0038 user_property_file_name=None): 0039 """ 0040 :param project_file: Path to the .sln file. 0041 :param targets: List of targets to build. 0042 :param upgrade_project: Will call devenv to upgrade the solution to your 0043 current Visual Studio. 0044 :param build_type: Use a custom build type instead of the default settings.build_type one. 0045 :param arch: Use a custom architecture name instead of the settings.arch one. 0046 It will be used to build the /p:Configuration= parameter of MSBuild. 0047 It can be used as the key of the platforms parameter. 0048 E.g. arch="x86", platforms={"x86": "i386"} 0049 :param parallel: Will use the configured number of cores in the conan.conf file or 0050 tools.cpu_count(): 0051 In the solution: Building the solution with the projects in parallel. (/m: parameter). 0052 CL compiler: Building the sources in parallel. (/MP: compiler flag) 0053 :param force_vcvars: Will ignore if the environment is already set for a different 0054 Visual Studio version. 0055 :param toolset: Specify a toolset. Will append a /p:PlatformToolset option. 0056 :param platforms: Dictionary with the mapping of archs/platforms from Conan naming to 0057 another one. It is useful for Visual Studio solutions that have a different naming in 0058 architectures. 0059 Example: platforms={"x86":"Win32"} (Visual solution uses "Win32" instead of "x86"). 0060 This dictionary will update the default one: 0061 msvc_arch = {'x86': 'x86', 'x86_64': 'x64', 'armv7': 'ARM', 'armv8': 'ARM64'} 0062 :param use_env: Applies the argument /p:UseEnv=true to the MSBuild call. 0063 :param vcvars_ver: Specifies the Visual Studio compiler toolset to use. 0064 :param winsdk_version: Specifies the version of the Windows SDK to use. 0065 :param properties: Dictionary with new properties, for each element in the dictionary 0066 {name: value} it will append a /p:name="value" option. 0067 :param output_binary_log: If set to True then MSBuild will output a binary log file 0068 called msbuild.binlog in the working directory. It can also be used to set the name of 0069 log file like this output_binary_log="my_log.binlog". 0070 This parameter is only supported starting from MSBuild version 15.3 and onwards. 0071 :param property_file_name: When None it will generate a file named conan_build.props. 0072 You can specify a different name for the generated properties file. 0073 :param verbosity: Specifies verbosity level (/verbosity: parameter) 0074 :param definitions: Dictionary with additional compiler definitions to be applied during 0075 the build. Use value of None to set compiler definition with no value. 0076 :param user_property_file_name: Specify a user provided .props file with custom definitions 0077 :return: status code of the MSBuild command invocation 0078 """ 0079 property_file_name = property_file_name or "conan_build.props" 0080 self.build_env.parallel = parallel 0081 0082 with environment_append(self.build_env.vars): 0083 # Path for custom properties file 0084 props_file_contents = self._get_props_file_contents(definitions) 0085 property_file_name = os.path.abspath(property_file_name) 0086 save(property_file_name, props_file_contents) 0087 vcvars = vcvars_command(self._conanfile.settings, arch=arch, force=force_vcvars, 0088 vcvars_ver=vcvars_ver, winsdk_version=winsdk_version, 0089 output=self._output) 0090 command = self.get_command(project_file, property_file_name, 0091 targets=targets, upgrade_project=upgrade_project, 0092 build_type=build_type, arch=arch, parallel=parallel, 0093 toolset=toolset, platforms=platforms, 0094 use_env=use_env, properties=properties, 0095 output_binary_log=output_binary_log, 0096 verbosity=verbosity, 0097 user_property_file_name=user_property_file_name) 0098 command = "%s && %s" % (vcvars, command) 0099 context = no_op() 0100 if self._conanfile.settings.get_safe("compiler") == "Intel" and \ 0101 self._conanfile.settings.get_safe("compiler.base") == "Visual Studio": 0102 context = intel_compilervars(self._conanfile.settings, arch) 0103 with context: 0104 return self._conanfile.run(command) 0105 0106 def get_command(self, project_file, props_file_path=None, targets=None, upgrade_project=True, 0107 build_type=None, arch=None, parallel=True, toolset=None, platforms=None, 0108 use_env=False, properties=None, output_binary_log=None, verbosity=None, 0109 user_property_file_name=None): 0110 0111 targets = targets or [] 0112 if not isinstance(targets, (list, tuple)): 0113 raise TypeError("targets argument should be a list") 0114 properties = properties or {} 0115 command = [] 0116 0117 if upgrade_project and not get_env("CONAN_SKIP_VS_PROJECTS_UPGRADE", False): 0118 command.append('devenv "%s" /upgrade &&' % project_file) 0119 else: 0120 self._output.info("Skipped sln project upgrade") 0121 0122 build_type = build_type or self._settings.get_safe("build_type") 0123 arch = arch or self._settings.get_safe("arch") 0124 if toolset is None: # False value to skip adjusting 0125 toolset = tools.msvs_toolset(self._settings) 0126 verbosity = os.getenv("CONAN_MSBUILD_VERBOSITY") or verbosity or "minimal" 0127 if not build_type: 0128 raise ConanException("Cannot build_sln_command, build_type not defined") 0129 if not arch: 0130 raise ConanException("Cannot build_sln_command, arch not defined") 0131 0132 command.append('msbuild "%s" /p:Configuration="%s"' % (project_file, build_type)) 0133 msvc_arch = {'x86': 'x86', 0134 'x86_64': 'x64', 0135 'armv7': 'ARM', 0136 'armv8': 'ARM64'} 0137 if platforms: 0138 msvc_arch.update(platforms) 0139 msvc_arch = msvc_arch.get(str(arch)) 0140 if self._settings.get_safe("os") == "WindowsCE": 0141 msvc_arch = self._settings.get_safe("os.platform") 0142 try: 0143 sln = tools.load(project_file) 0144 pattern = re.compile(r"GlobalSection\(SolutionConfigurationPlatforms\)" 0145 r"(.*?)EndGlobalSection", re.DOTALL) 0146 solution_global = pattern.search(sln).group(1) 0147 lines = solution_global.splitlines() 0148 lines = [s.split("=")[0].strip() for s in lines] 0149 except Exception: 0150 pass # TODO: !!! what are we catching here? tools.load? .group(1)? .splitlines? 0151 else: 0152 config = "%s|%s" % (build_type, msvc_arch) 0153 if config not in "".join(lines): 0154 self._output.warn("***** The configuration %s does not exist in this solution *****" 0155 % config) 0156 self._output.warn("Use 'platforms' argument to define your architectures") 0157 0158 if output_binary_log: 0159 msbuild_version = MSBuild.get_version(self._settings) 0160 if msbuild_version >= "15.3": # http://msbuildlog.com/ 0161 command.append('/bl' if isinstance(output_binary_log, bool) 0162 else '/bl:"%s"' % output_binary_log) 0163 else: 0164 raise ConanException("MSBuild version detected (%s) does not support " 0165 "'output_binary_log' ('/bl')" % msbuild_version) 0166 0167 if use_env: 0168 command.append('/p:UseEnv=true') 0169 else: 0170 command.append('/p:UseEnv=false') 0171 0172 if msvc_arch: 0173 command.append('/p:Platform="%s"' % msvc_arch) 0174 0175 if parallel: 0176 command.append('/m:%s' % cpu_count(output=self._output)) 0177 0178 if targets: 0179 command.append("/target:%s" % ";".join(targets)) 0180 0181 if toolset: 0182 command.append('/p:PlatformToolset="%s"' % toolset) 0183 0184 if verbosity: 0185 command.append('/verbosity:%s' % verbosity) 0186 0187 if props_file_path or user_property_file_name: 0188 paths = [os.path.abspath(props_file_path)] if props_file_path else [] 0189 if isinstance(user_property_file_name, list): 0190 paths.extend([os.path.abspath(p) for p in user_property_file_name]) 0191 elif user_property_file_name: 0192 paths.append(os.path.abspath(user_property_file_name)) 0193 paths = ";".join(paths) 0194 command.append('/p:ForceImportBeforeCppTargets="%s"' % paths) 0195 0196 for name, value in properties.items(): 0197 command.append('/p:%s="%s"' % (name, value)) 0198 0199 return " ".join(command) 0200 0201 def _get_props_file_contents(self, definitions=None): 0202 def format_macro(name, value): 0203 return "%s=%s" % (name, value) if value is not None else name 0204 # how to specify runtime in command line: 0205 # https://stackoverflow.com/questions/38840332/msbuild-overrides-properties-while-building-vc-project 0206 runtime_library = {"MT": "MultiThreaded", 0207 "MTd": "MultiThreadedDebug", 0208 "MD": "MultiThreadedDLL", 0209 "MDd": "MultiThreadedDebugDLL"}.get( 0210 self._settings.get_safe("compiler.runtime"), "") 0211 0212 if self.build_env: 0213 # Take the flags from the build env, the user was able to alter them if needed 0214 flags = copy.copy(self.build_env.flags) 0215 flags.append(self.build_env.std) 0216 else: # To be removed when build_sln_command is deprecated 0217 flags = vs_build_type_flags(self._settings, with_flags=False) 0218 flags.append(vs_std_cpp(self._settings)) 0219 0220 if definitions: 0221 definitions = ";".join([format_macro(name, definitions[name]) for name in definitions]) 0222 0223 flags_str = " ".join(list(filter(None, flags))) # Removes empty and None elements 0224 additional_node = "<AdditionalOptions>" \ 0225 "{} %(AdditionalOptions)" \ 0226 "</AdditionalOptions>".format(flags_str) if flags_str else "" 0227 runtime_node = "<RuntimeLibrary>" \ 0228 "{}" \ 0229 "</RuntimeLibrary>".format(runtime_library) if runtime_library else "" 0230 definitions_node = "<PreprocessorDefinitions>" \ 0231 "{};%(PreprocessorDefinitions)" \ 0232 "</PreprocessorDefinitions>".format(definitions) if definitions else "" 0233 template = """<?xml version="1.0" encoding="utf-8"?> 0234 <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 0235 <ItemDefinitionGroup> 0236 <ClCompile> 0237 {runtime_node} 0238 {additional_node} 0239 {definitions_node} 0240 </ClCompile> 0241 </ItemDefinitionGroup> 0242 </Project>""".format(**{"runtime_node": runtime_node, 0243 "additional_node": additional_node, 0244 "definitions_node": definitions_node}) 0245 return template 0246 0247 @staticmethod 0248 def get_version(settings): 0249 msbuild_cmd = "msbuild -version" 0250 vcvars = tools_vcvars_command(settings) 0251 command = "%s && %s" % (vcvars, msbuild_cmd) 0252 try: 0253 out = version_runner(command, shell=True) 0254 version_line = decode_text(out).split("\n")[-1] 0255 prog = re.compile(r"(\d+\.){2,3}\d+") 0256 result = prog.match(version_line).group() 0257 return Version(result) 0258 except Exception as e: 0259 raise ConanException("Error retrieving MSBuild version: '{}'".format(e))